ExtJS 4 - Update/Refresh single record - javascript

I have a problem that's bugging me.
I have a grid and when i dblclick on a item I want to open a window to edit that item. Pretty standard stuff. The problem is, i want to be sure the record is up to date, because other people using the program may have changed it or even deleted it.
I could reload the store, but i only want one specific record to be checked... So i figured i would just go get the data, build another record and replace the existing one in the store but i really want to know the best way to do this
Bear in mind RESTful proxy is not an option for me, even though i don't know if the update operation works in this case ( server -> client).
EDIT:
this may help somebody:
all i did was copy the data and raw objects from the new record to the old one and then "commit" the changes. worked for me.
Thank you.

ExtJS 4.1
I had a similar problem and as an experiment tried
sStore.load({
id: mskey,
addRecords: true
});
where mskey is a uuid of a currently loaded record.
I did not remove the existing record first (as an experiment) and it updated the existing record that had the same id with the new data from the server (via the model --> REST proxy). Perfect!
I know you said you are not using a REST proxy, but this might help others who found this post searching for search terms like your topic name (which is how I got here!)
So, it looks like 'addRecords' means add or update.
FYI,
Murray

The best way to do something like this would be to reload the record in the event which opens the window. So where you would for example load the record from the grid store into a form within the window, you can use your model to load from the id.
Item.load(id, { success: function(r) { form.loadRecord(r); } });
Once saved, you should probably also call refresh on the grid view, which will redraw the changes from the save event. You can also use refreshNode (see grid view documentation) on the exact record in the store if you're concerned about performance.
Of course you do not have to use the restful proxy with this, you can use any proxy as long as it will load the single record.

