'v-bind' directives require an attribute value - javascript

I am trying to create some type of tree with vue.js and stuck on a problem with element props. Help me out plz.
I've tried :content="{{tempCont}}" and I've tried content="{{tempCont}}", but none of them worked.
Here's the place where I am using tree element:
<div id="tree">
<treeItem :title="Parent" :content="{{tempCont}}"></treeItem>
</div>
Here's the entire tree element:
<template>
<div>
<p v-on:click="openTree">{{title}}</p>
<div id="childs" v-if="childVisibility">
<treeItem v-for="item in content" :key="item" title=item>
</div>
</div>
</template>
<script>
export default {
data: {
childVisibility: false
},
methods: {
openTree: function(){
childVisibility = !childVisibility;
}
},
props: {
title: String,
content: Array,
}
}
</script>
<style scoped>
</style>
I am getting this error:

Use like this: :content="tempCont"
<div id="tree">
<treeItem :title="Parent" :content="tempCont"></treeItem>
</div>

Ok so first of all, when you v-bind something like v-bind:title or :title, what you bind is expressed as a javascript expression.
So if you want your title attribute to be the string Parent, you need either to write it like a native html attribute title="Parent" (notice the lack of :), or as a vue bound attribute v-bind:title="'Parent'" or :title="'Parent'" (notice the use of '' to express a string primitive type in javascript.
Now, the {{ variable }} syntax is used inside vuejs template but you do not need to use it inside v-bind attributes since they are already interpreted as javascript.
So you shouldn't write this:
<div id="tree">
<treeItem :title="Parent" :content="{{tempCont}}"></treeItem>
</div>
but this instead:
<div id="tree">
<treeItem title="Parent" :content="tempCont"></treeItem>
</div>
SincetempCont is already a valid javascript expression.

You don't really need {{}} for passing attributes.
<treeItem :title="Parent" :content="tempCont"></treeItem>
This shall be good enough to work. The puspose of {{}} is to print data and not pass attributes.
Also, in your tree component, it's a good practice to follow object notations in your props. For ex:
props: {
title: {
type: String
},
content: {
type: Array
},
}

Also you should make your components data reactive and making sure that childVisibility is set to this instance rather than a direct reference by setting it like this
export default {
data() {
return {
childVisibility: false
}
},
methods: {
openTree() {
this.childVisibility = !this.childVisibility;
}
},
props: {
title: String,
content: Array,
}
}

Related

Vue Component Conditional Creation

I want to create a list of actions (each of which is a component) conditionally if the object variable store.plan is not empty, I have tried v-if which works well for rendering but not for creating the component.
I get an error:
Uncaught (in promise) TypeError: action is undefined
The full code of this component can be found here.
Can you please tell me how can I handle this problem? thanks in advance.
<template>
<div class="planlist" v-if="parse">
<ul id="planOl">
<Action
v-for="action in store.plan"
:action_id="action.act_id"
:actor="action.actor"
:color="action.color"
:size="action.size"
:lego_name="action.lego"
:pick_pos="action.pick"
:place_pos="action.place"
:blocked="action.blocked"
:status="action.status"
:key="action.act_id"
/>
</ul>
</div>
</template>
<script>
import Action from '../components/Action.vue';
import { store } from '../js/store.js'
export default {
name: 'Plan',
data() {
return {
store,
}
},
computed: {
parse() {
if (store.plan.length > 0) {
return true;
}
return false;
}
},
components: {Action}
}
</script>
Did you try with optional chaining:
parse() {
return store?.plan?.length > 0 ? true : false
}
and don't mix v-if and v-for. Try to create wrapper div with v-if around your component:
<ul id="planOl">
<div v-if="parse">
<Action
v-for="action in store.plan"
:action_id="action.act_id"
:actor="action.actor"
:color="action.color"
:size="action.size"
:lego_name="action.lego"
:pick_pos="action.pick"
:place_pos="action.place"
:blocked="action.blocked"
:status="action.status"
:key="action.act_id"
/>
</div>
</ul>
It is recommended not to use v-if and v-for directives together on the same element due to the syntax ambiguity.
As per your code, Computed property parse is used to check the length of an array. You can move the v-if to a container element (e.g. ul).
In template :
<ul id="planOl" v-if="parse">
<Action v-for="action in store.plan">...</Action>
</ul>
Script :
computed: {
parse() {
return store.plan.length > 0 ? true : false;
}
}
To make the process easy, we can move the store.plan to a computed property to use inside the template and parse property.
Simply return store.plan.length from the computed property will do the job too instead of returning true and false based on condition.
If you want to use v-if just outside the Action component, you can use template to do this. No need for an extra element.
So, below changes can help fixing the issues-
<template>
<div class="planlist">
<ul id="planOl">
<template v-if="parse">
<Action
v-for="action in plan"
:key="action.act_id"
:action_id="action.act_id"
:actor="action.actor"
:color="action.color"
:size="action.size"
:lego_name="action.lego"
:pick_pos="action.pick"
:place_pos="action.place"
:blocked="action.blocked"
:status="action.status"
/>
</template>
</ul>
</div>
</template>
<script>
import Action from "../components/Action.vue";
import { store } from "../js/store.js";
export default {
name: "Plan",
components: {
Action,
},
computed: {
// A computed property to access the plan ()
plan() {
return store.plan;
},
parse() {
/**
* 1. The plan should be available (not null or empty or undefined)
* 2. The plan should be an array so length property can be applied
* 3. If its an array then it should have data (length in other words)
*/
return this.plan && Array.isArray(this.plan) && this.plan.length;
},
},
};
</script>

vue.js component inline style concatenation

I'm stuck with a vue.js component inline style concatenation.
My code is the following:
components: {
'twitter-item': {
props: ['procolors'],
template: '\
<div class="color-quadrat" v-bind:data-id="procolors.id" v-bind:style="background-color: #{procolors.user.profile_background_color}">\
<p>{{procolors.user.profile_background_color}}</p>\
</div>\
'
}
}
I'm trying to get procolors.user.profile_background_color as inline background color. Special is that the value from procolors.user.profile_background_color has no #. So I have to add this in the template.
I tried all kinds of recommendations from the web, but none worked for me.
Any help appreciated!
Use this, which utilizes vue's style object syntax:
:style="{backgroundColor: '#' + procolors.user.profile_background_color}"
You have several choices in how to add styling. If you use v-bind:style="...", or it shorthand :style="...", you need to pass either a valid string, valid variable or a valid object.
Currently you are trying to parse background-color: #{procolors.user.profile_background_color} as javascript, which is not going to work.
You can use a javascript template to create a string:
components: {
'twitter-item': {
props: ['procolors'],
template: '\
<div class="color-quadrat" v-bind:data-id="procolors.id" v-bind:style="`background-color: #${procolors.user.profile_background_color}`">\
<p>{{procolors.user.profile_background_color}}</p>\
</div>\
'
}
}
It is often more readable to refactor it to use a variable or function instead:
components: {
'twitter-item': {
props: ['procolors'],
template: '\
<div class="color-quadrat" v-bind:data-id="procolors.id" v-bind:style="rowColor">\
<p>{{procolors.user.profile_background_color}}</p>\
</div>\
',
computed: {
rowColor () {
return {
"background-color": `#${this.procolors.user.profile_background_color}`
}
}
}
}
}
Accoding to Binding inline styles documentation there are to ways to pass inline styles - as an object or as an array.
In your example, background-color: #{procolors.user.profile_background_color} is neither object or an array.
For sake of readability and maintainability (and good practice in general), I'd suggest to create a computed property that will return an object with inline styles. This way it will more clear where is the issue with concatenation:
Template will look as follows:
<div
class="color-quadrat"
:data-id="procolors.id"
:style="itemStyles">
<p>{{ procolors.user.profile_background_color }}</p>
</div>
And computed property should be added to the same component:
props: ['procolors'],
template: `...`,
computed: {
itemStyles () {
return {
backgroundColor: `#${this.procolors.user.profile_background_color}`
}
}
}
If you still prefer to keep it inline, then style binding should be changed to following:
v-bind:style="{ backgroundColor: `#${procolors.user.profile_background_color}` }"
For those who want to use style binding with vue3. This is the solution:
<script setup lang="ts">
import {ref} from 'vue'
const color = ref("red")
</script>
<template>
<div class="parti-color"
:style="{backgroundColor: color, width: '20px', height: '30px'}"
/>
</template>

Use prop value as a variable in VueJS

I have a problem about using prop value as a variable in VueJS. I have a component which I tranmit prop:
This is parent component:
<template>
<div class="a">
<UploadAvatarModal
apiurl="upload_avatar"
id="UploadAvatarModal"
/>
</div>
</template>
This is script of UploadAvatarModal component:
<template>
<div class="a">
...
</div>
</template>
<script>
export default {
props: {
id: String,
apiurl: String
},
methods: {
def: function () {
this.$refs.id.hide()
}
}
}
</script>
In this line: this.$refs.id.hide() How can I call methods according to prop id. Example: this.$refs.UploadAvatarModal.hide() or this.$refs.UploadAvatarModal2.hide() changed by props value??
You can access props doing :
this.propName
To access id prop you need to do :
this.id
So the line you wrote this.$refs.id.hide() should be written :
this.$refs[this.id].hide()
But it will probably do nothing as .hide() is a jquery function.
In plain javascript you would need to do :
this.$refs[this.id].style.display = 'none'
That said, it's might not be a good idea to do so.
Using vue, the best way to show/hide a component is probably to use v-if or v-show

Filter with VueJS

I am very new with VueJS so my question is very simple. I cannot use vue filter. Please help me fix the problem.
My html file is shown as followed. When I try this code the item in v-for can't be shown and also the it has error Failed to resolve filter: uppercase.
Can any one tell me why?
<div id="pan" class="pan">
<div v-for="item in list|orderBy 'level'" >{{item.id}}</div>
<span>{{message | uppercase}}</span>
</div>
<script type="text/javascript" src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
var pan = new Vue({
el: '#pan',
data: {
list: [
{ name: '東京', id:"TOKYO",level:"2"},
{ name: '全国',id:"JAPAN",level:"1" },
{ name: '関東',id:"KANTO",level:"0" },
],
message:"hello"
}
});
</script>
If you are using vuejs2, with vuejs2 uppercase filter has been removed. You will have to use toUpperCase() for this, like following:
<span>{{message.toUpperCase()}}</span>
see demo.
Similarly orderBy filter also has been removed, vuejs2 suggests to use lodash’s orderBy (or possibly sortBy) in a computed property:
HTML
<p v-for="item in orderedList">{{ item.name }}</p>
vue
computed: {
orderedList: function () {
return _.orderBy(this.list, 'level')
}
}
Here is demo with orderBy.
You can use a computed property.
Markup:
<div id="pan" class="pan">
<div v-for="item in orderedList" >{{ item.id }}</div>
<span class="pan__title">{{ message }}</span>
</div>
Definition inside of Vue:
data(){
sortKey : 'level'
},
computed : {
orderedList(){ return this.list.sort(this.sorter) }
},
methods : {
sorter(a,b){ return a[this.sortKey] > b[this.sortKey] }
}
And then you can change order of the elements in orderedList by modifying sortKey (using v-model="sortKey" to any kind of input, like <select></select> or any other way).
Here is an example based on your code
And what about uppercase, I prefer to control a view with css, and text-transform property can solve this: .pan__title { text-transform: uppercase; }. But you can define a computed property for this one too or keep it inline with {{ message.toUpperCase() }}.

Dynamically created input. Can't use $refs so how to get e.g. focus?

I have dynamically created inputs (list with elements, every element has own ID) for an edition.
All of them have v-if to be there only when the edit of the particular element is has been triggered.
Because of that, I can't use $refs as Vue does not see that in refs.
How can I solve it?
I really don't want to add jQuery for that or having to use vanilla JS every time when I need something like that which is quite often.
Usually, we have e.g. a span before editing, and would use v-show rather than v-if on it since we still need it after editing, and each input is coupled with its according span, so something like event.target.nextSibling.focus() will do the job.
I prefer event.target... to $refs as declaring $refs adds complexity to the component's structure while the other is just something only relevant within the click event.
If you really wanted to avoid the use of vanilla js (apart from for focusing) then I'd suggest you have to move your list elements into a component:
Vue.component('list-items', {
template:
`<div>
<button #click="edit">edit</button>
<input v-if="editing" ref="input" type="text" :value="value" #input="$emit('input', $event.target.value)">
</div>`,
props: ['value'],
data () {
return {
editing: false,
}
},
methods: {
edit () {
this.editing = !this.editing
if (this.editing) {
this.$nextTick(() => {
this.$refs.input.focus()
})
}
},
},
})
new Vue({
el: '#app',
data: {
list: [{
title: 'foo',
}, {
title: 'bar',
}]
},
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<list-items v-model="item.title" v-for="item in list"></list-items>
<pre>{{ list }}</pre>
</div>

Categories