ExtJS store with data-bound URL not re-evaluating data properties - javascript

I've an ExtJS 5 ViewModel which contains a store, with a JSON reader using an AJAX proxy.
The URL of the proxy however relies on binding, which substitutes in ViewModel data properties. However it appears that the evaluation of the URL property is done only the first time the ViewModel loads, and not when the data property changes.
This code should illustrate what I'm trying to do, even though its missing parts (extends statements, etc.) for brevity in this post.
I define my view model with a store:
Ext.define('sample.ViewModel',{
extend:'Ext.app.ViewModel',
data:{
departmentId:0,
categoryId:0
}
stores:{
myItems:{
autoLoad:false,
fields:['id','name','price'],
proxy:{
type:'ajax',
url:'/items/department/{departmentId}/category/{categoryId}',
reader:{
rootProperty:'data',
successProperty:'success'
}
}
}
}
}
Then a view with a grid:
(columns, etc. removed for brevity)
Ext.define('sample.View',{
// ....
items:[{
xtype:grid,
bind:'{myItems}'
}]
}
And a controller who changes the view model data properties and reloads the store:
Ext.define('sample.ViewController',{
// ...
handleSomeEvent:function(){
var viewModel = this.getViewModel();
var store = viewModel.getStore('myItems');
viewModel.set('departmentId', 3);
viewModel.set('categoryId', 4);
// Desired behavior
store.load();
// Required workaround
viewModel.bind('/items/department/{departmentId}/category/{categoryId}', function(newUrl){
store.load({url: newUrl})
});
}
});
Without the "required workaround" in the controller logic the store / grid always tries to pull data from /items/department/0/category/0 (i.e. the ViewModel data values at the time that the ViewModel is instantiated).
I'd like to know how to force ExtJS to re-evaluate config properties (i.e. the proxy URL) when the ViewModel data properties change.
Much thanks in advance!

The problem you're having is that the ViewModel ticks on a timer. By the time you call load, it's not evaluated the calls to set. If it didn't, you'd have the binding trigger twice, once when you set the departmentId, then a second time when you set the categoryId.
You can force the ViewModel to tick by calling notify:
viewModel.set('departmentId', 3);
viewModel.set('categoryId', 4);
viewModel.notify();
store.load();

Related

Binding data into view - Is it better to pass data down from parent views to child/grandchild views or initialize/query data whenever creating views?

I am working on a Backbone/Marionette project. This project implements a way to cache data on local memory after loading them from server. Therefore data can be access anytime, anywhere within the project.
This makes me wonder what is the better way to populate data to view in my case:
const ChildView = marionette.View.extend({/*...*/});
const ParentView = marionette.View.extend({
// ...
onRender() {
// 1: pass data to child view from parent view
const childView = new ChildView({
data: this.options.data,
}));
// 2: initialize data when creating new child view
const childView = new ChildView({
data: SomeModel.new({/* some properties */}),
}));
},
// ...
});
new ParentView({
data: SomeModel.new({/* some properties */}),
}).render();
Both methods work correctly. However, the project view structure is pretty deep and complicated so I prefer the second way because with the first one I would need to go up and down a lot to check what data is and where it comes from.
Do you think if there are any possible problems with this method?
I prefer the 1st way, passing data from parent to child, but it depends on what your views are doing.
For me, a big advantage of sharing a data object is that updating it within one view updates it in all other views (this will work if you pass an existing backbone Model, or any object as data). This can save a lot of work... when a user updates their background color (for example), you can update it once in your BackgroundColorChoose view, and know that it is already updated everywhere else that data is in use.
In a sense, it doesn't matter where the data came from, only what it represents (because it can be accessed/modified from within any of your views).
I can imagine scenarios where this approach is not good, but I've found it makes a good baseline to start from (and avoids the need to trust browser-caching)

Ember Data not serializing record id on save(), resulting in PUT with no id?