With ExtJS 4.1, here is an override :
In CoffeeScript :
# Adds "reload" to models
Ext.define "Ext.ux.data.Model",
override: "Ext.data.Model",
# callBack is called with success:boolean
reload: (callBack) ->
Ext.getClass(#).load #getId(),
success : (r, o) =>
for k, v of r.data
#data[k] = v
#commit()
callBack(true) if Ext.isFunction(callBack)
failure: =>
callBack(false) if Ext.isFunction(callBack)
In JS (did not test) :
Ext.define("Ext.ux.data.Model", {
override: "Ext.data.Model",
reload: function(callBack) {
var me = this;
return Ext.getClass(this).load(this.getId(), {
success: function(r, o) {
var k;
for (k in r.data) {
me.data[k] = r.data[k];
}
me.commit();
if (Ext.isFunction(callBack)) {
callBack(true);
}
},
failure: function() {
if (Ext.isFunction(callBack)) {
callBack(false);
}
}
});
}
});

I created an override on the Ext.data.Model to add an additional method that can be used to update the data of an existing record (model instance).
Ext.define('Ext.overrides.data.Model', {
override: 'Ext.data.Model',
/**
* Refresh the data of a record from the server
*/
reloadData: function(cb) {
var me = this;
var id = me.getId();
Ext.ModelManager.getModel(me.modelName).load(id, {
callback: function(record, operation, success) {
if (!success) {
Ext.Error.raise('Problem reloading data from server in record');
}
if (!record) {
Ext.Error.raise('No record from server to reload data from');
}
//change the data of the record without triggering anything
Ext.apply(me.data, record.getData());
//call a final callback if it was supplied
if (cb) {
cb.apply(me, arguments);
}
}
});
return me;
}
});
This is how you can use it. It's actually pretty simple:
myRecord.reloadData(function(record, operation, success) {
//Done updating the data in myRecord
});
Internally it uses the load method on the associated model to create a new record. That new record is based on the same id as the original record that the reloadData method was called on. In the callback the data of the new record is applied to the data of the original record. No events triggered, which is probably hat you want.
This is Ext 4.2.1. There's probably dozens of scenario's that this solution breaks but we can always refine can't we.
Update: This solution basically implements the same as the one by #Drasill. Oh well... This one was tested though.

Related

Odd behavior when using Ember Data under a high-latency network

Saving my Ember Data model causes the data store to update the record from the response from the server.
For most cases, this functionality is expected and works perfectly fine. However, I ran into a case where if the server response is slow, then any fields that were modified post-save are now reset.
We had a requirement where we needed to "save on input blur", so I reopened some of the built-in view helpers to send a 'save' event to the current controller. For example:
Ember.Select.reopen(Ember.TargetActionSupport, {
_sendSave: function() {
this.triggerAction({
action: 'save',
target: this.get('controller')
});
}.on('change')
});
As for my current solution, I am simply throttling the save action:
save: function() {
Ember.run.throttle(this, this._save, 5000); // saves the model if `isDirty`
}
I was wondering if anyone has any ideas on the best way to handle this? One solution that was mentioned from my team was to not sync the data from the incoming response. I do not think that this is a good idea, and I would prefer that the current record merges itself into the response's record. Please let me know if further elaboration is needed.
I ended up modifying the adapterDidCommit hook, merging current data with the server data.
A solution is explained here:
How do I save data to my Ember store if the POST response contains only an id?
adapterDidCommit: function() {
var currentData = this.toJSON();
this._super.apply(this, arguments);
Ember.merge(this._data, currentData);
}

Saving Breeze entities manually and updating keyMappings

I want to manually save entities in Breeze. We just don't have the option (as much as I try to fight for my opinion) to use the SaveChanges(JObject saveBundle) and need to directly hit a 3rd party Web API with a specific URL for POST/PUT requests.
So I am basically looping through EntityManager.getChanges() and then handling Modified, Added, and Deleted entities.
I can handle the "Modified" without any problems. However, on "Added", I know I need to update keyMappings when I add a new entity after successful save but cannot find any documentation on how to do that manually in JavaScript.
I also wanted to see if there any examples in returning any errors. Basically I want to hook into this call:
$http(params).then(
function (response) { // success
console.log(response);
// update key mappings if its an "Added" somehow
// entityAspect.acceptChanges();
dfd.resolve("something eventually");
},
function () { // error
// added error object here and reject changes on this entity? or just show error message?
dfd.reject("error");
});
return dfd.promise;
In case anyone's wondering, I just check the entityAspect.entityState.isAdded() method. Get the new identity returned from my 3rd party and just update the id accordingly. Our system is a little bit nicer in that we have a set key for all of the entities.
Code wise it looks something like this (dfd is a $q defer):
$http(params).then(function (response) { // success
// on add update the instance id with the new instance id
if (entityState.isAdded()) {
var newId = response.data.changedInstance.newId;
entity.Id = newId ;
}
entityAspect.acceptChanges();
dfd.resolve(response.data);
},
function (response) { // error
dfd.reject(response.data);
});

How do I know my collection already has data using Backbone.JS?

I am developing a site using javascript framework BACKBONE.JS. In my site, There is one Category Selection drop down. Using Backbone collection fetch, I have rendered my category drop down successfully. In my header i have three horizontal menu[image shown below]. User click of the menu(Page navigation is done using backbone routers). My main content of the day will change. The user can filter the content based on the category. My category filter drop down option will not change frequently.
ALL = http://www.Site1.com
MOBILE = http://www.Site1.com/#all/mobile
DESKTOP = http://www.Site1.com/#all/desktop
My Router:
dealapp.AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"all/mobile": "mobile",
"all/descktop": "displayAllVoucher"
},
home: function () {},
mobile: function () {},
desktop: function () {}
});
Success Case
I am loading my site Using "http://www.Site1.com/". The function home will get a call and do the listed action. If i am navigating to some other tab(mobile/desktop), my category drop down displaying.[ Note : i am fetching my category from the server in the home function]
scenario
I am loading my site using "http://www.Site1.com/#all/deal" directly. In this case my category drop down is not rendering , i am getting an empty drop down. I know that i haven't added my category fetch in the other two functions mobile and desktop. If i include the category fetch in mobile and desktop function each time then my category fetch call goes to server and fetches data from server.
My doubt
How do i know if my collection already has data? I want to reuse the already downloaded data. If data not available in the local storage then i need to fetch it from the server.
You can override fetch on the collection. Fetch returns a deferred object, you can store this on the collection itself. If the deferred is null you will call the prototype fetch. The advantage is that in your code you always call fetch, and if the collection has data you return the cached data.
fetch : function(options) {
if(this.deferred){
return this.deferred;
}
this.deferred = Backbone.Collection.prototype.fetch.call(this, options);
return this.deferred;
}
This specific problem was dealt with by others and a few plugins can be found.
The one I am currently using with success is the Thorax framework that adds a few things over Backbone.
For Model and Collection they added isPopulated() and isEmpty() method as can be seen here in their documentation.
They will tell you if there is data in the collection or not. If you don't want to use the entire framework, just copying the code from their Git repository here, would do.
In a few words they solve the problem by overriding fetch to set a property called _fetched to true when the data are fetched.
Another way would be to cache the data. Most of the time this is a good idea, but this depends. In your scenario it could be a good idea to cache it in a localStorage.
A plugin I found that seems to do it's job is Backbone fetch cache.
Description:
This plugin intercepts calls to fetch and stores the results in a
cache object (Backbone.fetchCache._cache). If fetch is called with {
cache: true } in the options and the URL has already been cached the
AJAX call will be skipped.
Yet another version is mentioned in this answer: Caching collections in backbone.js?
As the answerer there said, you could do it similar to this:
var manager = (function(){
var constructors = {
'example': ExampleCollection
};
var collections = {};
return {
getCollection: function(name) {
if(!collections[name]) {
var collection = new constructors[name]();
collection.fetch();
collections[name] = collection;
}
return collections[name];
}
}
})();
Here the manager is responsible for instantiating collections and
fetching them. When you call:
var exampleCollection = manager.getCollection('example');
you get an instance of example collection with data being already
fetched. Whenever you need this collection again you can call the
method again. You will then get the exact same instance with no need
to fetch it again.

