Meteor: how to set array value, within spacebars, dynamically - javascript

I don't really know how to explain this... I have a collection that has an array in it and when I go through it I've been setting colors.[0].imageLink and not changing the [0], but now I'd like that to be dynamic depending on the value of a function (in this case the function is viewIndex).
Works, but isn't dynamic:
<h3 class='display-price'>$ {{colors.[0].price}}</h3>
What I'd think would work but doesn't:
<h3 class='display-price'>$ {{colors.[(viewIndex)].price}}</h3>
In the corresponding js file (does return 0):
'viewIndex': function() {
console.log(Template.instance().variation.get());
return Template.instance().variation.get();
}

One way to do what you are trying to do is to define a colorPrice helper that takes colors and viewIndex as parameters as follows:
Template.hello.helpers({
colors() {
return [
{ price: 1},
{ price: 2},
{ price: 3}
];
},
viewIndex(){
return 1;
},
colorPrice(colors, viewIndex){
return colors[viewIndex].price;
}
});
Then, in your template you can use it as follows:
<template name="hello">
${{ colorPrice colors viewIndex }}
</template>

So thank you to Kalman for getting me thinking of a dynamic way to do this using more of the javascript! My solution was essentially to use helpers and the "this" keyword by calling helper functions within {{#with item}}, which passed "this" all of the values of the current item it was on, so essentially this became possible:
variationImage() {
return this.colors[Template.instance().variation.get()].imageLink[0];
},
variationLink() {
return this.colors[Template.instance().variation.get()].referralLink;
},
variationPrice() {
return this.colors[Template.instance().variation.get()].price;
}
And at that point getting those values was as simple as using {{variationPrice}} where I needed the price, etc.
{{#with item}}
<h3 class='display-price'>$ {{variationPrice}}</h3>
{{/with}}
And just to add more about how the variations worked each item has a pretty much random number of variations (since we scrape them) so we have to do something along these lines to render how many there are and set which variation you're looking at:
(where colors is an array containing the different color variations of an item)
{{#each colors}}
<option value='{{#index}}'>Variation {{variationIndex #index}}</option>
{{/each}}
(helper function)
variationIndex(index) {
return index+1;
}
(event function to set variation value)
'change .color-variation-select': function(event, template) {
template.variation.set($(event.target).val());
},

Related

Vue filter function to return always 5 more elements

actually no biggie but how would a computed property filter function look like that always returns the current array + 5 more elements?
more in detail:
Template:
<span class="box-content" v-for="item in activeItems" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Script
data: function() {
return {
items: [],
limit: 1,
};
},
computed: {
activeItems: function() {
return this.items.filter( function(s) {
if(s.length > this.limit) {
return s;
}
});
// return this.limit ? this.items : this.items;
}
},
on page load , an axios post request gets an object of items, whose response is pushed into the items array which is empty upon component declaration.
so axios -> get object with items -> push into empty array.
now i want to display ,like, 5 items and make a show more button.
The problem now is, my activeItems function is invalid, it does not know "this.limit" and i doubt anyway that it returns the correct result as i just made it return itself and not a set of objects / arrays.
What I would do next is trying around with splice and slice, array copies and pushing elements into it until a certain condition is met but.. is there a better way ?
Thanks in advance
The filter function should be used to filter based on the internal values of an array. Say you have an array of objects with persons, and each Person as an age, then you could use the Array.prototype.filter function to filter based on that age of each entry.
The filter function therefore goes through every entry in your array and determines whether an item should be included or excluded.
If you, on the other hand, want to limit the amount of entries based on a maximum number of entries, I would suggest you use Array.prototype.slice, as you mentioned already.
Your computed function could be rewritten to:
activeItems: function() {
return this.items.slice(0, this.limit)
}
First, in your code, this.limit is undefined because this is referencing the anonymous function. If you want to access the component, you will better use arrow functions syntax.
Also, s references an element of your array, so s.length will be undefined too I guess...
Now, filter does not seem to be the best choice for your need. I'll go with slice instead. Somthing like:
computed: {
activeItems() {
return this.items.splice(0, this.limit)
}
}
Where limit is increased by 5 when you click the show more button.
Of course you could do it. You just missed some code on it. Here how you fix it
activeItems: function() {
let limit = this.limit
return this.items.filter( function(item, s) {
return s <= limit
});
}
If you don't mind using filter, here are some way to do it.
First : put condition in your for loop, this one
<span class="box-content" v-for="(item, index) in items" :key="item.id" v-if="index <= limit">
<img class="item" :src="item.filename" />
</span>
Second is to slice your array on you desired length, this one
<span class="box-content" v-for="(item, index) in items.slice(0, limit)" :key="item.id">
<img class="item" :src="item.filename" />
</span>

Vue.js - Remove specific component dynamically

I am trying to dynamically create/remove a Vue component. I have figured out how to dynamically add the component, but I am having some troubles with allowing the users to remove the specific component.
Consider below two Vue files:
TableControls.vue
<a v-on:click="addColumn">Add Column</a>
<script>
export default {
methods: {
addColumn: function () {
Event.$emit('column-was-added')
}
}
};
</script>
DocumentViewer.vue:
<div v-for="count in columns">
<VueDragResize :id="count">
<a #click="removeColumn(count)">Remove Column</a>
</VueDragResize>
</div>
<script>
import VueDragResize from 'vue-drag-resize';
export default {
components: {
VueDragResize
},
data() {
return {
columns: [1],
}
},
created() {
Event.$on("column-was-added", () => this.addColumn())
},
methods: {
addColumn: function () {
this.columns.push(this.columns.length + 1)
},
removeColumn: function (id) {
this.columns.splice(id, 1)
}
}
};
</script>
As you can see, whenever a user clicks on <a v-on:click="addColumn">Add Column</a>, it will submit an event, and the DocumentViewer.vue file will pick up it, firing the addColumn method. This will ultimately create a new <VueDragResize></VueDragResize> component.
This works great.
The problem is when I want to remove the component again. My removeColumn method simply removes an id from the columns array:
removeColumn: function (id) {
this.columns.splice(id, 1)
}
This results in that a column is in fact removed. However, consider below example. When user clicks on the remove icon for the first column, it will remove the 2nd column instead. (And when there is only one column present, it cannot be removed).
I believe this is due to the fact that I splice() the array, but I cannot see how else I can remove the component dynamically?
I see, Array on Vue does not re render when you modify them.
You need to use the
Vue.set(items, indexOfItem, newValue)
if you want to modify
and use
Vue.delete(target, indexOfObjectToDelete);
If you want to delete an item from an array
You may read the additional info here
https://v2.vuejs.org/v2/api/#Vue-delete
If you want to delete an item from array. Using this will cause the component to rerender.
In this case it will be intuitive to do this
removeColumn: function (id) {
Vue.delete(this.columns, id)
}
Note that id should be the index. Vue.delete ensures the re-render of the component.
EDIT, you must use the index, instead of the count here.
<div v-for="(count, index) in columns">
<VueDragResize :id="index">
<a #click="removeColumn(index)">Remove Column</a>
</VueDragResize>
</div>
I would recommend reshaping your data, each element should be an object with an id and whatever other properties you want. Not simply an id then you would need something like this:
removeColumn(id) {
const elToRemove = this.columns.findIndex(el => el.id === id)
let newArr = [elToRemove, ...this.columns]
this.columns = newArr
}
Also make another computed property for columns like this to make sure they change dynamically (when you add/remove):
computed: {
dynColumns(){ return this.columns}
}
I have same problem, and I found the solution of this problem. It is need to set #key with v-for. This is Built-in Special Attributes.
By default, if you do not set "#key", array index is set to#key. So if array length is 3, #key is 0,1,2. Vue identify eash v-for elements by key. If you remove second value of array, then array index is 0 and 1, because array length is 2. Then Vue understand that #key==2 element removed, So Vue remove 3rd component. So if you remove second value of array, if no #key, third component will be removed.
To avoid this, need to set #key to identify component like this:
let arr = [
{ id: 'a', ...},
{ id: 'b', ...},
{ id: 'c', ...}
];
<div v-for="obj in arr" :key="obj.id">
<someYourComponent>
...
</someYourComponent>
</div>

Iterate over object properties

In my aurelia app, I need to display a list of elements. For performance reasons, these elements are stored as a javascript object instead of an array. That is what the object looks like :
var x = {
0 : {...},
3 : {...},
5 : {...},
}
Here is the part of the template where I display these elements :
<template>
<ul>
<li repeat.for="property of object | ObjectToArray">
${ property.text }
</li>
</ul>
</template>
As you can see, I'm currently using a value converter to be able to iterate over my object properties. The value converter simply converts the object to an array :
export class ObjectToArrayValueConverter {
toView(data : {[key : string] : any}) : any[] {
var array = [];
for (var key in data) {
array.push(data[key]);
}
return array;
}
}
This solution works very well as long as properties do not get removed or added to the object after the list has been rendered for the first time. The reason is that the value converter only gets called once.
In my case, however, I need my list to stay up to date whatever happens.
I know I could create a function that could be manually called every time the object is modified, but that would add some unwanted complexity in business logic.
Is there an aurelia functionality that coud help me achieve what I want ? I could not find any help in the docs. Thank you !
You can get the keys using Object.prototype.keys and call Array.prototype.map on it to get an array every time you want to list it out.
var obj={...}; //Object with many keys
var genArray = Object.keys(obj).map(function(key){return obj[key];})
//Using as a function
function getKeysAsArray(obj){
if(!obj){
return [];
}
return Object.keys(obj).map(function(key){return obj[key]});
}
There's an easier strategy, as shown in the docs:
export class KeysValueConverter {
toView(obj) {
return Reflect.ownKeys(obj);
}
}
Usage:
<p repeat.for="greeting of friends | keys">${greeting}, ${friends[greeting].name}!</p>

Meteor Collection eliminate duplicates

I have a collection of movies that sit under categories, but I have a view that is suppose to show them all.
When I'm showing all, I have a few duplicates because these movies are in multiple categories.
I am trying to use this code in the helper (client side).
I've found this code that gets me all the titles without duplicates:
var distinctMovies = _.uniq(Movies.find({}, {
sort: {"title": 1},
}).fetch().map(function(x) {
return x.title;
}), true);
This seems to work in removing the duplications because I get an array of 241 titles instead of 251.
However, I want an array with whole objects, instead of an array with only the titles, but when I switch x.title per x I get the same 251 items.
I am trying to change this code to get the whole movie object and also trying not to run additional loops.
How to achieve this?
According to _.uniq documentation:
If you want to compute unique items based on a transformation, pass an iteratee function.
Do this:
_.uniq(Movies.find({}, { sort: { title: 1 } }).fetch(), function (movie) {
return movie.title;
});
It works for me:
Template.theater_list.theater=function(){
var cities = _.uniq(Theater.find({}, {sort: {city:1}, fields: {city:true}}).fetch().map(function(x) {
return x.city
}),true);
return cities;

Handlerbars.js using an helper function in a #if statement

With handlebars.js I want to display two blocks of html depending of a resulting json.
Let's say I want to thanks my user for ordering items at my shop.
I write my handlerbars.js template like this :
<p>{{name}}</p>
{{#if costIsZero}}
Can't find any order
{{else}}
You bought {{cost}} items in our shop, thanks.
{{/if}}
I'm coding a simple helper for costIsZero like this :
Handlebars.registerHelper('costIsZero', function(){
return this.cost == 0
});
When I mix it with the following json data :
var data = {
"name":"foo",
"cost": 9
};
Whatever the value of "cost" is the {{#if costIsZero}} seems always to be true.
If I comment out the helper itself, thus having nothing for costIsZero it returns always false.
All the code above is available as a JSFiddle there http://jsfiddle.net/gsSyt/
What I'm doing wrong ?
Maybe I'm hijacking the way handlebars.js work, but in that case, How should I implement my feature with handlebars.js ?
Helpers are not invoked when evaluating an expression such as costIsZero.
You could create a custom helper that works as an alternative to if:
Handlebars.registerHelper('ifCostIsZero', function(block) {
if (this.cost == 0) {
return block(this);
} else {
return block.inverse(this);
}
});
Which you would use like this:
{{#ifCostIsZero}}
Can't find any order
{{else}}
You bought {{cost}} items in our shop, thanks.
{{/ifCostIsZero}}
Alternatively, you can use the stock if (or unless) since your test is against zero :
{{#if cost}}
You bought {{cost}} items in our shop, thanks.
{{else}}
Can't find any order
{{/if}}
You can play with both options at http://jsfiddle.net/gsSyt/41/
Try this:
Handlebars.registerHelper('if', function(conditional, block) {
if(this.cost == 0) {
return block(this);
} else {
return block.inverse(this);
}
})
http://jsfiddle.net/mZbtk/2/

Categories