I am a newbie to JavaScript. I am trying to figure out what is the best way to write the control logic for my application. I have a list of checkboxes that hide and show different elements depending on certain options that are checked.
For example, I have the following HTML:
<label>
<input type="checkbox" name="productType" value="magazines" v-model="selectedProductType"> Magazines
</label>
<label>
<input type="checkbox" name="productType" value="books" v-model="selectedProductType"> Books
</label>
<label>
<input type="checkbox" name="productType" value="comics" v-model="selectedProductType"> Comics
</label>
<label>
<input type="checkbox" name="productType" value="videos" v-model="selectedProductType"> Videos
</label>
...snip...
And then I am hiding/showing things based on the checkmarked items
above, like so (this is just one example of some of the conditions I need to check):
<section v-if="(selectedOffice.jira) && (selectedProductType == 'comics') || (selectedProductType == 'videos')" id="booksInfo">Some info here</section>
...snip...
The issue is that I have to check different values in the data/model that looks like this:
//selectedOffice: '',
selectedProductType: [],
officeList: [
{
code: 'Blue Office',
jira: true
},
{
code: 'Red Office',
jira: false
}
...snip...
],
productList: [
{
type: 'comics',
url: 'www.comicsurl.com'
},
{
type: 'videos',
url: 'www.videosurl.com'
}
....snip...
]
Does anyone have any advice on the best way to approach the logic for my application? Better flexibility? My plan is to use an API for the data (Wordpress JSON REST API) and I will be able to customize the key/value properties on my own, but need help with the conditional stuff.
Thanks for any help!
Instead of using v-model, use #click or v-on:click to handle the click event instead. I like to keep most logic inside Vue instead of in the HTML so I recommend making use of methods and computed to determine what data to show.
const app = new Vue({
el: '#app',
data: {
selectedProductTypes: {
magazines: false,
books: false,
comics: false,
videos: false
},
productList: [],
typeList: [
{
type: 'magazines',
name: 'Magazines',
text: 'Check out new magazines'
},
{
type: 'books',
name: 'Books',
text: 'Check out new books'
},
{
type: 'comics',
name: 'Comics',
text: 'Check out new comics'
},
{
type: 'videos',
name: 'Videos',
text: 'Check out new videos'
}
]
},
computed: {
dataToBeShown() {
return this.productList
.filter(product => this.selectedProductTypes[product.type] === true) || [];
}
},
created() {
// get json data here
// assignning data here for demo
this.productList = [
{
type: 'magazines',
url: 'www.magazineurl.com'
},
{
type: 'books',
url: 'www.booksurl.com'
},
{
type: 'comics',
url: 'www.comicsurl.com'
},
{
type: 'videos',
url: 'www.videosurl.com'
}
]
},
methods: {
selectProductType(input) {
this.selectedProductTypes[input] = !this.selectedProductTypes[input];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div class="control">
<div v-for="item in typeList">
<label>
<input type="checkbox" #click="selectProductType(item.type)"> <b>{{item.name}}</b> <span>{{item.text}}</span>
</label>
</div>
</div>
<hr/>
<section v-for="item in dataToBeShown">
<div>Type: {{item.type}}</div>
<div>Url: {{item.url}}</div>
</section>
</div>
Related
I have an array with the students.
I have the implementation of the addition of students.
Question: how to clean up after pressing the field? Fields must be cleaned so that when you try to enter new values were not set to the old values.
Tried everything, nothing works. Neither the <form> -> <button type = "reset">, or the selectors ...
What can be done to solve this problem?
index.html
......
<!-- add new student -->
<br>
<input v-model="students.name" placeholder="surname">
<select v-model="students.group">
<option value="1">1</option>
<option value="2">2</option>
</select>
<input v-model="students.year" placeholder="Bitrh year">
<input type="checkbox" v-model="students.done">
<label>done</label>
<input v-model.number="students.mark" type="number" placeholder="mark">
<button type="reset" #click="addStudent()">add to array</button>
</div>
<!-- /add new student -->
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script src="/index.js"></script>
index.js
let students = [
{
id: '1',
name: "Test",
group: "1",
year: "1985",
done: true,
mark: 4,
},
]
var app = new Vue ({
el: '#app',
data: {
students: [],
search: '',
stud: students.slice(),
},
methods: {
deleteStudent(studId) {
this.stud = this.stud.filter(elem => {
return elem.id != studId;
});
},
addStudent() {
const id = this.stud.length + 1;
this.stud.push({ id, ...this.students });
}
}
})
I don't know vue, but you can always select all inputs and set it's value on '' :
document.querySelectorAll('input').forEach(input => input.value='')
in best case apply it to container so sth like that:
document.querySelector('.formClassName').querySelectorAll('input').forEach(input => input.value='')
Just use following:
deleteStudent(studId) {
this.stud.splice(students.id, 1)
}
You can splice something based on it's id the 1 after the comma is that it deletes 1 stud-data from your id.
When you want to use a v-model on your form and let the user enter some data, you should pass an initial object to your inputs. And when the user submits the form, you'll push that data to your array, following to reset your initial object.
So this implementation will help you:
<!-- add new student -->
<br>
<input v-model="initialState.name" placeholder="surname">
<select v-model="initialState.group">
<option value="1">1</option>
<option value="2">2</option>
</select>
<input v-model="initialState.year" placeholder="Bitrh year">
<input type="checkbox" v-model="initialState.done">
<label>done</label>
<input v-model.number="initialState.mark" type="number" placeholder="mark">
<button type="reset" #click="addStudent()">add to array</button>
</div>
<!-- /add new student -->
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script src="/index.js"></script>
var app = new Vue ({
el: '#app',
data: {
students: [],
initialState: {
id: '',
name: '',
group: '',
year: '',
done: true,
mark: 0
},
search: '',
},
methods: {
deleteStudent(studId) {
this.students = this.students.filter(elem => {
return elem.id != studId;
});
},
addStudent() {
const id = this.students.length + 1;
this.students.push({ id, this.initialState });
this.initialState = JSON.parse(JSON.stringify({
id: '',
name: '',
group: '',
year: '',
done: true,
mark: 0
}));
}
}
})
Vue JS computed property is not triggered With this markup
<!-- language: lang-html -->
<p>£{{plant_price}}</p>
<div v-if="selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
<!-- language: lang-js -->
var app = new Vue({
el: '#vueApp',
data: {
selected: {
type: {a: '' , b: ''},
vehicle: '',
plant: {
}
},
computed: {
plant_price: function() {
if (this.selected.plant.variations.length > 0 ) {
var variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
...
selected.plant is populated by clicking on a plant - triggering the updateSelected method.
<div class="col-sm-4" v-for="(plant, i) in step2.plants">
<div v-on:click="updateSelected(plant)" ....
methods: {
updateSelected: function(plant) {
this.selected.plant = plant; // selected plant
if (this.selected.plant.variations.length > 0 ) {
this.selected.plant.selected_variation = this.selected.plant.variations[0].id; // set the selected ID to the 1st variation
I have checked through the debugger, and can see that all the correct properties are available.
selected:Object
type:Object
vehicle: "Truck"
plant:Object
id:26
price:"52"
regular_price:"100"
selected_variation:421
variations:Array[2]
0:Object
id:420
name:"small"
price:52000
regular_price:52000
1:Object
etc...
I have a computed property, which should update the plant_price based on the value of selected.plant.selected_variation.
I grab selected.plant.selected_variation and search through the variations to retrieve the price. If no variation exists, then the plant price is given.
I have a method on each product to update the selected plant. Clicking the product populates the selected.plant and triggers the computed plant_price to update the price (as the value of selected.plant.selected_variation has changed).
However, the computed plant_price is not triggered by the select. Selecting a new variant does what its supposed to, it updates selected.plant.selected_variation. Yet my plant_price doesn't seem to be triggered by it.
So I refactored my code by un-nesting selected.plant.selected_variation. I now hang it off the data object as
data = {
selected_variation: ''
}
and alter my computer property to reference the data as this.selected_variation. My computed property now works??? This makes no sense to me?
selected.plant.selected_variation isn't reactive and VM doesn't see any changes you make to it, because you set it after the VM is already created.
You can make it reactive with Vue.set()
When your AJAX is finished, call
Vue.set(selected, 'plant', {Plant Object})
There're two ways you can do it, what you are dealing with is a nested object, so if you want to notify the changes of selected to the others you have to use
this.$set(this.selected, 'plant', 'AJAX_RESULT')
In the snippet I used a setTimeout in the created method to simulate the Ajax call.
Another way you can do it is instead of making plant_price as a computed property, you can watch the changes of the nested properties
of selected in the watcher, and then update plant_price in the handler, you can check out plant_price_from_watch in the snippet.
Vue.component('v-select', VueSelect.VueSelect);
const app = new Vue({
el: '#app',
data: {
plant_price_from_watch: 'not available',
selected: {
type: {a: '' , b: ''},
vehicle: "Truck"
}
},
computed: {
plant_price() {
return this.setPlantPrice();
}
},
watch: {
selected: {
handler() {
console.log('changed');
this.plant_price_from_watch = this.setPlantPrice();
},
deep: true
}
},
created() {
setTimeout(() => {
this.$set(this.selected, 'plant', {
id: 26,
price: '52',
regular_price: '100',
selected_variation: 421,
variations: [
{
id: 420,
name: "small",
price: 52000,
regular_price: 52000
},
{
id: 421,
name: "smallvvsvsfv",
price: 22000,
regular_price: 22000
}
]
})
}, 3000);
},
methods: {
setPlantPrice() {
if (!this.selected.plant) {
return 'not available'
}
if (this.selected.plant.variations.length > 0 ) {
const variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<p>£{{plant_price}}</p>
<p>£{{plant_price_from_watch}}</p>
<div v-if="selected.plant && selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
</div>
I have the following checkboxes set up:
<label v-for="service in team.services">
<input type="checkbox" v-model="form.services" :id="service.name" :value="service.id"/>
</label>
These are displayed correctly but when checking one checkbox they all get checked as the form.services model gets set to false / true.
However, when changing the model to another data attribute e.g. 'services' everything works as expected. Any reason why this isn't working within SparkForm?
Example data:
data: function() {
return {
form: new SparkForm({
userId: null,
services: [] // always only gets set as true / false
}),
services: [], // works fine
}
},
new Vue({
el: '#app',
data(){
return {
form : {
services : []
},
team : {
services : [
{
"name" : "Service name #1",
"id" : 1
},
{
"name" : "Service name #2",
"id" : 2
}
]
}
}
}
});
<div id="app">
<label v-for="service in team.services">
<input type="checkbox" v-model="form.services" :value="service.id"/>{{service.name}}
</label>
{{form.services}}
</div>
A few days ago I started using vue.js and trying to get the hang of it.
I've been fiddling quite a bit to get this easy example to work: reading the value of selected checkboxes in components with vue.js .
Please see my example on http://jsbin.com/gukoqo/edit?html,js,output
How can I let selected in the parent instance contain the selected values of the checkbox? E.g., filter_a and filter_c are selected, then selected should contain an array: ['filter_a', 'filter_c']
I expected vue.js to make this very easy, but don't know yet how to. Anyone? :)
I'm using the latest vue.js (2.3.3 at the moment)
One possible way.
Vue.component('facet-filter', {
props: ['filter', 'checked'],
template: `<div>
<label class="form-check-label">
<input #change="$emit('change', filter.text, $event)"
class="form-check-input"
type="checkbox"
:value="filter.text"
:checked="checked"
name="filters"> {{filter.text}}
{{$props | json 2}}</label>
</div>`,
});
new Vue({
el: '#app',
data: {
filterFacets: [
{ id: 0, text: 'filter_a' },
{ id: 1, text: 'filter_b' },
{ id: 2, text: 'filter_c' },
{ id: 3, text: 'filter_d' },
],
selected: [], // How can I let this contain ['filter_a', 'filter_b'] etc. when selected?
},
methods:{
onChange(filter, $event){
if ($event.target.checked)
this.selected.push(filter)
else {
const index = this.selected.findIndex(f => f === filter)
if (index >= 0)
this.selected.splice(index, 1)
}
}
}
});
And change your template to
<div id="app">
<facet-filter
v-for="item in filterFacets"
v-bind:filter="item"
v-bind:checked="selected.includes(item.text)"
:key="item.id"
#change="onChange"
>
</facet-filter>
<p><pre>data: {{$data | json 2}}</pre></p>
</div>
Updated bin.
I have a form which generates a list options for the user, each option has a check-box, a label and an input field. The input field should only be shown whilst the check-box is ticked. The options are generated through a JSON call.
However, knockout doesn't seem to be doing what I would have expected when using the visible binding. When I check a row, the text box is correctly shown but when I uncheck it, the text box stays shown.
I suspect this is something to-do with the observable "selected" being overridden or something like that but I am stuck for ideas.
Here is a fiddle showing the issue: http://jsfiddle.net/qccQs/2/
Here is the HTML I am using in the fiddle:
<div data-bind="template: { name: 'reason-template', foreach: reasonList }"></div>
<script type="text/html" id="reason-template">
<div>
<input type="checkbox" data-bind="value: selected" />
<span data-bind="text: name"></span>
<input type="text" class="datepicker" data-bind="value: date, visible: selected" />
</div>
</script>
Here is the javascript that I am using in the fiddle:
function ReasonItem(name) {
this.name = ko.observable(name);
this.date = ko.observable(null);
this.selected = ko.observable(false);
};
function MyViewModel() {
var self = this;
self.reasonList = ko.observableArray([ ])
};
var vm = new MyViewModel();
new Request.JSON({
url: '/echo/json/',
data: {
json: JSON.encode({
data: [
{ name: "Reason 1", selected: false, date: null },
{ name: "Reason 2", selected: false, date: null },
{ name: "Reason 3", selected: false, date: null }
]
}),
delay: 0
},
onSuccess: function(response) {
$.each(response.data, function(index, reason) {
vm.reasonList.push(new ReasonItem(reason.name));
});
}
}).send();
ko.applyBindings(vm);
Any ideas on how I can get this to behave like I expected it to?
For inputs of checkbox type you need to use checked instead of value:
<input type="checkbox" data-bind="checked: selected" />
See Knockout Documentation.