implicit load() in sencha touch store - extjs

I have a sencha touch store defined simply by a model and autoLoad=false. The model has only fields in its config, so I guess the data is stored within the memory, global to the application, am I right? (I'm new to sencha)
Ext.define('App.store.DataSession', {
extend: 'Ext.data.Store',
requires: ['App.model.DataSession'],
config: {
model: 'App.model.DataSession',
autoLoad: false
}
});
My question is that I'm seeing a code that doesn't load the data from the store explicitly, even though the store has autoLoad false
var storesession = Ext.getStore('DataSession');
var datasession = storesession.getAt(0);
if(datasession.get('state') == "1"){
....
does the getStore() function load the data implicitly? what are the implications of not using load()?

getStore() gets you a reference to the store.
If you want to load the data you have to e.g.:
var storesession = Ext.getStore('DataSession');
storesession.load({
params: {
id: 1 // some parameters
},
callback: function(records, operation, success) {
if(success) {
var datasession = storesession.getAt(0);
if(datasession.get('state') == "1") {
....
}
}
});
Have a look here: https://docs.sencha.com/extjs/7.0.0/modern/Ext.data.Store.html

Related

State provider does not restore sorters properly

I'm trying to store my grid state using the Ext.state.CookieProvider. The problem is I can't restore sorters parameters while state itself (width, order) is restoring properly.
First I've created cookieprovider in init() method of the viewport viewcontroller:
Ext.state.Manager.setProvider(Ext.create('Ext.state.CookieProvider', {}));
My store is set to auto load with remote sorting:
Ext.define('MyApp.requests.store.QueryRequestsGridStore', {
extend: 'Ext.data.Store',
model: 'MyApp.requests.model.QueryRequestsGridModel',
alias: 'store.queryRequestsGrid',
remoteSort: true,
autoLoad: true,
proxy: {
startParam: 'offset',
limitParam: 'limit',
url: '/requests',
noCache: false,
type: 'ajax',
reader: {
type: 'json',
rootProperty: 'data',
totalProperty: 'total'
}
},
});
Store is defined in grid using viewmodel binds:
bind: {
store: '{queryRequestsGrid}'
},
I'm loading the grid containing store from the viewport viewcontroller on button click like this:
var panelToAddName = Ext.create('MyApp.requests.view.QueryRequestsGridView', {});
var mainViewPort = Ext.ComponentQuery.query('#mainViewPort')[0];
var regionPanel = mainViewPort.down('[region=center][xtype=panel]');
regionPanel.removeAll();
regionPanel.add(panel);
Cookie contains sorters, but grid is loaded without any sort parameters.
"storeState":{"sorters":[{"root":"data","property":"date_completed","direction":"ASC"}]}}}
I've dug into the ext-all-debug.js source file and found initState() method of a 'Ext.state.Stateful' class.
initState: function() {
var me = this,
id = me.stateful && me.getStateId(),
hasListeners = me.hasListeners,
state, combinedState, i, len, plugins, plugin, pluginType;
if (id) {
combinedState = Ext.state.Manager.get(id);
if (combinedState) {
state = Ext.apply({}, combinedState);
if (!hasListeners.beforestaterestore || me.fireEvent('beforestaterestore', me, combinedState) !== false) {
plugins = me.getPlugins() || [];
for (i = 0 , len = plugins.length; i < len; i++) {
plugin = plugins[i];
if (plugin) {
pluginType = plugin.ptype;
if (plugin.applyState) {
plugin.applyState(state[pluginType], combinedState);
}
delete state[pluginType];
}
}
me.applyState(state);
if (hasListeners.staterestore) {
me.fireEvent('staterestore', me, combinedState);
}
}
}
}
},
If to log me.store from inside of this method, the store is shown in console as ext-empty-store while me is my loaded grid. Seems like state is applying before the store is properly loaded.
If to reuse the initState method inside beforerender grid event, sorters are restoring from cookie properly.
Any suggestions?
I have not worked with viewmodel binds as the sole bind between store and grid, and can't comment on whether that is supposed to work at all, or just by accident.
But I know that the viewmodel is processed very late, because the view has to be fully initialized first (including applyState), so the viewmodel can find all the components it wants to bind the listeners to.
So please try to add the store using any of the two "old-school" methods that work even without the viewmodel: store:'MyStoreId' or store:Ext.create('MyApp.store.MyStore') on the grid. That way, the store should be bound to the grid before applyState.
Furthermore, I see another issue you should address: Your store loads directly after store init. (autoLoad:true). At that time, it is not yet bound to the grid; thus, no sort/filter has been applied, which means that with remoteSort/remoteFilter enabled, you are sending too many requests to the server. I would recommend to load the store only after it has been applied to the grid (in grid.initComponent after the callParent call, or from grid.boxready listener). If you really want to use autoLoad, I'd recommend to look into setAutoLoad method

ExtJS model raw data is not synced with actual record upon load

I have a model
Ext.define('MyCompany.model.Customer', {
extend: 'Ext.data.Model',
fields: [
'id', 'name', 'taxID', 'tradeID', 'vatID', 'email'
]
}
and a store
Ext.define('MyCompany.store.Customers', {
extend: 'MyCompany.lib.base.Store',
model: 'MyCompany.model.Customer',
api: {
read: $["crmModule.CustomerController"].list,
create: $["crmModule.CustomerController"].save
}
});
The problem is that upon calling store.load() (e.g. from the console), the field vatID is not synced. I can see the field in store.data.items[0].raw, but in store.data.items[0].data the field is empty. How can I debug this issue? Where does ExtJS does the conversion from raw data to the actual records?
Look for the method named extractData defined for Ext.data.reader.Reader. There are following lines:
// If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
// We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
record.phantom = false;
// Use generated function to extract all fields at once
me.convertRecordData(convertedValues, node, record);
records.push(record);
When you step into the method convertRecordData, you will be presented with the generated temporary code for actual conversion like the following:
return function(dest, source, record) {
value = source["id"];
if (value !== undefined) {
dest["id"] = value;
}
value = source["name"];
if (value === undefined) {
if (me.applyDefaults) {
...

sencha touch 2: binding associations data to existing store

I have a simple data model that looks something like this (actual code below):
model Game:
fields: id, team_1_id, team_2_id
model GameScore:
fields: id, game_id, team_1_score, team_2_score, is_final, submission_date
model SpiritScore:
fields: id, game_id, team_1_score, team_2_score
What I want seems simple. I already have code that loads Games and GameScores in bulk. I have a 'Game' instance in hand, and can call gameScores(). And I get a store, but it's empty. I have code that will dynamically load it, by placing the store into the model's hasMany definition. But what I would really like is some way to bind the Game.gameScores() call to the my existing GameScores store. Even if it used a normal filter underneath, that gives me a single record that I can bind and use in a view. (Important note: the data does not come in nested form.)
This leads to my second question. Game:GameScores is 1:many, but I only ever display the most recent one (from live score reporting). What is the general approach here? I can also manually build a filter from the game_id, but I can only bind 1 record to a view, so I don't see how I can bring that other information into a view, short of a proper hasMany relationship. Is there another way?
Any and all advice, including telling me to RTFM (with a link to the relevant manual) would be greatly appreciated! I've been pulling my hair out on this (pro bono side project) for the last week.
Cheers!
b
Ext.define('TouchMill.model.Game', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'team_1_id', 'team_2_id' ],
hasMany: {
model: 'TouchMill.model.GameScore',
name: 'gameScores',
},
},
});
Ext.define('TouchMill.model.GameScore', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', 'is_final', 'submission_date', ],
},
// belongsTo necessary? Don't think so unless I want parent func?
});
Ext.define('TouchMill.model.SpiritScore', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', ],
},
},
I've never used touch, so I'm speaking about Ext4 here (4.2 to be precise)... And, your model definitions seem a bit broken to me (is that working with touch?). But whatever, you'll get the general idea. If my code don't work in touch, please try with Ext4.
Also, I understood that you're loading all your scores at once. If that's not the case, my solution will need to be adapted...
So, my general reasoning is the following: if you've loaded all your scores in memory, then why not use a memory proxy that uses the score store's data as the data source for the store generated for the association? I tried that and, quite to my surprise, it worked without a glitch.
To understand this, you need to know that a proxy is an independant data source, that is a proxy can be shared between multiple stores without problem. On the other hand, a store is expected to be bound to a single view or task. For example, if you bind the same store to two different grids, then filtering the first grid will affect the second as well.
And while most proxies do not "contain" their data, memory proxy do. Here's a relevant excerpt of Ext.data.proxy.Memory#read method:
resultSet = operation.resultSet = me.getReader().read(me.data)
So, enough theory, here's the proof of concept (tested in this fiddle):
// I instantiate this proxy myself in order to have a reference available
var masterScoreProxy = Ext.create('Ext.data.proxy.Memory');
Ext.define('TouchMill.model.GameScore', {
extend: 'Ext.data.Model',
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', 'is_final', 'submission_date' ],
// I've used a remote server to ensure this all works even asynchronously
proxy: {
// configure your own
}
});
Ext.define('TouchMill.model.Game', {
extend: 'Ext.data.Model'
,fields: [ 'id', 'team_1_id', 'team_2_id' ]
,hasMany: {
model: 'TouchMill.model.GameScore'
,name: 'gameScores'
// required in order to avoid Ext autogenerating it as 'touchmill.model.game_id'
,foreignKey: 'game_id'
// needed if we don't want to have to call gameRecord.gameScores().load()
,autoLoad: true
// first part of the magic: make the generated store use my own proxy
,storeConfig: {
proxy: masterScoreProxy
}
}
});
// Just mocking a store with two games
var gameStore = Ext.create('Ext.data.Store', {
model: 'TouchMill.model.Game'
,data: [{id: 1}, {id: 2}]
,proxy: 'memory'
});
// Creating the "master" score store (that will use the model's proxy)
var scoreStore = Ext.create('Ext.data.Store', {
model: 'TouchMill.model.GameScore'
// second part's in there
,listeners: {
load: function(store, records, success) {
if (success) {
// 1. replace the data of the generated association stores' proxy
// (I must say I'm quite surprised that I didn't had to extract the data of
// every records, nor to configure a reader and all for my shared proxy...
// But hey, that works!)
masterScoreProxy.data = records;
// 2. update already generated stores
// Alternatively, you could call gameRecord.gameScores().load() individually
// before each usage of gameRecord.gameStores()
gameStore.each(function(record) {
var childStore = record.gameScoresStore;
if (childStore) {
childStore.load();
}
});
}
}
}
});
// test first load
scoreStore.load({
callback: function(records, operation, success) {
if (success) {
// and here's to prove it
gameStore.each(function(record) {
record.gameScores().each(function(score) {
console.log('Game ' + record.id + ': ' + JSON.stringify(score.data, undefined, 2));
});
});
testRefreshedData();
}
}
});
function testRefreshedData() {
// test refreshing
scoreStore.load({
callback: function(records, operation, success) {
if (success) {
console.log('--- Scores have changed ---');
gameStore.each(function(record) {
record.gameScores().each(function(score) {
console.log('Game ' + record.id + ': ' + JSON.stringify(score.data, undefined, 2));
});
});
}
}
});
}
Regarding your other questions...
If you have a 1:n for Game:Score, you've got a 1:1 for Game:MostRecentScore... So, I'd try to use that.
As for the view, there should always be a way -- even if hackish -- to access data nested in your records. The way will depend on what you're calling view here... See, for example this question.

