I have a proxy store that fills through two different ways - either via loadData, or via load.
I have certain actions that should take place once the store is filled; namely, a certain record should be searched and selected:
preselectByName:function(name, groupName) {
var store = this;
if(store.preselected) return;
store.preselected = true;
store.on('load',function() {
store.selectByName(name, groupName);
store.preselected = false;
},store,{single:true});
}
called like this:
if(store.isLoaded) store.selectByName(name, groupName);
else store.preselectByName(name, groupName);
This code works fine if the store fills via load, but not via loadData.
Is there another event that is fired from both load and loadRecord?
How else would I implement a listener that fires on any of the two events, whichever comes first?
datachanged (http://docs.sencha.com/extjs/6.2.1/classic/Ext.data.Store.html#event-datachanged)
is an event, that fires from both load and loadRecord, but be careful, it fires from any change that made to the dataset.
Besides: I usually use this to find the event that I need in ExtJs:
Ext.util.Observable.capture(myObj, function(evname) {console.log(evname, arguments);})
It captures all ExtJs events, fired by the myObj component and logs it to the console.
Or you could create a custom event, which you fire manually on load and after loadData, like:
store.on("load",function(){store.fireEvent("myload")})
and
store.loadData(...)
store.fireEvent("myload")
then
store.on("myload",function(){...})
The load event is specifically for reading data from a remote data source. The store can be extended to introduce an event that satisfies your requirements.
/*
#event loadrecords Fires whenever records a loaded into the store.
#param {Ext.data.Model[]} records An array of records
*/
Ext.define('MyApp.data.Store', {
extend: 'Ext.data.Store',
load: function() {
this.callParent(arguments);
this.fireLoadRecords();
return this;
},
fireLoadRecords: function() {
this.fireEvent('loadrecords', this.getRange());
},
loadRecords: function(records, options) {
this.callParent(arguments);
this.fireLoadRecords();
}
});
http://docs.sencha.com/extjs/6.2.0/classic/Ext.data.ProxyStore.html#event-load
Related
Im working on an extjs application. We're have a page that is for looking at a particular instance of an object and viewing and editing it's fields.
We're using refs to get hold of bits of view in the controller.
This was working fine, but I've been sharding the controller into smaller pieces to make it more managable and realised that we are relying on a race condition in our code.
The logic is as follows:
Initialise the controller
parse the url to extract the id of the object
put in a call to load the model with the given view.
in the load callback call the controller load method...
The controller load method creates some stores which fire off other requests for bits of information using this id. It then uses some of the refs to get hold of the view and then reconfigures them to use the stores when they load.
If you try and call the controller load method immediately (not in the callback) then it will fail - the ref methods return undefined.
Presumably this is because the view doesnt exist... However we aren't checking for that - we're just relying on the view being loaded by the time the server responds which seems like a recipe for disaster.
So how can we avoid this and be sure that a view is loaded before trying to use it.
I haven't tried rewriting the logic here yet but it looks like the afterrender event probably does what I want.
It seems like waiting for both the return of the store load and afterrender events should produce the correct result.
A nice little abstraction here might be something like this:
yourNamespace.createWaitRunner = function (completionCallback) {
var callback = completionCallback;
var completionRecord = [];
var elements = 0;
function maybeFinish() {
var done = completionRecord.every(function (element) {
return element === true
});
if (done)
completionCallback();
}
return {
getNotifier: function (func) {
func = func || function (){};
var index = elements++;
completionRecord[index] = false;
return function () {
func(arguments);
completionRecord[index] = true;
maybeFinish();
}
}
}
};
You'd use it like this:
//during init
//pass in the function to call when others are done
this.waiter = yourNamespace.createWaitRunner(controller.load);
//in controller
this.control({
'SomeView': {
afterrender: this.waiter.getNotifier
}
});
//when loading record(s)
Ext.ModelManager.getModel('SomeModel').load(id, {
success: this.waiter.getNotifier(function (record, request) {
//do some extra stuff if needs be
me.setRecord(record);
})
});
I haven't actually tried this out yet so it might not be 100% but I think the idea is sound
I have a tabular data view with ability to sort on each individual column. As data being huge, I prefer to show few rows in the page and load rows on demand. So wheneever user sorts a column in page, a new request is made to server and new data rows are fetched to display. But User can modify sort type(asc or desc) of two or more columns at a time with pop up page allowing it to modify sort type for more columns. I tried to solve this use case with a collection of metadata (asc/desc and other information) for columns.
var ColumnsMetadata = Backbone.Collection.extend({
model: ColMetadata
});
var ColMetadata = Backbone.Model.extend({
defaults: {
sort: 'none'
}
});
A EditSortView page to present and edit the column sort info. And a TableView to display tablular data. The TableView and EditSortview both share ColumnsMetadata.
var EditSortView = Backbone.View.extend({
initialize: function() {
//this.colleciton = columnMetadataCollection
},
events: function() {
"change input.sortVal" : "_updateSortMetdata",
"click button#sort": '_sort'
},
_updateSortMetdata: function() {
// updates right ColMetadata instance' sort property
},
_sort: funciton() {
// should make fetch request only when user finally
// satisfy with sort order changes
}
});
var TableView = Backbone.View.extend({
initialize: function() {
// other set up code
// i could add listener on collection
this.listenTo(this.columnMetadataCollection, 'change', this._fetchAndUpdate);
},
_fetchAndUpdate: function() {
// uses columnMetadataCollection to fetch data
}
});
As I added change listener on columnMetadataCollection in TableView, for each colMetadata change (from EditSortView popup) would trigger a new request to server. But it is optimal to make only one request to server when user satisfies with changes made.
How can I listen to columnMetadataCollection in TableView so that only a request is made to sever? Or how can I structure my code with Backbone to make it right?
You might want to look into rate-limiting your requests, e.g. with TrafficCop. See http://lostechies.com/derickbailey/2012/03/20/trafficcop-a-jquery-plugin-to-limit-ajax-requests-for-a-resource/
The other option is to keep a reference to the xhr that is returned when you call (e.g.) fetch and abort the operation with resolveor fail(see http://api.jquery.com/category/deferred-object/)
I think someone must have run into this situation before. Basically I have a big "form" which is composed of multiple smaller "forms" inside. (In fact, they are not real forms, just sets of inputs that are grouped together to collect info for models).
This form is for a checkout page, which contains:
shipping address
shipping method
billing address
billing method
other additional info such as discounts code input, gift wrapping etc.
I would like to update the user filled info to the server as soon as they complete each part (for example, when they complete shipping address). However, I want to make it work seamlessly without the need for the users to click some kind of "update" button after filling each partial part. I wonder if there is some way to go around this?
You'll want to $watch the fields in question and act upon them (say save to db) when they are filled in. The issue you will run into is how to determine when a user has filled fields in. Things like onblur etc don't work very well in practice. I would recommend using what is called a debounce function which is basically a function that allows the user to pause for X amount of time without our code going "ok done! now let's.. ohh wait still typing..."
Here's an example that I use on my own cart - I want to automatically get shipping quotes once I have an address so I watch these fields, allow some pausing with my debounce function then call my server for quotes.
Here's some controller code:
// Debounce function to wait until user is done typing
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
// Apply debounce to our shipping rate fetch method
var fetch = debounce(function() {
$scope.fetching = true;
cartService.updateShipping($scope.shipping, function(data) {
$scope.fetching = false;
$scope.quotes = data;
});
}, 1000);
// Watch the shipping fields - when enough done and user is done typing then get quote
$scope.$watch('shipping', function(newVal, oldVal) {
// I use this to play around with what fields I actually want before I do something
var fields = ['street', 'region', 'name', 'postal', 'country', 'city'];
var valid = true;
fields.forEach(function(field) {
if (!$scope.form[field].$valid) {
valid = false;
}
});
if (valid) fetch();
}, true);
My form fields are setup like this:
<input type="text" name="street ng-model="shipping.street" required>
<input type="text" name="name" ng-model="shipping.name" required>
Notice how I make them part of a "shipping" object - that allows me to watch the shipping fields independently of others such as billing.
Note that the above is for the extreme cases such as shipping fields. For simple things such as subscribing to a newsletter if they check a box then you don't need to use the above and can simply do an ng-click="spamMe();" call in your checkbox. That function (spamMe) would be in your controller and can then call your server etc...
var spamMe = function() {
// Grab the email field that might be at top - ideally check if it's filled in but you get the idea
var email = $scope.email;
$http.post('/api/spam', ....);
}
I'd apply a $scope.$watch on each of those variables to trigger a function that checks to see if all the fields for a given section are filled out, and if so, then submit it to the server as an ajax request.
Here's my attempt at writing this:
var shippingFields = ['address', 'city', 'state', 'zip'] // etc
function submitFieldsWhenComplete(section, fields) {
fieldValues = fields.forEach(function (field) {
return $scope[section][field]
});
if (fieldValues.every()) {
// We've got all the values, submit to the server
$http.post({
url: "/your/ajax/endpoint",
data: $scope.shipping
})
}
}
shippingFields.forEach(function(field) {
$scope.$watch(function() {
return $scope['shipping'][field]
}, function(val) {
submitFieldsWhenComplete('shipping', shippingFields);
});
});
I have two grids; I call them child and parent grid. When I add a new row(data) into the parent grid, I want to reload the parent grid. I was trying to edit it using the afteredit function in the code. If I uncomment out line number 2 in the alert, that works fine. But with out the alert, the newly added row is hidden. I don't understand what's going wrong in my code. Please can anyone tell me what to do after I add the new row in to my grid and how to reload the grid immediately?
this my afteredit function
afteredit : function (roweditor, changes, record, rowIndex)
{ //alert('alert me');
if (!roweditor.initialized) {
roweditor.initFields();
}
var fields = roweditor.items.items;
// Disable key fields if its not a new row
Ext.each(fields, function (field, i) {
field.setReadOnly(false);
field.removeClass('x-item-disabled');
});
this.grid.getSelectionModel().selectRow(0);
this.grid.getView().refresh();
},
xt.ux.grid.woerp =
{
configRowEditor:
{
saveText: "Save",
cancelText: "Cancel",
commitChangesText: WOERP.constants.gridCommitChanges,
errorText: 'Errors',
listeners:
{
beforeedit: WOERP.grid.handler.beforeedit,
validateedit: WOERP.grid.handler.validateedit,
canceledit: WOERP.grid.handler.canceledit,
afteredit: WOERP.grid.handler.afteredit,
aftershow: WOERP.grid.handler.aftershow,
move: WOERP.grid.handler.resize,
hide: function (p)
{
var mainBody = this.grid.getView().mainBody;
if (typeof mainBody != 'undefined')
{
var lastRow = Ext.fly(this.grid.getView().getRow(this.grid.getStore().getCount() - 1));
if (lastRow != null)
{
mainBody.setHeight(lastRow.getBottom() - mainBody.getTop(),
{
callback: function ()
{
mainBody.setHeight('auto');
}
});
}
}
},
afterlayout: WOERP.grid.handler.resize
}
},
AFAIK RowEditor is a plugin for GridPanel which changes underlying data which comes from store. Usually updates are also made by store. If you want to know when data is saved, you should attach event handler to store. Example:
grid.getStore().on('save', function(){ [...] });
Finally i found solution. When i add reload function in to the afteredit method that will be hide newly added row. So Grid reload After commit data in to that data grid store work well for me. Anyway thanks lot all the people who try to help
this my code look like
record.commit();
grid.getView().refresh();
I think there exist a Save button after editing grid.
So in the handler of Save you can catch the event
or using
Ext.getCmp('your_saveButtonId').on('click', function(component, e) {
// Here they will be checking for modified records and sending them to backend to save.
// So here also you can catch save event
}
I have a "Cancel" button on my page which should reverts all the changes I made back to the state it was loaded from server..
I guess I need to store an initial state of Backbonejs model and restore a current (changed) state back to initial.
What is the best way to achieve that?
Thank you
FWIW - i wrote a plugin to handle this automatically, specifically with the idea of "cancel" buttons in mind: http://github.com/derickbailey/backbone.memento
model.previousAttributes() returns all of the previous attributes, while model.changedAttributes() returns all the changed attributes, but with their new values (or false if nothing has changed). So you could combine them to write a cancelChanges method in your prototype :
var MyModel = Backbone.Model.extend({
cancelChanges: function() {
var changed = this.changedAttributes();
if(!changed)
return;
var keys = _.keys(changed);
var prev = _.pick(this.previousAttributes(), keys);
this.set(prev, {silent: true}); // "silent" is optional; prevents change event
},
});
I dont believe there's a single method call for returning a model to its unedited state.. but the unedited values are available individually through model.previous(attribute) and collectively via model.previousAttributes.
Here is what I came up with:
var RollbackEnabledModel = Backbone.Model.extend({
initialize: function() {
this._initAttributes = _.clone(this.attributes);
},
parse: function(data) {
this._initAttributes = _.clone(data);
return data;
},
rollback: function() {
this.set(this._initAttributes);
}
});
Take a look at NYTimes' backbone.trackit. It tracks multiple changes to the model instead of only the most recent change like model.changedAttributes() and model.previousAttributes(). From the README:
var model = new Backbone.Model({id:1, artist:'Samuel Beckett', 'work':'Molloy'});
model.startTracking();
model.set('work', 'Malone Dies');
console.log(model.unsavedAttributes()); // >> Object {work: "Malone Dies"}
model.set('period', 'Modernism');
console.log(model.unsavedAttributes()); // >> Object {work: "Malone Dies", period: "Modernism"}
model.save({}, {
success: function() {
console.log(model.unsavedAttributes()); // >> false
}
});
In addition, the library adds functionality to resetAttributes to
their original state since the last save, triggers an event when the
state of unsavedChanges is updated, and has options to opt into
prompting to confirm before routing to a new context.