I have a route that creates a new record like so:
App.ServicesNewRoute = Ember.Route.extend({
model : function() {
return this.store.createRecord('service');
},
setupController: function(controller, model) {
controller.set('model', model);
},
});
Then I bind that model's properties to the route's template using {{input type="text" value=model.serviceId ... }} which works great, the model gets populated as I fill up the form.
Then I save record:
App.ServicesNewController = Ember.ObjectController.extend({
actions : {
saveService : function() {
this.get('model').save(); // => POST to '/services'
}
}
});
Which works too.
Then I click the save button again, now the save method does a PUT as expected since the model has an id set (id: 102):
But then when I look at the PUT request in Dev Tools, I see that the id attribute was not serialized:
As a result, a new instance is created in the backend instead of updating the existing one.
Please ignore the serviceId property, it is just a regular string property unrelated to the record id which should be named just id.
I don't know why the id is not being serialized... I cannot define an id property on the model of course since Ember Data will not allow it, it is implicit. So I don't know what I am missing...
Any help is greatly appreciated!
The base JSONSerializer in Ember-Data only includes id in the payload when creating records. See DS.JSONAdapter.serialize docs.
The URL the RestAdapter generates for PUTting the update includes the ID in the path. In your case I believe it would be: PUT '/services/102'.
You can either extract it from the path in your backend service. Or you should be able to override the behavior of your serializer to add the id like this:
App.ServiceSerializer = DS.JSONSerializer.extend({
serialize: function(record, options) {
var json = this._super.apply(this, arguments); // Get default serialization
json.id = record.id; // tack on the id
return json;
}
});
There's plenty of additional info on serialization customization in the docs.
Hope that helps!
Initially I used ronco's answer and it worked well.
But when I looked at ember data's source code I noticed that this option is supported natively. You just need to pass the includeId option to the serializer.
Example code:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serialize: function(record, options) {
options = options ? options : {}; // handle the case where options is undefined
options.includeId = true;
return this._super.apply(this, [record, options]); // Call the parent serializer
}
});
This will also handle custom primary key definitions nicely.
Well, as far as I know it's a sync issue. After first request you do the post request and then, it has been saved in the server, when you click next time the store haven't got enough time to refresh itself. I've got similar issue when I've created something and immediately after that (without any transition or actions) I've tried to delete it - the error appears, in your case there's a little bit another story but with the same source. I think the solution is to refresh state after promise resolving.

GeoJSON layer does not get recreated when rendered using EmberJS's 'link-to' helper