backbone.js - Overridden parse() does not set model attributes

I have a model which links to two sub-models like so:
var SubModel = Backbone.Model.extend({
defaults: {
headline: null,
image_url: null,
url: null
}
});
var MainModel = Backbone.Model.extend({
defaults: {
subModelA: null,
subModelB: null,
title: null
},
urlRoot: function() {
if (this.isNew()) {
return '/mainmodel/new';
}
return '/mainmodel';
},
initialize: function() {
this.fetch();
},
parse: function(data) {
var response = {};
response.subModelA = new SubModel(data.subModelA);
response.subModelB = new SubModel(data.subModelB);
response.title = data.title;
return response;
}
});
The issue I'm currently having is that calling var mainModelInstance = new MainModel() does correctly fetch from /mainmodel/new but mainModelInstance.attributes is always a blank object {}.
var mainModelInstance = new MainModel();
mainModelInstance.attributes; // Returns {}
Here is a sample of the server's response to /mainmodel/new:
{
"title": "Politics",
"subModelA": {
"headline": "Investigators: Iran tried to smuggle suicide belts, missiles by boat into Yemen",
"url": "http://dailycaller.com/2013/02/09/yemen-minister-says-weapons-came-from-iran/",
"image_url": "http://cdn01.dailycaller.com/wp-content/uploads/2013/02/54c7d52e1a384db489ab9ea568afddb0-e1360455589316.jpg"
},
"subModelB": {
"headline": "Review: Who needs Windows RT? Acer's Iconia W510 runs the real thing",
"url": "http://arstechnica.com/gadgets/2013/02/review-who-needs-windows-rt-acers-iconia-w510-runs-the-real-thing/",
"image_url": "http://cdn.arstechnica.net/wp-content/uploads/2013/02/w510-main-640x388.jpg"
}
}
It seems as though the model's attributes are not being updated via parse. Why aren't the model's attributes being updated?
Your code might as well be working, but you are not testing it correctly
You are calling this.fetch in yout initialize method.
calling model.fetch is an asynchronous call and when you are trying to evaluate mainModelInstance.attributes, the http request call is not yet completed.
You should test this with:
var mainModelInstance = new MainModel();
mainModelInstance.on('change', function() {
console.log(mainModelInstance.toJSON());
});
or even better, dont auto fetch on initialize (its not a best practice anyway)
and use the jQuery promise pattern:
var mainModelInstance = new MainModel();
mainModelInstance.fetch().done(function () {
console.log(mainModelInstance.toJSON());
});
This is just a work in progress answer, feel free to discuss in the comments.
I would change your MainModel definition like so:
subModelA: new SubModelA(),
subModelB: new SubModelB(),
parse: function(data){
this.subModelA.set(data.subModelA);
this.subModelB.set(data.subModelB);
return data; // we keep two copies of the data, in mainModel and submodels.
}
So assuming your server responds exactly like in your answer
var model = new MainModel();
model.get('title'); // Politics
model.subModelA.get('headline'); // Investigators: Iran tr...
Then, you may have to override the save method depending on how you wish to persist things back to the server — this might work.
save: function(key, val, options) {
this.set({
subModelA: this.subModelA.toJSON(),
subModelB: this.subModelB.toJSON()
});
Backbone.Model.prototype.save.apply(this, arguments);
}
First, default attributes values must be put into the defaults option, like so:
var SubModel = Backbone.Model.extend({
defaults: {
headline: null,
image_url: null,
url: null
}
});
Then, you'll have value to save to the server when creating a new instance. That will also fill up your mainModelInstance.attributes hash.
For the parsing problem, have you logged (console.log) in what you get back from the server?

