Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ExtJS and Complex Save Operations

ExtJS 4.1.0

Update 6/6/13:

I have posted this same question on the Sencha forums where there hasn't been much action. The post is more or less the same, but I figured I would add it here just for reference. I am still eager to hear other community members' input on what must be a very common scenario in an ExtJS Application! http://www.sencha.com/forum/showthread.php?265358-Complex-Model-Save-Decoupling-Data-and-Updating-Related-Stores

Update 7/16/13 (Conclusion?)

The Sencha post garnered very little discussion. I have decided to put the majority of the load of complex save operations on my application server and lazily refresh client stores where need be. This way I can use my own Database wrapper to encompass all of the transactions associated with one complex Domain Object save to guarantee atomicity. If saving a new Order consists of saving the order metadata, ten new instances of OrderContents and potentially other information (addresses residing in other tables, a new customer defined at the time of order creation, etc.) I would much rather send the payload to the application server, rather than establish a vulgar web of callbacks in client-side application code. Data which is associated on a One-to-One basis (such as an Order hasOne Address) is updated in the success callback of the Order.save() operation. More complex data, such as the Order's contents, is lazily handled by simply calling contentStore.sync(). I feel that this is the means to guarantee atomicity without an overwhelming number of client-callbacks

Original Post Content

Given the overall disappointing functionality of saving association-heavy models, I have all but ditched model associations in my application and rely retrieving associated data myself. This is all well and good, but unfortunately does not resolve the issue of actually saving the data and updating ExtJS stores to reflect the changes on the server.

Take for example saving an Order object, which is composed of metadata as well as OrderContents i.e., the parts on the order. The metadata ends up in an Order_Data table in the database, whereas the contents all end up in an Order_Contents table where each row is linked to the parent order via an order_id column.

On the client, retrieving the contents for an order is quite easy to do without any need for associations: var contents = this.getContentsStore().query('order_id', 10).getRange(). However, a major flaw is that this is hinging on the content records being available in the OrderContents ExtJS Store, which would apply if I were using associations NOT returned by the data server with the "main" object.

When saving an order, I send a single request which holds the order's metadata (e.g., date, order number, supplier information, etc.) as well as an array of contents. These pieces of data are picked apart and saved to their appropriate tables. This makes enough sense to me and works well.

All is well until it comes to returning saved/updated records from the application server. Since the request is fired off by calling a OrderObject.save(), there is nothing telling the OrderContents store that new records are available. This would be handled automatically if I were to instead add records to the store and call .sync(), but I feel this complicates the saving process and I would just much rather handle this decoupling on the application server not to mention, saving an entire request is quite nice as well.

Is there a better way to solve this? My current solution is as follows...

var orderContentsStore = this.getOrderContentsStore();
MyOrderObject.save({
    success: function(rec, op){
        // New Content Records need to be added to the contents store!
        orderContentsStore.add(rec.get('contents')); // Array of OrderContent Records
        orderContentsStore.commitChanges(); // This is very important
    }
});

By calling commitChanges() the records added to the store are considered to be clean (non-phantom, non-dirty) and thus are no longer returned by the store's getModifiedRecords() method; rightly so as the records should not be passed to the application server in the event of a store.sync().

This approach just seems kinda sloppy/hacky to me but I haven't figured out a better solution...

Any input / thoughts are greatly appreciated!

like image 792
John Hall Avatar asked May 14 '13 20:05

John Hall


People also ask

What is ExtJS?

Ext JS is a JavaScript application framework for building interactive cross-platform web applications using techniques such as Ajax, DHTML and DOM scripting.

What is the use of ExtJS?

Ext JS is a popular JavaScript framework which provides rich UI for building web applications with cross-browser functionality. Ext JS is basically used for creating desktop applications. It supports all the modern browsers such as IE6+, FF, Chrome, Safari 6+, Opera 12+, etc.

What is requires ExtJS?

Requires - All classes required to be defined for the class to be instantiated. Uses - A list of classes potentially used by the class at some point in its lifecycle, but not necessarily requried for the class to initially be instantiated. Subclasses - Classes that extend the current class.

What is Ext store?

Ext.util.Observable. The Store class encapsulates a client side cache of Model objects. Stores load data via a Proxy, and also provide functions for sorting, filtering and querying the model instances contained within it.


1 Answers

Update 8/26/13 I found that associated data is indeed handled by Ext in the create/update callback on the model's proxy, but finding that data wasn't easy... See my post here: ExtJS 4.1 - Returning Associated Data in Model.Save() Response

Well, it's been a couple months of having this question open and I feel like there is no magically awesome solution to this problem.

My solution is as follows...

When saving a complex model (e.g., a model that would, or does have a few hasMany associations), I save the 'parent' model which includes all associated data (as a property/field on the model!) and then add the (saved) associated data in the afterSave/afterUpdate callback.

Take for example my PurchaseOrder model which hasMany Items and hasOne Address. Take note that the associated data is included in the model's properties, as it will not be passed to the server if it solely exists in the model's association store.

console.log(PurchaseOrder.getData());
---
id: 0
order_num: "PO12345"
order_total: 100.95
customer_id: 1
order_address: Object
    id: 0
    ship_address_1: "123 Awesome Street"
    ship_address_2: "Suite B"
    ship_city: "Gnarlyville"
    ship_state: "Vermont"
    ship_zip: "05401"
    ...etc...
contents: Array[2]
    0: Object
        id: 0
        sku: "BR10831"
        name: "Super Cool Shiny Thing"
        quantity: 5
        sold_price: 84.23
    1: Object
        id: 0
        sku: "BR10311"
        name: "Moderately Fun Paddle Ball"
        quantity: 1
        sold_price: 1.39

I have Models established for PurchaseOrder.Content and PurchaseOrder.Address, yet the data in the PurchaseOrder is not an instance of these models, rather just the data. Again, this is to ensure that it is passed correctly to the application server.

Once I have an object like described above, I send it off to my application server via .save() as follows:

PurchaseOrder.save({
    scope: me,
    success: me.afterOrderSave,
    failure: function(rec,op){
        console.error('Error saving Purchase Order', op);
    }
});

afterOrderSave: function(record, operation){
    var me = this;
    switch(operation.action){
        case 'create':
            /** 
              * Add the records to the appropriate stores.
              * Since these records (from the server) have an id,
              * they will not be marked as dirty nor as phantoms 
              */
            var savedRecord = operation.getResultSet().records[0];  // has associated!
            me.getOrderStore().add(savedRecord);
            me.getOrderContentStore().add(savedRecord.getContents()); //association!
            me.getOrderAddressStore().add(savedRecord.getAddress()); // association!
            break;

        case 'update':
            // Locate and update records with response from server
            break;
    }
}

My application server receives the PurchaseOrder and handles saving the data accordingly. I will not go into gross details as this process is largely dependent on your own implementation. My application framework is loosely based on Zend 1.11 (primarily leveraging Zend_Db).

I feel this is the best approach for the following reasons:

  • No messy string of various model.save() callbacks on the client
  • Only one request, which is very easy to manage
  • Atomicity is easily handled on the application server
  • Less round trips = less potential points of failure to worry about
  • If you're really feeling lazy, the success method of the callback can simply reload stores.

I will let this answer sit for a bit to encourage discussion.

Thanks for reading!

like image 174
John Hall Avatar answered Oct 23 '22 17:10

John Hall