Ember template not updating when array in model is modified - javascript

I have a simple array of ints that I'm displaying on a page.
This is my model:
var Favorite = Ember.Object.extend({
stuff: Ember.A([1,2,3,4])
});
This is my template:
{{#each model.stuff as |num|}}
<li>{{num}}</li>
{{/each}}
In my controller, modifications to this array generally display on the page (such as pushObject()), but modifying elements directly does not cause the page to update. I have a button on my page linked to this controller action:
actions: {
arrayAdd() {
this.get('model').stuff[0] = 100;
}
}
Clicking the button modifies the underlying array, but doesn't update the page. Is there a way to have ember automatically pick up on that change?
I'm doing this in Ember 1.13

Actually the equivalent of arr[idx] = foo for an ember array is arr.replace(idx, 1, [foo]).
The other thing you could do is to call this.get('model').notifyPropertyChange('stuff') after you manually edited the array.

In Ember, you need to use KVO(key value observer) compliant methods so that it will trigger computed property recalculation and observer and update the template. Always we need to use get for getting the properties and set for setting the values. if you didn't follow ember will throw assertion error sometime.
For array there are KVO methods which is equivalent to standard method.
Standard Method -> Observable Equivalent
pop -> popObject
push -> pushObject
reverse -> reverseObjects
shift -> shiftObject
unshift -> unshiftObject
In your case, you need to update it like the below, reference twiddle
arrayAdd() {
this.get('model.stuff').unshiftObject(100);
}
Note: You can declare array stuff: [1,2,3,4] it's good to initialize it in init method.
I still feel the array indexer should properly update the value,
though
Interestingly in glimmer component, you don't need to use getters/setters.

Related

Vue.js - Unable to trigger DOM update

so I have a complex nested structure that is like so in my Vue app's data:
{
x: {
y: [{
z: {
a: 1
}
}]
}
}
Although I used v-model in a v-for with the child property, setting .z.a = 2 doesn't seem to trigger this in the UI. I figured ok, must be that I'm mutating a property without alerting Vue, no biggie I just need to use Vue.set.
So I tried the following:
Vue.set(app.x.y[0].z, "a", 2)
Vue.set(app.x.y[0], "z", {a:2})
Vue.set(app.x.y, 0, app.x.y[0]) // app.x.y[0] is definitely {z:{a: 2}}
Vue.set(app.x, "y", app.y)
app.x.y = app.x.y.map(_ => _)
While these all usually do the trick for me, in this case it doesn't seem to work. If it changes things, I'm using v-model instead of a traditional prop, so it would be synced back to the app. I wonder if perhaps this disassociates the app.x and the actual data property for x.
I'm looking for a way to trigger a DOM update or properly set the value in Vue. I've also tried an app.$forceUpdate() to no avail :/
EDIT:
While I wasn't able to make Vue observe the change by itself, I found that I had a function that was populating z after it had been initialized to {}. I assume Vue set watchers on z the moment it was initialized, and so did not observe any further changed (i.e. adding a in the next line). Changing this to populate every possible property on initialization, combined with any of the .set's above and a $forceUpdate, I was able to trigger a DOM update. It's a temporary workaround though and I'd really like to be able to have Vue automatically observe this update.
You can try the vue computed property or watcher. This will automatically detect the changed in your data and update the v-model data you have.
In this example a change in the returned value of message will automatically be applied to the input.
<input v-model="message">
computed: {
message(){
return {x:{y:[1,2,3]}}
}
}

How to add extender to existing observable in Knockout

I've looked at this:
http://knockoutjs.com/documentation/extenders.html
The issue is that I'm using fromJs to create my view model, so my observerables already exist. I would think I could do the following to add an extender:
var data = result.Data;
if (!window.vmRealTimeActivity) {
window.vmRealTimeActivity = ko.mapping.fromJS(data, mappingKeys);
ko.applyBindings(vmRealTimeActivity, $('#second-btm')[0]);
} else {
ko.mapping.fromJS(data, vmRealTimeActivity);
}
vmRealTimeActivity.MyExistingObservable.extend({ numeric: null });
vmRealTimeActivity.MyExistingObservable(9999); // doesn't call numeric extender
My extender gets called the first time the extender is attached, but not after trying to change the value.
I read another SO post that stated that .extend() creates a new observerable so you have to do this, but this doesn't work either:
vmRealTimeActivity.MyExistingObservable = vmRealTimeActivity.MyExistingObservable.extend({ numeric: null });
In addition to not calling my formatter a second time, the value starts coming back NaN.
How do I attach an extender the proper way to an existing observable?
Since you are using the mapping plugin, you could specify a create callback. If you add the following to the existing mappingKeys, it would probably work (I don't know your exact mapping, so you might need to change bits here and there):
'MyExistingObservable': {
create: function(options) {
return new ko.observable(options.data).extend({ numeric: null });
}
}
This result in an extended observable upon mapping from yor data.
Here's a jsFiddle with a working example (vm1) and your current non-working example (vm2) for comparison
The above answer is correct, but for anyone interested, I found the simpler approach is to just create your view models client side and use fromJs to refresh them rather than both create and refresh them. You can then apply the answer here to support adding extend to both your parent and child view models: Map JSON data to Knockout observableArray with specific view model type
With either approach you will have to create additional mappings.

Creating and updating nested objects in Ember

I got nested JSON data from the server like this:
{
name: "Alice",
profile: {
something: "abc"
}
}
and I have the following model:
App.User = Ember.Object.extend({
name: null,
profile: Ember.Object.extend({
something: null
})
})
If I simply do App.User.create(attrs) or user.setProperties(attrs), the profile object gets overwritten by plain JS object, so currently I'm doing this:
var profileAttr = attrs.profile;
delete attrs.profile
user.setProperties(attrs); // or user = App.User.create(attrs);
user.get('profile').setProperties(profileAttrs);
It works, but I've got it in a few places and in the real code I've got more than one nested object, so I was wondering if it's ok to override User#create and User#setProperties methods to do it automatically. Maybe there's some better way?
Based on your comment, you want the automatic merging behaviour you get with models (the sort of thing you get with .extend()). In that case, you could try registering a custom transformer, something like:
App.ObjectTransform = DS.Transform.extend({
deserialize: function(json){
return Ember.Object.create(json);
}
});
App.User = DS.Model.extend({
profile: DS.attr('object')
});
See: https://github.com/emberjs/data/blob/master/TRANSITION.md#json-transforms
If you are doing your server requests without an adapter you can use the model class method load() with either an array of json objects or a single object. This will refresh any known records already cached and stash away the JSON for future primary key based lookups. You can also call load() on a model instance with a JSON hash as well but it will only update that single model instance.
Its unclear why you are not using an adapter, you can extend one of the Ember Model adapters and override the the record loading there, eg. extend from the RESTAdapter and do any required transform on the JSON if required by overriding _loadRecordFromData
You can also override your models load function to transform data received if required as well. The Ember Model source is fairly easy to read so its not hard to extend to your requirements.

Correct way to share state between controllers in Ember JS

Image you have an ArrayController which displays a list of items, each item using its own ObjectController. Now you have an action handler on the ArrayController which should change some property of all items. This property should not be persistend, it's only for the view state. An example would be isSelected.
Looking at the Todos Example (http://todomvc.com/architecture-examples/emberjs/) it seems very similar to the isCompleted property. But the main difference is that this property is meant to be stored in the db and so the model is the right place here.
In Ember.js, controllers allow you to decorate your models with display logic. In general, your models will have properties that are saved to the server, while controllers will have properties that your app does not need to save to the server.
Is there any way to loop through all item controllers of any ArrayController and update the controller's property? Would this be the correct approach?
It sounds like the correct approach to me. Controllers as you quote are meant for holding properties that doesn't need to be persisted. These properties held application state and/or are used in display logic.
The main difference with the Todos example is that you would modify the property in the itemController and not in the model. (Having in mind you don't want the property to be stored on the server).
What I'd do would go something like this:
App.MyArrayController = Ember.ArrayController.extend({
itemController: 'item',
actions: {
toggleSelection: function() {
this.forEach(function(item, index) {
item.toggleProperty('isSelected');
});
}
}
});
In case you'd want the action to change the model, you'd have to access the content propery of each itemControlller.
Hope it helps!

How to make a DOM element update after an object it depends on changes in meteor?

I have an object in an array
var
sidelist = [
{
name:"MURICA",
types:[...]
}
];
I have a box that displays the object's name. Then I have a text field and a button. On button press the object's name gets set to text field value. But I don't know how to make the name in the box change accordingly.
As I understand putting the object in a session variable is not an option since I will not be able to modify properties of objects inside of it without resetting the whole session var. I tried it and failed.
html
<template name="asdf">
{{#with object}}
<div>{{name}}</div>
{{/with}}
</template>
js
Template.asdf.object = function() {
return Objects.findOne(...);
};
EDIT
I think I've got your question wrong, sorry. If you have a value in memory that you'd like to change and have the DOM updated, use dependencies:
html
<template name="asdf">
{{property}}
</template>
js
var property;
// Create new dependency object that will manage refreshing property value:
var _dep = new Deps.Dependency;
updateProperty = function(value) {
property = value;
// Whenever you change value of the property, call changed() function:
_dep.changed();
};
Template.asdf.value = function() {
// Within reactive function, call depend() to rerun the function
// each time the value is changed:
_dep.depend();
return value;
};
How about a different and in my opinion simpler solution - using a local collection for your data.
I am not sure exactly why do you keep that sort of data into an array, but if it is because you only need it on the client then you can instead create a local collection and have all the reactivity benefits without writing all that code for making the array reactive. The data stored in a local collection is never sent to the server, so no communication or storage overhead.
You'd do it like that:
Sidelist = new Meteor.Collection(null);
[EDIT] Put the above line in your client-side-only part of the code.
Notice the null parameter. This will give you a collection that is only stored on the client and is a regular Meteor reactive source. Then you go about using it in your code and html just as you would a normal collection.
Hope that helps.

Categories