⬆️ ⬇️

Creating a prototype of a social network on ExtJS. The first and not the last problems with ExtJS 4

Constantly changing requirements and tight deadlines pushed us to use ExtJS 4 to create a prototype.



The problems in ExtJS that we encountered during development hardly exceeded the experience that we received while preparing ExtJS.



There are many articles on the ExtJS level 4 basic on the Internet. For example, I was very pleased with this article http://css.dzone.com/articles/how-use-extjs-4-jquery . But there are not many serious “non-cash” articles on solving (or creating) any problems with the help of ExtJS.

')

I suggest you heed the first article in the ExtJS patch lab ... Well, a little about the social. network ... quite a bit.







For the tape posts, it was decided to use Ext.panel.Grid with Ext.data.Store . You can say these are the fundamental things for which you chose to use ExtJS.



Why the grid , and not the dataView (later the end had to abandon the grid in favor of the view). Grid liked the visual mode of sorting and filtering data, an “endless” scroll. The joy of the opportunities provided simply overwhelmed.



Ext.data.Store



Data comes from the server using the call API, then it is transmitted in raw form to the store's loadData method. We did not use the proxy built into the Store because we have an API like that of Foursqure .



var myStore = Ext.create('Ext.data.Store', { model: 'Post', filterOnLoad: true, idProperty: 'id', filters: new Ext.util.Filter({ filterFn: function (item) { return item.get("status") > 0; } }) }); … API.Events.get().done(function(posts){ myStore.loadData(posts); }); 


The Post model is a successor to the Ext.data.Model class.



 Ext.define('Post', { extend: 'Ext.data.Model', idProperty: 'id', constructor: function (raw) { return this.callParent([this.prepare(raw), raw[this.idProperty]]); }, prepare: function (raw) { … } }); 


In the Post model, raw data is prepared through the prepare method. In the constructor of the parent class data comes already in the processed form.



What happens in the prepare method:

For example, casting the date of a post from a string to a Date object.



 this.data = new Date(this.data); 


... getting the string value of the name of the post from typeId through the hash table of matches



 this.typeName = Dict.post.types[this.typeId]; 


And so on...



Store stores data inside itself in the form of instantiated models, in the loadData method there is a check, if the data is still raw, then we transfer it to the model's constructor, and write it in ourselves, and so on with each element of the array.



Pleasant utility # 1



Thus loadData accepts both an array of raw objects, and already instantiated models.



Feature # 1



When a single object is transferred to loadData, an error occurs, the method expects only an array, even if from a single element.



Feature # 2



Objects in javascript are passed by reference, so the transferred raw data in loadData will become an array of models.



 API.Events.get().done(function(posts){ //posts is array of objects myStore.loadData(posts); //posts is array of Models }); 


If you need the original data after loading in unchanged form, use the Ext.Array.clone () method, but this method does not support deep cloning, like jQuery.extend .



Problem # 1



The loadData method has a second parameter, append (false) ; Those. from the server come new posts, we must add them to the existing ones. To do this, set the second parameter to true .



In our case the storage uses filtering by loading (filterOnLoad = true)



How filtering works:



Store stores all records in two collections, the main one is data , and the hidden one is snapshot . The “filterOnLoad: true” key in the Store forces the storage when loading data in any way to check it on the filters and discard the snapshot filtered into its internal storage.



The use of filters in the Store and the loadData method are incompatible.



Listing from ext-all-debug.js file (the loadRecords method is used by loadData ... and not only)



 loadRecords: function(records, options) { … if (!options.addRecords) { delete me.snapshot; me.clearData(); } me.data.addAll(records); … } 


Here lies the problem, options.addRecords in our case is true , the snapshot deletion does not occur, the data falls only in the main collection.



 API.Posts.get().done(function(posts){ myStore.loadData(posts); //posts.length 8, filtered 2 entries }); … API.Posts.get().done(function(posts){ myStore.loadData(posts, true); //posts.length 10, filtered all entries }); 


At the first boot, only 2 entries went through, and the second one was all.



me.data contains 2 + 10 records, and me.snapshot only 8, because data from the second call did not fall into the hidden storage.



In order to re-sort our collection, we need to clean up the old filters and apply new ones; we cannot simply “re-read” the filters (which is very, very strange).



The mystore.clearFilter () method kills the last 10 records, with the result that we have 8 records in the repository.



Decision



 loadRecords: function(records, options) { … if (!options.addRecords) { delete me.snapshot; me.clearData(); } me.data.addAll(records); //add records in snapshot too. if (options.addRecords && me.snapshot) { me.snapshot.addAll(records) } … } 


Problem # 2



The idProperty key is not used for its intended purpose.



The idProperty field is an analogue of the primary key in the database. In our case, the data has a unique ID key, according to which fresh posts should be added to the repository. If a post changes, for example, adding a comment to it, we must update this entry in the repository. The loadData method in append mode with the idProperty = "id" key set should solve this problem. But in fact, this key is not used for this, and we cannot make the data aggregation automatic.



Having rummaged in source codes I have found out that such functionality is present, but simply is not used. The me.data.addAll (records) method can accept not only arrays, but also key-value objects. You only need to slightly modify the same loadRecords method to use this feature.



Decision



 loadRecords: function(records, options) { … if (!options.addRecords) { delete me.snapshot; me.clearData(); } me.data.addAll(records); if (this.idProperty) { var indexRecord = {}; for (i = 0; i < length; i++) { indexRecord[records[i].data[this.idProperty]] = records[i]; } me.data.addAll(indexRecord); if (options.addRecords && me.snapshot) { me.snapshot.addAll(indexRecord); } records = []; for (key in indexRecord) { records.push(indexRecord[key]); } length = records.length; } else { me.data.addAll(records); if (options.addRecords && me.snapshot) { me.snapshot.addAll(records); } } … } 


After this change, as the key, we have the id values, and we can add new data, they will not be duplicated ...



PS Don't forget that it’s bad to crawl into the sources with your edits, use Ext.override . Thus, you get rid of the problem when updating the framework version, you just write your patch file, and when you fix the old version of the old problem, you remove your solution from the patch.



On the horizon, consideration of the problem of synchronization of the Store with its presentation, and what to do if there are several representations.



Applications are accepted, perhaps there are some more interesting problems and their solutions.

Source: https://habr.com/ru/post/129080/



All Articles