Is it a code smell if I have the need to save a Backbone.Collection?

I've been trying to wrap my head around best RESTful practices while using BackboneJS. I feel like I've written myself into a bit of a knot and could use some guidance.
My scenario is this: a user wants to create a new Playlist with N items in it. The data for the N items is coming from a third-party API in bursts of 50 items. As such, I want to add a new, empty Playlist and, as the bursts of 50 come in, save the items and add to my Playlist.
This results in my Playlist model having a method, addItems, which looks like:
addItems: function (videos, callback) {
var itemsToSave = new PlaylistItems();
var self = this;
// Create a new PlaylistItem with each Video.
videos.each(function (video) {
var playlistItem = new PlaylistItem({
playlistId: self.get('id'),
video: video
});
itemsToSave.push(playlistItem);
});
itemsToSave.save({}, {
success: function () {
// OOF TERRIBLE.
self.fetch({
success: function () {
// TODO: For some reason when I call self.trigger then allPlaylists triggers fine, but if I go through fetch it doesnt trigger?
self.trigger('reset', self);
if (callback) {
callback();
}
}
});
},
error: function (error) {
console.error("There was an issue saving" + self.get('title'), error);
}
});
}
ItemsToSave is generally a Collection with 50 items in it. Since BackboneJS does not provide a Save for Collections, I wrote my own. I didn't care much for creating a Model wrapper for my Collection.
So, when I call Save, none of my items have IDs. The database assigns the IDs, but that information isn't implicitly updated by Backbone because I'm saving a Collection and not a Model. As such, once the save is successful, I call fetch on my Playlist to retrieve the updated information. This is terrible because a Playlist could have thousands of items in it -- I don't want to be fetching thousands of items every time I save multiple.
So, I'm thinking maybe I need to override the Collection's parse method and manually map the server's response back to the Collection.
This all seems... overkill/wrong. Am I doing something architecturally incorrect? How does a RESTful architecture handle such a scenario?
My opinion is do what works and feels clean enough and disregard what the RESTafarians credence might be. Bulk create, bulk update, bulk delete are real world use cases that the REST folk just close their eyes and pretend don't exist. Something along these lines sounds like a reasonable first attempt to me:
create a bulkAdd method or override add carefully if you are feeling confident
don't make models or add them to the collection yet though
do your bulk POST or whatever to get them into the database and get the assigned IDs back
then add them as models to the collection

BackboneJS: Load more items into a collection

In Backbone JS when I fetch a collection should I be fetching the entire collection or a small portion of it?
For example I have news feed collection in mongoDB that could have potentially 1000s of items. When the user hits the page I only want to show them the latest 10 items with the option to 'Load More'. But if they visit a specific item via URL http://site.com/#/feed/:itemID I want to be able to pull up that item's record.
1. How many document should I be fetching initially?
2. How would I got about fetching any item by id?
I ended up using the {add: true} statement when calling fetch on my collection. This prevents the collection from being replaced by the result of the fetch and but instead appends the result to the collection. I then also passed the 'skip' amount using the {data: {skip: amountOfItemsInCollectionAlready }, this is used on the server-side to get the correct batch of items from the database.
My final fetch method looks like this:
loadMore: function(e){
this.collection.fetch({
add: true,// this adds to collection instead of replacing
data:{// this is optional params to be sent with request
skip: this.collection.length// skip the number of items already in the collection
}
});
}
You probably don't want to just use Collection.fetch(), because you won't get the benefit of client-side caching - it'll drop the items you've already loaded from the server and reset the collection. You will probably need to extend Backbone.Collection with a custom function to retrieve more items. I used the following code in a recent project:
Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) collection.trigger('reset', collection, options);
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is mostly copied from the default fetch() code, but instead of dropping existing items it will add new ones. You'd probably want to implement something server-side, using the options object as Julien suggests to pass in the parameters of what items you want to load, probably either a page number (if you want to control page size on the server) or a start-stop pair (if you want to control it on the client).
1 - You should be fetching 10
Add a page argument to your collection and have the backend code return the page matching (10/page). /my_objects?page=2 to get records 10-20 etc.
You do this like this (untested):
collection.fetch({data: {page:2}})
Or you alter the URL directly
2 - To fetch an item by ID you create the model
object = new Model({id: 1})
and fetch it
object.fetch()

Categories

Resources