I use the following code, in simplified form.
I have a template that shows a list of stores, coming from the _stores array. Depending on some other properties that are set in the app, the _sort and _filter function do their respective work well and show the list I want to see.
When I recalculate distances for each store (running _stores trough calculateDistance) I want to re-render the storelist by calling _search(), which calls the Polymer .render() function.
This call is not reliable: sometimes it renders, sometimes not. It happens on desktop and mobile, safari and android, so it seems like it is a Polymer issue. I cannot find what is causing this. Any idea?
<template id="storeList" is="dom-repeat" items="{{_stores}}" sort="_sort" filter="_filter">
<div class="search_result">
<div class="picture">
<div class="logo" hidden$="{{!item.logo.length}}">
<div class="centered">
<img src$="{{item.logo}}" alt="logo" />
</div>
</div>....
</template>
<script>
Polymer({
is: 'user-store-search-page',
properties: {
...
_stores: Array
...
},
_calculateDistance: function () {
this._stores.forEach(s) {
//do stuff per store
}
},
_filter: function (store) {
//do filtering stuff
},
_sort: function (a, b) {
//sort stuff
},
_search: function () {
this.$.storeList.render();
}
I would use compute property advantage for rerender your list on any action performed on _stores Array :)
<template id="storeList" is="dom-repeat" items="[[_displayStores]]" sort="_sort" filter="_filter">
<div class="search_result">
<div class="picture">
<div class="logo" hidden$="{{!item.logo.length}}">
<div class="centered">
<img src$="{{item.logo}}" alt="logo" />
</div>
</div>....
</template>
<script>
Polymer({
is: 'user-store-search-page',
properties: {
...
_stores: Array,
_displayStores: {
type: Array,
computed: '_computeStoresToDisplay(_stores.*)'
}
...
},
_computeStoresToDisplay: function(stores) {
return stores.value;
},
_calculateDistance: function () {
this._stores.forEach(s) {
//do stuff per store
}
},
_filter: function (store) {
//do filtering stuff
},
_sort: function (a, b) {
//sort stuff
},
_search: function () {
this.$.storeList.render();
}
</script>
Related
Is there a way to bind a given data prop in template while looping over array?
<div v-for="(page, pageIndex) in pages" :key="pageIndex">
<img
:src=""
#load="onImageLoaded.bind(this, someDataVar)"
/>
</div>
So if in the meantime (until the image gets loaded) someDataVar will gonna be changed, I still want to output in onImageLoaded the original value of someDataVar at the time the image was added to DOM by the for-loop.
PS: I've tried with IIFE but it didn't worked
define a 'directive'.
try this.
new Vue({
el: '#app',
data () {
return {
pages: [1,2,3],
someDataVar: 'someData'
}
},
mounted () {
setTimeout(() => {
this.someDataVar = 'otherData'
}, 1000)
},
directives: {
'init-val': {
inserted: (el, binding) => {
el.dataset.initVal = binding.value
}
}
},
methods: {
onImageLoaded (event) {
console.log(event.target.dataset)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(page, pageIndex) in pages" :key="pageIndex">
<div
v-init-val="someDataVar"
#click="onImageLoaded"
>click me!</div>
</div>
</div>
Here my code :
~/store/state.js
export default () => ({
selectLanguage: 'fr'
})
~/store/actions.js
export default {
switchToFr (context) {
context.commit('switchToFr')
},
switchToEn (context) {
context.commit('switchToEn')
}
}
~/store/mutations.js
export default {
switchToFr (state) {
state.selectLanguage = 'fr'
},
switchToEn (state) {
state.selectLanguage = 'en'
}
}
~/layouts/inside.js
<b-dropdown-item :value="'fr'" #click="$store.dispatch('switchToFr')" aria-role="listitem">
<div class="media">
<img width='30px' height='30px' src="~/assets/img/icons8-france-48.png"/>
<div class="media-content">
<h3>Français</h3>
</div>
</div>
</b-dropdown-item>
<b-dropdown-item :value="'en'" #click="$store.dispatch('switchToEn')" aria-role="listitem">
<div class="media">
<img width='30px' height='30px' src="~/assets/img/icons8-great-britain-48.png"/>
<img width='30px' height='30px' src="~/assets/img/icons8-usa-48.png"/>
<div class="media-content">
<h3>English</h3>
</div>
</div>
</b-dropdown-item>
data () {
return {
activeLanguage: this.$store.state.selectLanguage,
}
},
watch: {
activeLanguage: function() {
console.log(this.activeLanguage)
}
},
~/pages/projects.js
data () {
return {
activeLanguage: this.$store.state.selectLanguage,
}
},
watch: {
activeLanguage: function() {
console.log(this.activeLanguage)
}
},
The problem :
In layout.js, when i switch language, the data activeLanguage change and the watch do a console.log of the new value.
-> it's okay
In project.js, it does not work, i have to change the page and come back to it to have the new store value in my data.
-> it's bad
Anyone know how to do with project.js to have the same comportment that layout.js ?
Thank's !
I'm surprised that activeLanguage did change for you in layout.js. The data function only gets run once when the component gets created and strings are immutable, so I wouldn't have expected activeLanguage in layout.js to pick up when that the selectLanguage value in the store changed.
You should be getting state values from a computed function instead as recommended by the Vuex docs.
Something like this should do the trick:
computed: {
activeLanguage () {
return this.$store.state.selectLanguage
}
}
For a short version, look at mapState.
I have seen a few questions similar to this but all revolve around webpack, which I am not currently using.
I have a Vue template:
var question = Vue.component('question', {
props: {
scenario: { type: Object }
},
beforeMount: function () {
this.retrieveScenario();
},
methods: {
postChoice: function () {
Post("Study", "PostScenarioChoice")
},
retrieveScenario: function () {
Get("ScenariosVue", "GetScenario", 1, this,
(c, data) => { this.scenario = data; },
(c, errors) => { console.log(errors); }
);
}
},
template:
`<div class="visible">
<div class="row justify-content-center">
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenario.scenarioLeftImg" />
</a>
</div>
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenario.scenarioLeftImg" />
</a>
</div>
</div>
</div >`
});
The Ajax retrieval returns an object with the following:
returned
Object {
scenarioId: 1,
description: "Dog vs Bolard",
scenarioLeftImg: "images\\Scenarios\\bolard_dog_Left.png",
scenarioRightImg: "images\\Scenarios\\bolard_dog_Right.png",
participantScenarios: [],
scenarioTypesScenarios: []
}
However, the Html, doesn't add the src tag to the tag and I'm really not sure why because the data's clearly available.
Much Help would be greatly appreciated.
This is happening because Vue's template system doesn't "watch" properties (or nested properties) of an object for changes. If you need to do this, then you can either use the computed property with a watch on the computed property, or you can just create two props instead of the single prop. Here is what I would do to change your code:
var question = Vue.component('question', {
props: {
// Replace this prop with the two below.
// scenario: { type: Object }
scenarioLeftImg: { type: String },
scenarioRightImg: { type: String }
},
beforeMount: function () {
this.retrieveScenario();
},
methods: {
postChoice: function () {
Post("Study", "PostScenarioChoice")
},
retrieveScenario: function () {
Get("ScenariosVue", "GetScenario", 1, this,
(c, data) => { this.scenario = data; },
(c, errors) => { console.log(errors); }
);
}
},
template:
`<div class="visible">
<div class="row justify-content-center">
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenarioLeftImg" />
</a>
</div>
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenarioLeftImg" />
</a>
</div>
</div>
</div >`
});
Your main issue is that due to the limitations of Javascript, Vue cannot detect property additions in Objects.
Change to
retrieveScenario: function () {
Get("ScenariosVue", "GetScenario", 1, this,
(c, data) => { Vue.set(this.data, 'scenarios', data); },
(c, errors) => { console.log(errors); }
);
}
Please be aware that it is considered really bad practice to have components modify their props. Rather mirror those props into your vm's data() function in the created() hook of your component, then modify that. If you need the "modified" props outside of your component, emit an event telling those components props has changed.
Also replace the backslashes in your path by slashes:
scenarioLeftImg: "images\\Scenarios\\bolard_dog_Left.png",
scenarioRightImg: "images\\Scenarios\\bolard_dog_Right.png",
has to be
scenarioLeftImg: "images/Scenarios/bolard_dog_Left.png",
scenarioRightImg: "images/Scenarios/bolard_dog_Right.png",
I'm working on a basic to-do application. Each to-do/task item gets listed as an input item in a Vue <list-item> component, and the <list-item>s are displayed with a v-for pointing to a tasks array.
I'm trying to allow the user to edit each task input, and upon changing the value, have this update the array item (rather than just the input itself). My #change event on the input is firing, but I'm at a loss as to what to do after this point.
https://jsfiddle.net/xbxm7hph/
HTML:
<div class="app">
<div class="add-control-area columns is-mobile is-multiline">
<responsive-container>
<div class="field is-grouped">
<div class="control is-expanded">
<input class="input add-control-text" type="text" placeholder="New Task" v-model="newTask" v-on:keyup.enter="addTask">
</div>
<div class="control">
<a class="button is-white add-control-button" #click="addTask" :disabled="!isThereText">Add Task</a>
</div>
</div>
</responsive-container>
<responsive-container>
<list-item v-for="task, index in tasks" :item="task" :index="index" #task-completed="completeTask(index)" #task-deleted="deleteTask(index)" ></list-item>
</responsive-container>
</div>
</div>
JS:
Vue.component('list-item', {
props: ['item', 'index'],
template: `<div class="task-wrapper">
<input class="task" :value="item" #change="updateTask()">
<div class="task-control delete-task" #click="deleteTask()"></div>
<div class="task-control complete-task" #click="completeTask()"></div>
</div>
`,
methods: {
completeTask: function() {
this.$emit('task-completed', this.index);
},
deleteTask: function() {
this.$emit('task-deleted', this.index);
},
updateTask: function() {
console.log('changed');
}
}
});
Vue.component('responsive-container', {
template: `
<div class="column is-4-desktop is-offset-4-desktop is-10-tablet is-offset-1-tablet is-10-mobile is-offset-1-mobile">
<div class="columns is-mobile">
<div class="column is-12">
<slot></slot>
</div>
</div>
</div>
`
});
var app = new Vue({
el: '.app',
data: {
tasks: [],
completedTasks: [],
newTask: ''
},
methods: {
addTask: function() {
if(this.isThereText) {
this.tasks.push(this.newTask);
this.newTask = '';
this.updateStorage();
}
},
completeTask: function(index) {
this.completedTasks.push(this.tasks[index]);
this.tasks.splice(index, 1);
this.updateStorage();
},
deleteTask: function(index) {
this.tasks.splice(index, 1);
this.updateStorage();
},
updateStorage: function() {
localStorage.setItem("tasks", JSON.stringify(this.tasks));
}
},
computed: {
isThereText: function() {
return this.newTask.trim().length;
}
},
// If there's already tasks stored in localStorage,
// populate the tasks array
mounted: function() {
if (localStorage.getItem("tasks")) {
this.tasks = JSON.parse(localStorage.getItem("tasks"));
}
}
});
Use a v-model directive on your <list-item> component, instead of passing in an item property. You will also need to pass in a reference from the array (tasks[index]), because task in this scope is a copy that is not bound to the element of the array:
<list-item v-for="task, index in tasks" v-model="tasks[index]"></list-item>
In your component definition for the list item, you'll need to now take in a value prop (this is what gets passed when using v-model) and set a data property item to that value. Then, emit an input event on the change to pass the item value (this is what the component is listening for when using v-model):
Vue.component('list-item', {
props: ['value'],
template: `<div class="task-wrapper">
<input class="task" v-model="item" #change="updateTask"></div>
</div>
`,
data() {
return {
item: this.value,
}
},
methods: {
updateTask: function() {
this.$emit('input', this.item);
}
}
});
Here's a fiddle with those changes.
As Bert Evans mentioned, even though this works, Vue requires that components using the v-for directive also use a key attribute (you will get a warning from Vue otherwise):
<list-item
v-for="task, index in tasks"
:key="index"
v-model="tasks[index]"
></list-item>
Also, realize that the index variable in a v-for scope can change, meaning that the item at index 1 might change to index 4 and this can pose some problems as the application gets more complex. A better way would be to store items as an object with an id property. This way you can have an immutable id associated with the item.
You can pass the index and new value to your change event handler:
<input class="task" :value="item" #change="updateTask(index, $event)">
Then access them accordingly:
updateTask: function(index, event) {
console.log(index);
console.log(event.target.value);
}
I am using Sortable.js and Vue.js. The goal is to sort items and keep data updated.
It worked well with Vue 1.x, but after update to 2.0 sorting became incorrect. Array still properly updates, but items in DOM are in the wrong places.
new Vue({
el: '#app',
template: '#sort',
data: function() {
return {
items: [
"http://placehold.it/200X300?text=image1",
"http://placehold.it/200X300?text=image2",
"http://placehold.it/200X300?text=image3",
"http://placehold.it/200X300?text=image4"
],
}
},
mounted: function() {
this.$nextTick(function () {
Sortable.create(document.getElementById('sortable'), {
animation: 200,
onUpdate: this.reorder.bind(this),
});
})
},
methods: {
reorder: function(event) {
var oldIndex = event.oldIndex,
newIndex = event.newIndex;
this.items.splice(newIndex, 0, this.items.splice(oldIndex, 1)[0]);
}
}
});
jsFiddle
https://jsfiddle.net/4bvtofdd/4/
Can someone help me?
I had a similar problem today.
Add :key value to make sure Vue rerenders elements in correct order after Sortable changes the items order
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
https://v2.vuejs.org/v2/guide/list.html#key
As it happens, Sortable keeps track of the order in sortable.toArray(), so it's pretty easy to make a computed that will give you the items in sorted order, while the original item list is unchanged.
new Vue({
el: '#app',
template: '#sort',
data: {
items: [
"http://placehold.it/200X300?text=image1",
"http://placehold.it/200X300?text=image2",
"http://placehold.it/200X300?text=image3",
"http://placehold.it/200X300?text=image4"
],
order: null
},
computed: {
sortedItems: function() {
if (!this.order) {
return this.items;
}
return this.order.map((i) => this.items[i]);
}
},
mounted: function() {
this.$nextTick(() => {
const sortable = Sortable.create(document.getElementById('sortable'), {
animation: 200,
onUpdate: () => { this.order = sortable.toArray(); }
});
})
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.4.2/Sortable.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//unpkg.com/vue#2.0.1/dist/vue.js"></script>
<div id='app'></div>
<template id="sort">
<div class="container">
<div class="row sort-wrap" id="sortable">
<div class="col-xs-6 col-md-3 thumbnail" v-for="(item, index) in items" :data-id="index">
<img v-bind:src="item" alt="" class="img-responsive">
</div>
</div>
<div v-for="item in sortedItems">
{{item}}
</div>
</div>
</template>
Make sure you aren't using a prop, or you won't be able to sort. If you are using a prop, assign the prop data to a data attribute and use the data attribute instead.