I have a list of terms that have a description. I need these description to be displayed right after the term name instead of in a modal.
this is the vue component:
Vue.component('taxonomy-list', {
template : ''+
'<span><template v-for="(room, index) in event.terms[tax]">' +
'<template v-if="room.url"><a :href="room.url">{{room.name}}</a></template>' +
//HERE
'<template v-else-if="room.desc">{{room.name}}</template>' +
//END
'<template v-else>{{room.name}}</template>' +
'<template v-if="index !== (event.terms[tax].length - 1)">, </template>' +
'</template></span>' ,
props : [ 'tax', 'options', 'event', ],
methods : {
openModal: function( item, options, $event ){
this.$emit( 'open-modal', item, options, $event );
},
}
});
on the second case on click it generate a modal with the description that i need.
This is the generated modal:
Vue.component( 'modal-taxonomy', {
template: '#wcs_templates_modal--taxonomy',
props: [ 'data', 'options', 'content', 'classes' ],
mixins: [wcs_modal_mixins]
});
<script type="text/x-template" id="wcs_templates_modal--taxonomy">
<div class="wcs-modal" :class="classes" v-on:click="closeModal">
<div class="wcs-modal__box">
<div class="wcs-modal__inner">
<div class="wcs-modal__content wcs-modal__content--full">
<h2 v-html="data.name"></h2>
<div v-html="data.content"></div>
</div>
</div>
</div>
</div>
</script>
I just need to print the "data.content" right after the name of the {{room.name}}, but i cant manage to do so...
Thanks guys.
EDIT: thats the modalOpen function:
openModal: function( data, options ){
var $self = this;
this.data = data;
this.options = options;
if( ! this.visible ){
this.visible = true;
}
this.loading = true;
if( typeof data.start !== 'undefined' ){
if( typeof this.events[data.id] === 'undefined' ){
this.getClass( data.id );
} else {
this.data.content = this.events[data.id].content;
this.data.image = this.events[data.id].image;
this.loading = ! this.loading;
}
} else {
if( typeof this.taxonomies[data.id] === 'undefined' ){
this.getTaxonomy( data.id );
} else {
this.data.content = this.taxonomies[data.id];
this.loading = ! this.loading;
}
}
}
The modal component uses room.content to display the description, but that property is initialised only if you call openModel, see this.data.content = this.events[data.id].content; and this.data.content = this.taxonomies[data.id];.
IMO you have two options, either duplicate the openModel logic into the HTML or create a computed property that returns the description (I'd choose the latter).
EDIT
Actually, a method should be better because you extract the description from room. You'll need something like:
Vue.component('taxonomy-list', {
template : ''+
'<span><template v-for="(room, index) in event.terms[tax]">' +
'<template v-if="room.url"><a :href="room.url">{{room.name}}</a></template>' +
//HERE
'<template v-else-if="room.desc">{{room.name}}</template>' +
//END
'<template v-else>{{room.name}}</template>' +
// DESCRIPTION
'{{ description(room) }}' +
// END
'<template v-if="index !== (event.terms[tax].length - 1)">, </template>' +
'</template></span>' ,
props : [ 'tax', 'options', 'event', ],
methods : {
openModal: function( item, options, $event ){
this.$emit( 'open-modal', item, options, $event );
},
description: function( room ) {
// insert the logic used by openModal to set content here and return that string
// this.events[data.id].content or this.taxonomies[data.id]
return 'description...';
},
},
},
});
Related
I would like to create a simple web app that can validation form using Vue?
I have two input fields, firstname[1] and firstname[2]
data: {
firstname: ['',''],
}
I want to use the following code to validate the form, but finally not suessful.
computed: {
missfirstname(){
for(var i=1;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
},
methods: {
validateForm: function (e) {
this.attemptSubmit = true;
if(this.missfirstname){
e.preventDefault();
}else{
return true;
}
}
},
Is it possible to use array Loop on the validation form??
here it my code I am using Vue 2
my full code
script.js
var app = new Vue({
el: '#app',
data: {
firstname: ['',''],
firstname_ErrMsg: ['',''],
attemptSubmit: false
},
mounted () {
var self = this;
},
computed: {
missfirstname(){
for(var i=1;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
},
methods: {
validateForm: function (e) {
this.attemptSubmit = true;
if(this.missfirstname){
e.preventDefault();
}else{
return true;
}
}
},
})
index.html
<div id="app">
<form action='process.php' method="post" autocomplete="off" name="submit_form" v-on:submit="validateForm">
firstname1 : <input type='text' id='firstname1' name='firstname1' alt='1' v-model='firstname[1]'>
<div v-if="missfirstname">{{firstname_ErrMsg[1]}}</div>
<br><br>
firstname2 :
<input type='text' id='firstname2' name='firstname2' alt='2' v-model='firstname[2]'>
<div v-if="missfirstname">{{firstname_ErrMsg[2]}}</div>
<br><br>
<input id="submit" class="preview_button" name="submit_form" type="submit">
</form>
</div>
<script src='https://cdn.jsdelivr.net/npm/vue#2.7.8/dist/vue.js'></script>
<script src='js/script.js'></script>
Observations :
missfirstname property should be separate for each field else it will be difficult to assign the error for a specific field.
Instead of iterating over a this.firstname everytime in computed property, you can use #blur and check the value for that particular field which user touched.
v-model properties should be unique else it will update other one on changing current one.
Your user object should be like this and you can use v-for to iterate it in HTML for dynamic fields creation instead of hardcoding.
data: {
user: [{
name: 'firstName1',
missfirstname: false
}, {
name: 'firstName2',
missfirstname: false
}]
}
Now, In validateForm() you can just pass the index of the iteration and check the model value. If value is empty, you can assign missfirstname as true for that particular index object in user array.
Update : As per author comment, assigning object in users array via for loop.
data: {
users: []
},
mounted() {
for(let i = 1; i <= 2; i++) {
this.users.push({
name: 'firstName' + i,
missfirstnam: false
})
}
}
The array of javascript start from index 0.
Which means in your missfirstname(), i should be defined with 0
missfirstname(){
for(var i=0;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
I store results per page number, see below:
<ul v-for="iten in listingsData" :key="item.id">
<li>{{ item.name }}</li>
</ul>
<button #click="pushPrev">Push Prev Results</button>
<button #click="pushNext">Push Next Results</button>
export default {
data(){
return {
listingsData : [],
page : 1
}
},
methods : {
pushNext(){
var _self = this;
axios.get('https://myapi.com/get/users?page='+this.page+1).then(function(response){
_self.page = _self.page + 1;
_self.listingsData = _self.listingsData.push({
page : _self.page,
results : response.data.results
})
});
},
pushPrev(){
var _self = this;
axios.get('https://myapi.com/get/users?page='+this.page-1).then(function(response){
_self.page = _self.page + 1;
_self.listingsData = _self.listingsData.push({
page : _self.page,
results : response.data.results
})
});
}
}
created(){
//load default data
var _self = this;
axios.get('https://myapi.com/get/users?page='+this.page).then(function(response){
_self.listingsData = {
page : 1,
results : response.data.results
}
});
}
}
Now how I can show or loop only results correspond to the the page number this.page?
_self.listingsData = _self.listingsData.push({
page : _self.page, // page number
results : response.data.results
})
What can I try?
I'm using Vue CLI and webpack.
You should iterate over computed property that will return specific page from listingsData, not over all pages.
Something like that:
<template>
<div v-if="currentPage">
<ul v-for="item in currentPage.results" :key="item.id">
<li>{{ item.name }}</li>
</ul>
</div>
<div v-else><i>Loading...</i></div>
<button #click="fetchPage(-1)" :disabled="page===1">Prev Results</button>
<button #click="fetchPage(1)" :disabled="page===10">Next Results</button>
</template>
<script>
const api = "https://myapi.com/get/users";
export default {
data() {
return {
page: 1,
listingsData: [],
};
},
created() {
this.fetchPage(0);
},
computed: {
currentPage() {
return this.listingsData.find(i => i.page === this.page);
},
},
methods: {
fetchPage(diff) {
this.page += diff;
if (this.currentPage) {
return; // page already loaded
}
const page = this.page;
axios.get(api, { params: { page } })
.then((res) => {
this.listingsData.push({
page,
results: res.data.results,
});
});
},
}
};
</script>
Here, we're loading page only if it hasn't been loaded before, and disable Prev/Next buttons if current page is 1/10 respectively.
Here is jsfiddle (with mockup data instead of actual API calls).
Below is vue script - the concern method is called notLegalToShip which checks when age < 3.
export default {
template,
props: ['child', 'l'],
created() {
this.name = this.child.name.slice();
this.date_of_birth = this.child.date_of_birth.slice();
},
data() {
return {
edit: false,
today: moment().format('DD/MM/YYYY'),
childUnder3: false
};
},
computed: {
age() {
var today = new Date();
var birthDate = new Date(this.child.date_of_birth);
var age = today.getFullYear() - birthDate.getFullYear();
var m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
},
methods: Object.assign(
mapActions(['updateChild']),
{
notLegalToShip() {
if(this.age < 3){
this.childUnder3 = true;
}
this.childUnder3 = false;
},
showForm() {
this.edit = true;
},
hideForm() {
this.edit = false;
},
submitForm() {
this.hideForm();
this.updateChild({
child: this.child,
name: this.name,
dateOfBirth: this.date_of_birth,
childUnder3 : this.childUnder3
});
}
}
)
}
Here's the snippet of my template. The input as below.
I want the notLegalToShip method to be triggered when I click arrow changing the year. A warning will appear when childUnder3 is "true". I've tried #change, #input on my input but my method is not triggered at all:
<div>
{{childUnder3}}
{{age}}
<div class="callout danger" v-if="childUnder3">
<h2>Sorry</h2>
<p>Child is under 3!</p>
</div>
<div v-if="!edit">
<a #click.prevent="showForm()" href="#" class="more-link edit-details edit-child">
<i class="fa fa-pencil" aria-hidden="true"></i>{{ l.child.edit_details }}
</a>
</div>
<form v-show="edit" #submit.prevent="submitForm()">
<div class="input-wrap">
<label for="account__child__date-of-birth__date">{{ l.child.date_of_birth }}</label>
<input id="account__child__date-of-birth__date" type="date" name="date_of_birth" v-on:input="notLegalToShip" v-model="date_of_birth" v-validate="'required'">
<p class="error-message" v-show="errors.has('date_of_birth')">{{ l.child.date_of_birth_invalid }}</p>
</div>
</form>
</div>
Any help checking my code above would be appreciated!
You have a couple of problems...
Initialise the name and date_of_birth properties in the data() initialiser so Vue can react to them. You can even initialise them from your child prop there...
data() {
return {
edit: false,
today: moment().format('DD/MM/YYYY'),
name: this.child.name // no need to use slice, strings are immutable
date_of_birth: this.child.date_of_birth
}
}
Use this.date_of_birth inside your age computed property instead of this.child.date_of_birth. This way, it will react to changes made via your v-model="date_of_birth" input element.
Make childUnder3 a computed property, it will be easier that way
childUnder3() {
return this.age < 3
}
Alternately, ditch this and just use v-if="age < 3"
With the above, you no longer need any #input or #change event listeners.
I have a <select> component. Once I select a category I need to get the ID and also a boolean which checks if the category has a subcategory, if it does, I make an API call to fetch the subcategories.
Parent template:
<material-selectcat v-model="catId" name="category" id="selcat">
<option
v-for="cat in cats"
:value="cat.cat_id"
:subcat="cat.has_subCat"
v-text="cat.category_name"
></option>
</material-selectcat>
Child component:
<template>
<select><slot></slot></select>
</template>
<script>
export default {
props: ['value', 'subcat'],
watch: {
watcher: function() {}
},
computed: {
watcher: function() {
this.relaod(this.value);
this.fetchSubCategories(this.value, this.subcat);
}
},
methods: {
relaod: function(value) {
var select = $(this.$el);
select.val(value || this.value);
select.material_select('destroy');
select.material_select();
},
fetchSubCategories: function(val, sub) {
var mdl = this;
var catID = val || this.value;
console.log("sub: " + sub);
mdl.$emit("reset-subcats");
if (catID) {
if (sub == 1) {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
} else {
axios.get(URL.API + '/subcategories/' + catID)
.then(function(response) {
response = response.data.subcatData;
response.unshift({
subcat_id: '0',
subcategory_name: 'All Subcategories'
});
mdl.$emit("update-subcats", response);
$('.subdropdown').fadeIn();
})
.catch(function(error) {
if (error.response.data) {
swal({
title: "Something went wrong",
text: "Please try again",
type: "error",
html: false
});
}
});
}
} else {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
}
}
},
mounted: function() {
var vm = this;
var select = $(this.$el);
select
.val(this.value)
.on('change', function() {
vm.$emit('input', this.value);
});
select.material_select();
},
updated: function() {
this.relaod();
},
destroyed: function() {
$(this.$el).material_select('destroy');
}
}
</script>
But inside the fetchSubCategories() function this line always returns undefined:
console.log("sub: " + sub);
If I check the Vue Devtools tab in my Chrome inspector, I can see that all of the data exists:
cat_id:0
category_name:"All Subcategories"
has_subCat:0
But why doesnt has_subCat get passed as a prop?
The subcat prop is undefined because you are not passing it to the component. But, a subcat prop doesn't make much sense anyway, since you want to check the value for each option in the select which could all be different.
If you compose the options inside of the component definition:
// child component script
props: ['value', 'options'],
// child component template
<template>
<select>
<slot>
<option
v-for="option in options"
:key="option.id"
:value="option.id"
:subcat="option.subcat"
v-text="option.text"
></option>
</slot>
</select>
</template>
And then pass a correctly formatted options prop to the component:
// parent component script
computed: {
options() {
return this.cats.map((cat) => {
return {
id: cat.cat_id,
subcat: cat.has_subcat,
text: cat.category_name
}
});
}
}
// parent component template
<material-selectcat v-model="catId" :options="options" name="category" id="selcat">
</material-selectcat>
Then, you could get the correct subcat value for any option's corresponding value by referencing the options prop in the fetchSubCategories method:
fetchSubCategories: function(val) {
var mdl = this;
var catID = val || this.value;
var sub = (this.options.find(o => o.id === val) || {}).subcat;
...
The reason your value prop is defined in your code is that you are using the v-model directive on the component tag. The v-model directive is just syntactic sugar for :value="something" #input="something = $event.target.value". Since you've specified catID as the v-model, that will be passed to your component as the value prop.
i have problem with input and lists, I would like to go through from input to list element below and add class 'active' to current li element
<div class="control">
<label class="label">Input Test</label>
<input type="text" class="input" #keydown="keyHandler">
<ul>
<li v-for="suggestion in suggestions" v-bind:class="{active}">{{suggestion.message}}</li>
</ul>
</div>
methods : {
keyHandler(e){
if(e.keyCode === 38){
e.preventDefault();
console.log('arrow up')
this.currentKey = e.key
}
else if(e.keyCode === 40){
e.preventDefault();
console.log('arrow down')
this.currentKey = e.key
}
}
}
here is fiddle: https://jsfiddle.net/o8fwf0gh/13/
I would be grateful for help
Hope this helps.
var app = new Vue({
el: '#form',
data: {
currentKey: null,
suggestions: [{
message: 'Foo'
}, {
message: 'Bar'
}, {
message: 'Foobar'
}, {
message: 'pikachu'
}, {
message: 'raichu'
}],
active: false
},
methods: {
keyHandler(e) {
if (e.keyCode === 38) {
e.preventDefault();
console.log('arrow up')
this.setActiveClass(this.currentKey, e.key)
this.currentKey = e.key
} else if (e.keyCode === 40) {
e.preventDefault();
this.setActiveClass(this.currentKey, e.key)
console.log('arrow down')
this.currentKey = e.key
}
},
setActiveClass(previousKey, currentKey) {
if (previousKey) {
var tempIndex = this.suggestions.findIndex(x => x.class == "active");
this.$set(this.suggestions[tempIndex], 'class', 'inactive')
if (currentKey === 'ArrowDown') {
if (tempIndex === this.suggestions.length - 1)
this.$set(this.suggestions[0], 'class', 'active')
else
this.$set(this.suggestions[tempIndex + 1], 'class', 'active')
} else {
if (tempIndex === 0)
this.$set(this.suggestions[this.suggestions.length - 1], 'class', 'active')
else
this.$set(this.suggestions[tempIndex - 1], 'class', 'active')
}
} else {
if(currentKey === 'ArrowUp')
this.$set(this.suggestions[this.suggestions.length - 1], 'class', 'active')
else
this.$set(this.suggestions[0], 'class', 'active')
}
}
}
}
})
and in HTML you can do the following:
<li v-for="suggestion in suggestions" v-bind:class='suggestion.class'>{{suggestion.message}}
Working example here
What you could do is update a data property, let's say selected, with where you should be in the list. By default we'll set it on 0, so that the first element is selected:
data: {
selected: 0,
// other data stuff
}
When pressing the up or down arrow you obviously have to update this.selected as such:
methods : {
keyHandler(e){
if(e.keyCode === 38){
e.preventDefault();
this.selected--;
}
else if(e.keyCode === 40){
e.preventDefault();
this.selected++;
}
}
}
You can then set up your list as such:
<li v-for="(suggestion, index) in suggestions" :class="index === selected ? 'active' : ''">{{suggestion.message}}</li>
What you see inside the class property is know as shorthand syntax. It's basically an if statement that returns 'active' if the index is equal to the list-number that is currently selected. As you can see the index is passed along as a second property inside v-for.
This should do the trick, if I understood correctly what you're trying to achieve. :P