I am trying to render a LeafletJS map where the colours of the states in the map are dependent on a global parameter that is set in the appropriate Ember route. The setting of the parameter is not the issue but rather the (re)creation of the geoJson layer. When hitting the URL for the first time or when reloading the page the correct map is created, however when the page is rendered using Ember's 'link-to' helper, the map still holds the state colours of the previous page.
drawAll: function() {
var that = this;
Ember.$.ajax('/data/sa_provinces.json').then( function(data){
Frontend.globalPaths = data;
that.get('store').findAll('province').then(function(provinces) {
provinces.forEach(function(province) {
var provinceGeoJSON = window.L.geoJson( province.get('dataFromJSON'),
{ style: province.get('geoJSONStyle'),
province: province,
onEachFeature: province.get('onEachFeature') });
province.set('geo_json', provinceGeoJSON);
provinceGeoJSON.addTo(Frontend.map);
window.province = province;
});
});
});
}.property('drawAll')
This drawAll function is located within a Ember controller and is called from an Ember template. The functions dataFromJSON, geoJSONStyle and onEachFeature are all called the first time a page is called or when the page is refreshed but not when the page is rendered using the Ember's 'link-to' helper. Neither are they called when the URL is entered manually.
If anyone has any ideas or experience with LeafletJS and/or Ember I would really appreciate your help. Thanks in advance, Greg.
The first issue I notice is that drawAll is a computed property, not a function - you seem to be confusing computed properties and functions.
http://emberjs.com/guides/object-model/computed-properties/
Ember computed properties are more like normal attributes that observe other variables, and recompute when those variables change. The property() method after the function declaration changes it into a computed property and specifies which variables the property depends on. On the last line you're specifying that drawAll observes itself, which doesn't make much sense.
You can't call functions from handlebars templates - you can only access properties. So you can access a property, with the side effect of causing that property's function to be called.
If you want just a function that is called as soon as the template loads, you can implement the didInsertElement function on that templates corresponding view, and the contents of the didInsertElement function will run when the template loads.
If you want a property that recomputes based on some conditions changing, you should change the last line to specify which conditions it is observing.
I can't be sure without more info about the template and controller you're using, but for your current use case it looks like you just want a function that runs whenever the template is inserted, so changing the drawAll to an actual function (by removing the .property('drawAll)) and calling it from didInsertElement of the corresponding view will rerun it every time the controller is inserted. Like:
didInsertElement: function() {
this.drawAll()
}
(You need to have created a view that corresponds to the controller in this context)

Knockout - added observable not updating on new objects

For UI purposes, when I load the array my viewModel is based on I add a new property to each object based on some other properties:
item.forEach(function (party) {
if (party.AcknowledgementDate() === null) {
party.Agreed = ko.observable(false);
}
else {
party.Agreed = ko.observable(true);
}
vm.Parties.push(party);
});
"Parties" is defined as ko.observableArray when the page starts.
The items in this array are edited in a separate UI window. When those changes are saved and the window closed, I call this function to update those values:
function updateAgreed() {
vm.Parties().forEach(function (i) {
if (i.AcknowledgementDate() === null) {
i.Agreed(false);
}
else {
i.Agreed(true);
}
});
}
This all works fine, and makes me very happy. The problem arrives when users create a new party item. We're using Breeze too, so we go off to the data service which requests entity framework create a new object of the appropriate type, then add an observable:
var lp = manager.createEntity('Party_dto'. { [an array of initial values] });
lp.Agreed = ko.observable('');
return lp;
Thanks to Breeze, this adds itself to the Parties observableArray because it's related to the same parent object. I can then call updateAgreed again to populate the Agreed observable with the appropriate value.
Logically, this work as expected - you can step through it and watch the Agreed observable of the new item be added and populated with the expected values. The problem comes in the UI - it doesn't update as having changed. Yet running the same code against an already-loaded object does cause the UI to update.
I'm stumped by this. I can't replicate it in Fiddle because we create objects in Breeze and not on the fly - and making a mock version without Breeze works perfectly. Why do my observables update on already loaded objects, but the same observable not update on a new object?
There are a few things that I see that need to be addressed. One, since you are using Breeze, take advantage of the model constructors and initializers. Wherever you are defining properties for your models, add the following code -
metadataStore.registerEntityTypeCtor(
'Party', null, partyinitializer);
function partyinitializer(party) {
party.Agreed = ko.observable(false);
}
Now all of your party entities have an agreed property that you can access. Next, make sure you aren't setting the Party's parent navigation property in the createEntity method, as that will break your binding.
var lp = manager.createEntity('Party'. { [an array of initial values] });
lp.parentParty(something); // Set the parent here
return lp;
This will make sure that before the party is bound back to the parent and shown in the view, all of the properties will be set. Then when you set the navigation property, it will show up in your view all happy-like.

rivets.js: prepopulate model with data from view on init

Perhaps this seems a bit backwards, but I have a view bound with Rivets.js for which I'd like the view to populate the model on initialization.
The usecase is that I'm using server-side rendering to return a snippet (the view) including rivets' data-attributes. So NO JSON is returned from server to client.
Now, by pressing 'edit' a user may put the content in 'edit'-mode, and start editing at will. (Using contenteditable, but this is out of scope here I guess).
So how to make sure the model is populated with values from the view on init?
I know that this question is a little outdated but I recentry tried rivets and I came across the same problem.
The solution:
// In your rivets configuration you disable preload:
rivets.configure({
templateDelimiters: ['[[', ']]'],
preloadData: false
});
// you bind your data
var binding = rivets.bind($('#auction'), {auction: auction});
// you manually publish it once to populate your model with form's data
binding.publish();
And that's it. I still don't know how to disable prelaod per bind
From the example on Rivets website (assign to 'rivetBinding')
var view = rivets.bind($('#auction'), {auction: auction});
doing rivetBinding.publish(); will bootstrap the model with values from the view for all bindings that have 'publishes = true'.
This question is old but it still has no accepted answer, so here goes:
You need to disable the preload configuration so rivets doesn't override whatever is in the input with what you have in your model at the time you do the binding. This can be done via the preloadData=false configuration, either globally (rivets.configure(...)) or view-scoped (third param to rivets.bind(...)).
After the binding, you need to publish the view (pull the values to your model). You also need to set up the observers via sync() call, otherwise your binded methods won't be triggered.
Using the same example as the previous answers:
var view = rivets.bind($('#auction'), { auction: auction }, {
preloadData: false
});
view.publish();
view.sync();

Categories

Resources