I am trying to render a dynamic list of items using a template of dom-repeat like this:
<template is="dom-repeat" items={{numbers}} as="anumber" >
<div>
{{anumber}}
<paper-button class="deleteThisNumber" index={{index}}></paper-button>
</div>
</template>
<paper-button id="addNumber"></paper-button>
Each item has a button which will delete this item.
There is also a button outside of the dom-repeat template that tries to add an entry to array numbers. The JS looks like this:
Polymer ({
is: "something",
properties: {
numbers: {
type: Array,
value: ["1"]
}
},
removeByIndex: function (array, index) {
return array.filter(function (elem, _index) {
return index != _index;
});
},
attached: function () {
var myself = this;
$(this).on('click', '.deleteThisNumber', {}, function (e) {
myself.numbers = myself.removeByIndex(myself.numbers, this.index)
});
this.$.addNumber.addEventListener("click", function (e) {
myself.numbers.push("123");
})
},
...
});
The result is: deleting works, but adding does not.
By saying "works", I mean the list reflects the change by adding/removing an entry in the DOM. I checked the property numbers it is correctly modified all the time. So why does Polymer not reflect changes of an array property to a template if the change is addition(array.push)? How should I fix this? (I am open to any suggestions other than manually adding divs.)
My Polymer version is 1.X
Change the code for array push to :
this.$.addNumber.addEventListener("click", function(e) {
myself.push("numbers", "123");
})
There has to be an observable change in order to render the updated property or subproperty. An observable change is a data change that Polymer can associate with a path.
If you manipulate an array using the native methods (like Array.prototype.push), you must notify Polymer after the fact. OR, use the Polymer methods for array mutations.
When modifying arrays, a set of array mutation methods are provided on
Polymer element prototypes which mimic Array.prototype methods, with
the exception that they take a path string as the first argument. The
path argument identifies an array on the element to mutate, with the
following arguments matching those of the native Array methods.
These methods perform the mutation action on the array, and then
notify other elements that may be bound to the same array of the
changes. You must use these methods when mutating an array to ensure
that any elements watching the array (via observers, computed
properties, or data bindings) are kept in sync.
Every Polymer element has the following array mutation methods
available:
push(path, item1, [..., itemN])
pop(path)
unshift(path, item1, [...,
itemN])
shift(path)
splice(path, index, removeCount, [item1, ..., itemN])
Learn More
I found out the solution is to force a notifyPath with a little more:
myself.numbers.push("123"); //before only has this
myself.notifyPath('numbers', myself.numbers.slice()); //added
Referred to https://github.com/Polymer/polymer/issues/2068#issuecomment-120767748
Answer from #miyconst
More than one way you can fix your code.
1) The way you've already been using when you delete an item.
this.$.addNumber.addEventListener("click", function (e) {
myself.numbers.push("123");
myself.numbers = myself.numbers.slice();
})
2) Answer from yourself (with slight changes)
this.$.addNumber.addEventListener("click", function (e) {
myself.numbers.push("123");
myself.notifyPath("numbers");
})
3) Answer from #Ofisora
this.$.addNumber.addEventListener("click", function(e) {
myself.push("numbers", "123");
})
Here's why the fixes work, https://www.polymer-project.org/1.0/docs/devguide/data-system#observable-changes
Related
So I am new in Vue and JS in general, and I am trying to do a user and password check with framework vue.
In the .js I have a list object inside in data, what contain parameters like user and password.
Then I use this list in a function on methods, to check if the input is equal to one of the user in the list with a if.
the problem is, all the items on methods are undefined using the respective console log (but those items exist with the proper v-for in the html).
this is a example of the code.
const app=new Vue({
el:"#app",
data:{
list:[
{user:'cpalma',password:'123456'},
{user:'cjara',password:'654321'}
],
tryUser:'',
tryPass:'',
},
methods:{
checkPass(){
console.log(this.list.user);
for(item in this.list){
if(item.user == this.tryUser){
// rest of code
}
}
}
Vue version is 2.5
"checkPass" is execute with a button.
tryUser and tryPass are get from the html with his respective v-model.
So if anyone can explain what is my error and how to fix it I will be in eternally grateful.
Observations :
As list is an array of objects. You can't access the objects properties directly via dot(.) notation. You have to access those via index.
this.list.forEach(obj => {
console.log(obj.user)
});
To iterate arrays, Use for...of loop instead of for...in which is basically more useful for objects not arrays.
const list = [
{user:'cpalma',password:'123456'},
{user:'cjara',password:'654321'}
];
for (const userDetail of list) { // ✅
console.log(userDetail.user);
}
for (const userDetail in list) { // ❌
console.log(userDetail);
}
for with in returns and index of the item due to this you are getting undefined
try the correct snippet as below:
let list =[
{user:'cpalma',password:'123456'},
{user:'cjara',password:'654321'}
]
for(item in list){console.log(list[item].user)}
To simplify it you can also use forEach:
let list =[
{user:'cpalma',password:'123456'},
{user:'cjara',password:'654321'}
]
list.forEach(item=>{
console.log(item.user)
//Rest of the logic
})
I use ng-prime <p-autocomplete> for display values via search in the back-end
here is my html
<p-autoComplete [(ngModel)]="agent" [suggestions]="filteredAgents" name="agents" (completeMethod)="filterAgents($event)" [size]="10"
placeholder="Agents" [minLength]="3"></p-autoComplete>
At the component.ts I initialize array like this at start of the component
filteredAgents: string[] = [];
and I have a method to send query to back end and push it to array
filterAgents(event) {
let query = event.query;
this._agentsService.getAgentSearch(query).subscribe(result => {
result.items.forEach((value) => {
this.filteredAgents.push(value.name);
console.log(this.filteredAgents);
});
});
}
I see filtered value in console, but I don't see it in suggestions.
Where can be my problem?
AutoComplete either uses setter based checking or ngDoCheck to realize if the suggestions has changed to update the UI. This is configured using the immutable property, when enabled (default) setter based detection is utilized so your changes such as adding or removing a record should always create a new array reference instead of manipulating an existing array as Angular does not trigger setters if the reference does not change. ( Angular documentation )
Array.prototype.push doesnt create a new reference it rather mutates the original array. So you need to make a new one.
filterAgents(event) {
let query = event.query;
this._agentsService.getAgentSearch(query).subscribe(result => {
this.filteredAgents = [...result.items.map(e => e.name)]
});
}
I maped the result to extract the names.
If filtered agents is an object array try adding field="name" to the directive attributes.
Here name is a field in the object. The directive uses this field to display in suggestions
Making an SPA using Polymer, and I need my custom components to all use a common custom component which represents my backend API and is responsible of GET-ting/POST-ing data from/to the API. It also serves as a "cache" and holds the data to display. This way, all the components that have access to this single element will share the same data.
So what I want to do is this... :
<my-api
users="{{users}}"
products="{{products}}">
</my-api>
...but programmatically, as <my-api> is not declared in all of my components but once in the top one and then passed down through the hierachy by JavaScript:
Polymer({
is: 'my-component',
properties: {
api: {
observer: '_onApiChanged',
type: HTMLElement
},
products: {
type: Array
},
users: {
type: Array
}
},
_onApiChanged: function(newVal, oldVal) {
if (oldVal)
oldVal.removeEventListener('users-changed', this._onDataChanged);
// Listen for data changes
newVal.addEventListener('users-changed', this._onDataChanged);
// Forward API object to children
this.$.child1.api = newVal;
this.$.child2.api = newVal;
...
},
_onDataChanged: function() {
this.users = this.api.users; // DOESN'T WORK as 'this' === <my-api>
this.products = this.api.products; // Plus I'd have to repeat for every array
}
});
Does Polymer offers a built-in way to do this ? Can I create a double curly braces binding programmatically ?
I would likely architect this slightly differently: passing down the products/users arrays declaratively taking advantage of Polymer's binding system. Or you could write your my-api element in such a way that they all share state and the first declared one is the primary while future declared ones are replicas. This would let you declare them wherever you need them and bind to the values via Polymer's normal ways.
But to answer your question, there's currently no way to easily programmatically setup the same kind of binding without using private Polymer APIs.
To avoid repeating as much and for the binding issue you were having you could use Polymer's built-in listen and unlisten methods:
Polymer({
is: 'my-component',
properties: {
api: {
observer: '_onApiChanged',
type: HTMLElement
},
products: {
type: Array
},
users: {
type: Array
}
},
_onApiChanged: function(newVal, oldVal) {
var apiProperties = ['users', 'products'];
if (oldVal) {
apiProperties.forEach(function(prop) {
this.unlisten(oldVal, prop + '-changed', '_onDataChanged');
});
}
// Listen for data changes
apiProperties.forEach(function(prop) {
this.listen(newVal, prop + '-changed', '_onDataChanged');
});
// Forward API object to children
this.$.child1.api = newVal;
this.$.child2.api = newVal;
...
},
_onDataChanged: function() {
this.users = this.api.users; // `this` should be the element now
this.products = this.api.products;
}
});
Given how this is a common pattern you're doing, you could probably get a lot of benefit out of extracting some of these things into a Behavior that abstracts away the binding/unbinding and API element forwarding.
Another optimization you may could make work would be to to look at the event passed to _onDataChanged to see if you can infer which value changed and update your corresponding property. This could prevent you needing to add a line for every property.
I ended up using an other solution. Instead of manually passing the top <my-api> element down the hierarchy any element that needs access to this shared data declares its own <my-api>.
Then in the <my-api> element's declaration I made that all instances use the same arrays references. So whenever I update one they all get updated, and I don't have to pass anything down the HTML hierarchy, which makes things a LOT simpler.
I've been reading through the docs and API and I'm having a hard time finding an explanation for the following:
export default Ember.Controller.extend({
collectionTotal: function() {
var games = this.get('model');
return games.length
}.property('#each')
});
What is actually happening with the .property('#each')? I know I am getting the computed property back, I don't understand what #each is.
What is #each?
#each observes individual properties on each item in an array.
For example, if I observe users.#each.name, I will receive an event if:
the users property is replaced e.g. this.set('users', ...)
an item is added or removed from users
the name property changes on any of the items
You can observe multiple properties using this syntax: users.#each.{name,email}
You cannot nest them. This will not work: users.#each.friends.#each.mood
Read more in the official documentation:
Computed Properties and Aggregate Data with #each
Ember.ArrayProxy Class
To answer your question
#each by itself doesn't make sense. You can observe the [] property if you only need to watch for items being added or removed.
Typically you should observe the same properties that are used in the body of the function. In your example that would be model and length:
collectionTotal: function() {
var games = this.get('model');
return games.get('length');
}.property('model.length')
Or equivalently:
collectionTotal: function() {
return this.get('model.length');
}.property('model.length')
Or equivalently:
collectionTotal: Ember.computed.reads('model.length')
I am using the click event on a button to set the value of an item that was generated using a foreach.
<table>
<tbody data-bind="foreach: Employees">
<a data-bind="click:$parent.delete()">
..
in my delete function I am setting the value but it doesn't update the screen
Delete :(emp) {
emp.active=false;
}
When I create I am setting all the individual properties as observable but seems like they are not when in the foreach loop.
Update
Employees is filtered.computed
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(AllEmployees(), function (empid) {
return empid.ID == filter();
});
When you get/set observables you need to call them like this:
var val = obj.prop(); //Getter
obj.prop(false); //Setter
One other issue you have is that you are using parenthesis in your click binding. Remember that Knockout bindings are just javascript, so it will actually execute that expression when it binds.
You need to get rid of those parenthesis or emp will be undefined initially.
UPDATE:
I've updated this jsFiddle to include three filtered lists similar to what you have shown above. You can see that using a filtered list via a computed has no bearing on how knockout handles the bindings, and the UI updates seamlessly.
http://jsfiddle.net/jwcarroll/ceRPK/
To set an observable, you have to call it (since observables are implemented as functions):
emp.active(false);
Your method simply overwrites the observable.
Knockout subscribes to the observable array, but not to each observable within that array. If you want to subscribe to individual properties you need to subscribe manually by using myObservable.subscribe()
Knockout subscribes to the observable array, but not to each observable within that array. If you want to subscribe to individual properties you need to subscribe manually using myObservable.subscribe()
Edit
If you are trying to have your computed keep track of what should be in your computed you can do so like this -
var allEmployees = ko.observableArray([my data goes here]);
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(allEmployees(), function (emp) {
return emp.active === true;
});
});
That works if active is not an observable property of each allEmployees(). If it is an observable just change that to -
var allEmployees = ko.observableArray([my data goes here]);
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(allEmployees(), function (emp) {
return emp.active();
});
});