Handling Subsidiary Views in Backbone.js

I have a basic Backbone application which obtain an array of JSON objects from a remote service and displays them: all good so far. However, each JSON object has an array of tags and I want to display the tags in a separate area of the webpage.
My question is: what is the most Backbone-friendly way of doing this? I could parse the existing data again in a second view, which is cleaner but takes up more computation (processing the entire array twice).
An alternative is gathering up the tag information in the primary view as it is working through the array and then passing it along to the subsidiary view, but then I'm linking the views together.
Finally, I'd like to filter based on those tags (so the tags will become toggle buttons and turning those buttons on/off will filter the information in the primary view); does this make any difference to how this should be laid out?
Bonus points for code snippets.
Hm. I'm not sure if this is the Backbone-friendly way, but I'll put the logic to retrieve a list of tags (I think that's what you meant by "parse") in the collection.
Both the main view and the subview will "listen" to the same collection, and the subview will just call collection.getTags() to get a list of tags it needs.
// Model that represents the list data
var ListDataModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
tags: []
};
}
});
// Collection of list data
var ListDataCollection = Backbone.Collection.extend({
model: ListDataModel,
initialize: function() {
var me = this;
// Expires tag collection on reset/change
this.on('reset', this.expireTagCache, this);
this.on('change', this.expireTagCache, this);
},
/**
* Expires tag cache
* #private
*/
expireTagCache: function() {
this._cachedTags = null;
},
/**
* Retrieves an array of tags in collection
*
* #return {Array}
*/
getTags: function() {
if (this._cachedTags === null) {
this._cachedTags = _.union.apply(this, this.pluck('tags'));
}
return this._cachedTags;
},
sync: function(method, model, options) {
if (method === 'read') {
var me = this;
// Make an XHR request to get data for this demo
Backbone.ajax({
url: '/echo/json/',
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
json: JSON.stringify([
{ id: 1, name: 'one', tags: [ 'number', 'first', 'odd' ] },
{ id: 2, name: 'two', tags: [ 'number', 'even' ] },
{ id: 3, name: 'a', tags: [ 'alphabet', 'first' ] }
]),
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
var listColl = new ListDataCollection();
listColl.fetch({
success: function() {
console.log(listColl.getTags());
}
});
I guess two reasons for handling this in the collection:
It keeps the View code cleaner (This is given that we are not doing very complex logic in the tag extraction - It's just a simple _.pluck() and _.union().
It has 0 business logic involved - It can arguably belong to the data layer.
To address the performance issue:
It does go through the collection twice - However, if the amont of data you are consuming is too much for the client to process even in this case, you may want to consider asking the Backend to provide an API endpoint for this. (Even 500 pieces of data with a total of 1000 tags shouldn't bee too much for a somewhat modern browser to handle nowadays.)
Hmm. Does this help?
JSFiddle to go with this with the collection and the model: http://jsfiddle.net/dashk/G8LaB/ (And, a log statement to demonstrate the result of .getTags()).

Resources