Check if props in vue.js component template exists - javascript

After tried many variations, I don't know how to properly style a component's slot or partial code within <template></template> section.
Is there a way to check if props <counter :recent="true"></counter> from parent level exists, so in a Counter.vue in section <template></template> i would show a special html markup for it ?
=== UPDATED ===
Vue.component('counter', {
template: `
<span class="counter" :number="21" v-text="number">
<span v-if="recent">
since VARTIME
</span>
</span>
`,
data: function(){
return{
count: this.number + 1
}
},
props: {
number: Number,
recent: {
type: Boolean,
default: false
}
},
computed: {
},
created(){
if( this.recent === true ){
console.log('mounted recent true');
}
}
});
new Vue({
el: "#app",
data: {
count: ''
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<counter :number="20" :recent="true"></counter>
</div>

Here the default value for the recent will be false and if the recent is passed from the parent it will get set in the child.
Just use the detailed props definition as mentioned here.
Remove the v-text="number" as it overrides the internal content of the span and therefore the v-if will never executes.
This is a working example
Vue.component('counter', {
template: `
<span class="counter" :number="21">
<span v-if="recent"> since VARTIME </span>
</span>
`,
data: function() {
return {
count: this.number + 1
}
},
props: {
number: Number,
recent: {
type: Boolean,
default: false
}
},
computed: {},
created() {
if ( this.recent === true ) {
console.log('mounted recent true');
}
}
});
new Vue({
el: "#app",
data: {
count: ''
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<counter :number="20" :recent="true"></counter>
</div>

You should add a condition class binding, that you eventually style from css/sass/stylus/less.
This can be done as follows:
<template>
<span class="counter" v-text="count" :class="{ cssClassName: recent}">
<slot></slot>
<span v-if="recent">
since VAR_DATETIME <i class="fa fa-refresh" #click="updateList"></i>
</span>
</span>
</template>
Notice that vuejs will automatically combine multiple class declarations on the same element without problems, as described in the manual.

Related

How to switch between v-html and insert as plain text in vue.js?

I'm looking to toggle between v-html and insert as plain text in vue.js v2. So far I have this
HTML
<div id="app">
<h2 v-html="html ? text : undefined">{{html ? '' : text}}</h2>
<button #click="toggle">
switch
</button>
</div>
JS
new Vue({
el: "#app",
data: {
text:"Hello<br/>World",
html:false
},
methods: {
toggle: function(){
this.html = !this.html;
}
}
})
but this doesn't work when html is false. How can I get this to work? I'm looking for a solution where I don't need to repeat <h2> twice using a v-else. Preferably, if I can do it with just the 1 <h2> tag.
Thanks
Use v-bind with the prop modifier. see docs.
new Vue({
el: "#app",
data: {
text:"Hello<br/>World",
html:false
},
computed: {
headingProps() {
return this.html ? { innerHTML: this.text } : { innerText: this.text } ;
},
},
methods: {
toggle: function(){
this.html = !this.html;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<h2 v-bind.prop="headingProps"></h2>
<button #click="toggle">
switch
</button>
</div>

Vue.js - Computed property not updating - child component

I've created a simple component named DefaultButton.
It bases on properties, that are being set up whenever this component is being created.
The point is that after mounting it, It does not react on changes connected with "defaultbutton", that is an object located in properties
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: Object
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" || this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
Having following component as a parent one:
<template>
<div v-if="state_items.length == 2" class="ui placeholder segment">
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_items[0]" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_items[1]" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_items: []
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
this.state_items[this.current_active_state].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
this.state_items[index].state = 'BUTTON_ACTIVE';
this.current_active_state = index;
}
},
},
mounted: function () {
this.state_items[0].state = 'BUTTON_ACTIVE';
this.state_items[1].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
It clearly shows, using:
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
that the state of these items are being changed, but I am unable to see any results on the generated "DefaultButtons". Classes of objects included in these components are not being changed.
#edit
I've completely changed the way of delivering the data.
Due to this change, I've abandoned the usage of an array; instead I've used two completely not related object.
The result is the same - class of the child component's object is not being
DefaulButton.vue:
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<style lang="scss">
import './DefaultButton.css';
</style>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: {
type: Object,
default: () => ({
id: '',
text: '',
state: '',
})
}
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" ||
this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
ChangeStateBox.vue:
<template>
<div class="ui placeholder segment">
{{ this.state_first.state }}
{{ this.state_second.state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_first" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_second" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_first: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
state_second: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
if(this.current_active_state == 1){
this.$set(this.state_first, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE");
} else {
this.$set(this.state_first, 'state', "BUTTON_ACTIVE");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
}
this.current_active_state = index;
}
},
},
created: function () {
this.state_first.state = 'BUTTON_ACTIVE';
this.state_second.state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
You're declaring props wrong. It is either an array of prop names or it is an object with one entry for each prop declaring its type, or it is an object with one entry for each prop declaring multiple properties.
You have
props: {
state_items: []
},
but to supply a default it should be
props: {
state_items: {
type: Array,
default: []
}
},
But your problem is most likely that you're mutating state_items in such a way that Vue can't react to the change
Your main problem is the way you are changing the button state, according with Array change detection vue can't detect mutations by indexing.
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue When you modify the length of the
array, e.g. vm.items.length = newLength
In case someone will be having the same issue:
#Roy J as well as #DobleL were right.
The reason behind this issue was related with the wrong initialization of state objects.
According to the documentation:
Vue cannot detect property addition or deletion.
Since Vue performs the getter/setter conversion process during instance
initialization, a property must be present in the
data object in order for Vue to convert it and make it reactive.
Before reading this sentence, I used to start with following objects as an initial data:
var local_state_first = {
id: '1',
text: 'Realized',
};
var local_state_second = {
id: '2',
text: 'Active'
};
and the correct version of it looks like this:
var local_state_first = {
id: '1',
text: 'Realized',
state: 'BUTTON_ACTIVE'
};
var local_state_second = {
id: '2',
text: 'Active',
state: 'BUTTON_ACTIVE'
};
whereas declaring the main component as:
<change-state-box :state_first="local_state_first" :state_second="local_state_second" #buttonAction="onbuttonAction"/>
Rest of the code remains the same ( take a look at #edit mark in my main post )

Count the occurrences of a child component

I have a single file component like this:
<template>
<div>
<template v-if="offers.length > 3">
View all offers here
</template>
<template v-else-if="offers.length > 1">
<offer v-for="offer in offers" :data="offer"></offer>
</template>
<template v-else-if="offers.length == 1">
<offer :title="The offer" :data="offers[0]"></offer>
</template>
</div>
</template>
Based on the number of offers, I choose how many to render.
Question: How do I efficiently get/count the number of <offer> components? I also need that number to be reactive.
There's no clean way how.
You could count the children of the current instance that are of a specific type. But you would have to call the "recount" logic on update hook (as well as mounted).
Example:
Vue.component('offer', {
name: 'Offer',
template: '<span> offer </span>'
})
new Vue({
el: '#app',
data: {
offers: [1, 2],
offerCount: 0
},
methods: {
updateOfferCount() {
this.offerCount = this.$children.filter(child => child.constructor.options.name === 'Offer').length;
}
},
updated() {
this.updateOfferCount()
},
mounted() {
this.updateOfferCount()
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div>
<template v-if="offers.length > 3">
View all offers here
</template>
<template v-else-if="offers.length > 1">
<offer v-for="offer in offers" :data="offer"></offer>
</template>
<template v-else-if="offers.length == 1">
<offer :data="offers[0]"></offer>
</template>
</div>
<br>
<button #click="offers.push(123)">Add Offer</button> offerCount: {{ offerCount }}
</div>
I'm answering this based solely on the idea that you want to count instantiations and destructions of Offer components. I'm not sure why you don't just count offers.length. Maybe other things can trigger instantiations.
Have the component emit events on creation and destruction and have the parent track accordingly.
Alternatively (and maybe overkill) you could use Vuex and create a store that the Offer commits to on creation and destruction. This means that you don't have to manually attach #offer-created/destroyed directives every time you put an <offer> in your markup.
Both methods are included in the following example:
const store = new Vuex.Store({
strict: true,
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
}
});
const Offer = {
props: ["data"],
template: "<div>{{data.name}}</div>",
created() {
console.log("Created");
this.$emit("offer-created");
this.$store.commit("increment");
},
destroyed() {
console.log("Destroyed");
this.$emit("offer-destroyed");
this.$store.commit("decrement");
}
};
const app = new Vue({
el: "#app",
store,
components: {
offer: Offer
},
data() {
return {
offers: [],
offerCount: 0
};
},
computed: {
offerCountFromStore() {
return this.$store.state.count;
}
},
methods: {
offerCreated() {
this.offerCount++;
},
offerDestroyed() {
this.offerCount--;
},
addOffer() {
this.offers.push({
name: `Item: ${this.offers.length}`
});
},
removeOffer() {
this.offers.pop();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<div>Offer instances: {{offerCount}}</div>
<div>Offer instances (from store): {{offerCountFromStore}}</div>
<div>
<div v-if="offers.length > 3">
View all offers here
</div>
<div v-else-if="offers.length > 1">
<offer #offer-created="offerCreated" #offer-destroyed="offerDestroyed" v-for="offer in offers" :data="offer"></offer>
</div>
<div v-else-if="offers.length == 1">
<offer #offer-created="offerCreated" #offer-destroyed="offerDestroyed" :data="offers[0]"></offer>
</div>
</div>
<div>
<button #click.prevent="addOffer">Add</button>
<button #click.prevent="removeOffer">Remove</button>
</div>
</div>
The problem with trying to use $children is that it is, inherently, not reactive:
The direct child components of the current instance. Note there’s no
order guarantee for $children, and it is not reactive. If you find
yourself trying to use $children for data binding, consider using an
Array and v-for to generate child components, and use the Array as
the source of truth.

Vue `$refs` issues

I've an issue in this code
let bus = new Vue();
Vue.component('building-inner', {
props: ['floors', 'queue'],
template: `<div class="building-inner">
<div v-for="(floor, index) in floors" class="building-floor" :ref="'floor' + (floors - index)">
<h3>Floor #{{floors - index }}</h3>
<button type="button" class="up" v-if="index !== floors - floors">up</button>
<button type="button" class="down" v-if="index !== floors - 1">down</button>
</div>
</div>`,
beforeMount(){
bus.$emit('floors', this.$refs);
}
})
Vue.component('elevator', {
data: {
floorRefs: null
},
props: ['floors', 'queue'],
template: `<div class="elevator" ref="elevator">
<button type="button" v-for="(btn, index) in floors" class="elevator-btn" #click="go(index + 1)">{{index + 1}}</button>
</div>`,
beforeMount() {
bus.$on('floors', function(val){
this.floorRefs = val;
console.log(this.floorRefs)
})
},
methods: {
go(index) {
this.$refs.elevator.style.top = this.floorRefs['floor' + index][0].offsetTop + 'px'
}
}
})
new Vue({
el: '#building',
data: {
queue: [],
floors: 5,
current: 0
}
})
<div class="building" id="building">
<elevator v-bind:floors="floors" v-bind:queue="queue"></elevator>
<building-inner v-bind:floors="floors" v-bind:queue="queue"></building-inner>
</div>
I tried to access props inside $refs gets me undefined, why?
You should use a mounted hook to get access to the refs, because on "created" event is just instance created not dom.
https://v2.vuejs.org/v2/guide/instance.html
You should always first consider to use computed property and use style binding instead of using refs.
<template>
<div :style="calculatedStyle" > ... </div>
</template>
<script>
{
//...
computed: {
calculatedStyle (){
top: someCalculation(this.someProp),
left: someCalculation2(this.someProp2),
....
}
}
}
</script>
It's bad practice to pass ref to another component, especially if it's no parent-child relationship.
Refs doc
Computed

How to toggle class and template if in array using Vue.js?

UPDATE: Almost there. Added the template to HTML. Added v-if and v-else to the button (couldn't get it to work on the template). Hard coded the example below. Trying to sus out final object path to array - for some reason v-if="this.id == favourites.listings_id" isn't working.
I'm building a favourites feature using vue.js. The create and delete requests both work fine. Finally what's required is to either show class favourited or notFavourited with appropriate action template create or delete depending on if the user has favourited the object or not. The users favourites array passes to the favourites component - and I've been trying to handled it that way.
I've commented out what I'm trying to achieve in the js code below.
HTML
<div id="app">
<listings>
</listings>
</div>
<template id="listing-template">
<div class="container">
<div v-for="listing in listings" class="panel panel-default">
<div class="panel-body">
<div>#{{ listing.id }}</div>
<favourite :id="listing.id"></favourite>
</div>
</div>
</div>
</template>
<template id="favourite-template">
<form><input class="hidden" type="input" name="_method" value="#{{ id }}" v-model="form.listings_id"></input><button v-if="this.id == 2" class="not-favourited" #click.prevent="create(favourite)" :disabled="form.busy"><i class="fa fa-heart" aria-hidden="true"></i><button v-else class="favourited" #click.prevent="delete(favourite)" :disabled="form.busy"><i class="fa fa-heart" aria-hidden="true"></i></button></form>
</template>
VUE.JS
new Vue({
el: '#app'
});
Vue.component('favourite', {
props: ['id'],
data: function() {
return {
form: new SparkForm({
listings_id: ''
}),
favourited: {
color: 'red',
fontSize: '12px'
},
notFavourited: {
color: 'black',
fontSize: '12px'
},
favourites: [], listings: [],
};
},
created() {
this.getFavourites();
},
methods: {
getFavourites() {
this.$http.get('/api/favourites')
.then(response => {
this.favourites = response.data;
});
},
// if favourite exists in favourites array
// then give class favourited and call template delete
// else give class notFavourited and call template create
create() {
Spark.post('/api/favourite', this.form)
.then(favourite => {
this.favourite.push(favourite);
this.form.id = '';
});
},
delete(favourite) {
this.$http.delete('/api/favourite/' + this.id);
this.form.id = '';
}
}
});
Vue.component('listings', {
template: '#listing-template',
data: function() {
return {
listings: [],
};
},
created() {
this.getListings();
},
methods: {
getListings() {
this.$http.get('/api/listings')
.then(response => {
this.listings = response.data;
});
}
}
});
EXAMPLE OBJECT IN FAVOURITES ARRAY
4: Object
created_at: "2016-07-22 21:14:40"
id: 81
listings_id: 1
updated_at: "2016-07-22 21:14:40"
user_id: 3

Categories