Creation of a global, persistent object into Extjs - javascript

Good day all.
I'm into a big project that uses EXTjs (i guess it's 4.0), the project is huge and have several years behind.
I'm not into Extjs so I'm trying to learn what to do and how to do it, and my new task is to create a persistent, global object, available into the whole application in which I need to store some information that are used in different parts of the project (let's say for example that the user can set a particular property of this object to "true" while doing some actions and this "true" it will be used into another viewcontroller to enable some functions, things like this).
so, I've created a new file called userJsonMainModel.js :
Ext.define('Tac3.userJsonMainModel', {
extend: 'Ext.data.Model',
constructor: function() {
var userJsonMainModel = this;
userJsonMainModel.callParent(arguments);
userJsonMainModel.data.tmp = {};
},
testProperty:{foo:"bar"},
testMethod: function (){
console.log("testFunction called");
}
});
and in Application.js :
requires: [
...
'Tac.userJsonMainModel'
],
stores: ['Countries', 'Kpis', 'Dimensions'],
autoCreateViewport: false,
init: function() {
var controller = this
Ext.tip.QuickTipManager.init();
Ext.setGlyphFontFamily('FontAwesome');
var userJsonMainModel = controller.createUserJsonMainModel();
console.log("into init: ", this.userJsonMainModel.testProperty);
...
createUserJsonMainModel: function() {
var controller = this;
controller.userJsonMainModel = Ext.create('Tac3.userJsonMainModel', {
controller: controller
});
console.log("check if the jsonmainmodel exist ",controller.userJsonMainModel.testProperty);
},
this is actually working, now the second step is to access the same object from another view (or its viewcontroller), this is what I've done into a a viewController:
Ext.define('Tac3.view.udesign.UdesignController', {
extend: 'Ext.app.ViewController',
alias: 'controller.udesign',
init: function(view) {
...
console.log("into init: ", this.userJsonMainModel.testProperty);
}
and this is actually throwing a:
Uncaught TypeError: Cannot read property 'testProperty' of undefined
I was pretty sure the objects defined into application.js would be globally accessible, but I guess I'm wrong, or doing something in a wrong way.
since I've found quite no examples on this topic (which is probably because it is not a standard way to do this), I'd like to ask what I'm doing wrong?

Just define a class and require it in your application:
Ext.define('MyApp.Globals', {
singleton: true,
foo: 100,
bar: 'baz'
});

Related

How to override extJs Controller component

I have created some generic component which I am using in different product. Now here I have one window and window controller which is generic and I am overriding window class to make use of that in our product.
My Generic window.
Ext.define('App.win.Panel', {
extend: 'Ext.window.Window',
closeAction:'destroy',
maximizable:true,
hideToolbar:false,
requires: [
'App.MyWinCon.PanelController'
],
xtype: 'MyWin',
name:'MyWin',
controller: 'MyWinCon',
layout: {
type: 'border'
},
gridConfigs:{},
initComponent:function(){
var p=this;
p.items = [{
//items
}];
p.callParent(arguments);
}
});
And In my Product app I am using overriding like this way :
var Window1 = Ext.create('App.win.Panel', {
title: Windo,
modal:true,
height: '90%',
width: '95%',
parentGridObj:gridObj,
});
Window1.show();
There is no problem in that part. Window is coming.
Now in similar passion I have written controller in generic. I will show you small piece of code
Ext.define('App.MyWinCon.PanelController', {
extend: 'Ext.app.ViewController',
alias: 'controller.MyWinCon',
init: function(){
var p = this;
p.control({
#:{
beforeclose : function(this){
// SOme code
}
}
});
}
Now can anybody help me how to access this beforeclose or similar kind of methods in my app which is written in generic class.
Thanks for all the help.
You can't, or it's at least really really complicated; but there is a really really easy way with a minimum of refactoring:
Ext.define('App.MyWinCon.PanelController', {
extend: 'Ext.app.ViewController',
alias: 'controller.MyWinCon',
init: function(){
var p = this;
p.control({
#:{
beforeclose : p.beforeClose // just a reference to function below!
}
});
},
beforeClose: function(component){ // thou ought not use a variable named "this"!
// SOme code
}
Now you can access the function from your view:
view.getController().beforeClose()

Giving a single reference to multiple Backbone.Models

I have a Backbone.Model which looks something like:
var FooModel = Backbone.Model.extend({
defaults: {
details: '',
operatingSystem: ''
};
});
There are many instances of FooModel which are stored in a collection:
var FooCollection = Backbone.Collection.extend({
model: FooModel
});
FooModel's OperatingSystem is a property which only needs to be calculated once and is derived asynchronously. For example:
chrome.runtime.getPlatformInfo(function(platformInfo){
console.log("Operating System: ", platformInfo.os);
});
If I perform this logic at the FooModel level then I will need to perform the logic every time I instantiate a FooModel. So, I think that this operation should be performed at a higher level. However, it is bad practice to give properties to a Backbone.Collection.
As such, this leaves me thinking that I need a parent model:
var FooParentModel = Backbone.Model.extend({
defaults: {
platformInfo: '',
fooCollection: new FooCollection()
},
initialize: function() {
chrome.runtime.getPlatformInfo(function(platformInfo){
this.set('platformInfo', platformInfo);
}.bind(this));
},
// TODO: This will work incorrectly if ran before getPlatformInfo's callback
createFoo: function(){
this.get('fooCollection').create({
details: 'hello, world',
operatingSystem: this.get('platformDetails').os
});
}
});
This works and is semantically correct, but feels over-engineered. The extra layer of abstraction feels unwarranted.
Is this the appropriate way to go about giving a property to a model?
Although Backbone Collections may not have attributes, they may have properties (as well as any object) which you can use to store shared data.
var FooCollection = Backbone.Collection.extend({
model: FooModel
initialize: function() {
this.platformInfo = null; // shared data
chrome.runtime.getPlatformInfo(function(platformInfo){
this.platformInfo = platformInfo;
}.bind(this));
},
// wrapper to create a new model within the collection
createFoo: function(details) {
this.create({
details: details,
operatingSystem: this.platformInfo? this.platformInfo.os : ''
});
}});
});

How to access a field outside of rootProperty from itemTpl's XTemplate in Sencha Touch?

Say, we have the following in a store:
{
"document": {
"success": "true",
"totalAllocation": "40000000.00",
"fundAllocation": [
{
"fundName": "Zais Opportunity Ltd Class B",
"allocation": "10000000.00"
},
{
"fundName": "Metacapital Mortgage Opportunities Ltd",
"allocation": "10000000.00"
},
...
]
}
}
And what I'd like to do is something like this:
itemTpl: Ext.create('Ext.XTemplate',
'<div>',
'<span>{fundName}</span>',
'<span>{[this.getPercentage(values.allocation, parent.totalAllocation)]}%</span>',
'</div>',
{
getPercentage: function (allocation, totalAllocation) {
return Ext.Number.toFixed(allocation / totalAllocation, 2);
}
}
)
But, of course, this doesn't work since 'parent' in this scope is empty.
Any idea how to fetch the value of the totalAllocation field inside XTemplate's fundtion to display the percentage allocated to the current fund in a list item?
Workarounds are welcomed as well.
From your data code it looks like document is the store root because there is a success property underneath it. Assuming that is the case, you can use the store reader's rawData property to get a reference to the value before you create the template. Then you can simply use the referenced value in the getPercentage function.
Your code does not show where you are creating this itemTpl in your class so I am assuming that you are creating this itemTpl inside the initComponent of the view you are instantiating.
I have no idea what type of component you are trying to create here other than the fact that it has an itemTpl config property which could be any subclass of Ext.view.AbstractView.
So I will assume that you are trying to use this in a gridpanel's view because that is the most common subclass of Ext.view.AbstractView.
Here's some code examples.
Example 1:
Ext.define('YourApp.view.TemplateGrid', {
extend: 'Ext.grid.Panel',
// column configs etc...
initComponent: function() {
var me = this,
templateStore = Ext.getStore('TemplateStoreId'),
totalAllocation = templateStore.proxy.reader.rawData.totalAllocation;
me.viewConfig.itemTpl = Ext.create('Ext.XTemplate',
'<div>',
'<span>{fundName}</span>',
'<span>{[this.getPercentage(values.allocation)]}%</span>',
'</div>',
{
getPercentage: function (allocation) {
return Ext.Number.toFixed(allocation / totalAllocation, 2);
}
}
)
}
});
Example 1 wouldn't work if you want to be able to load the store again (after initialization), it also assumes that your view store is already loaded. Here's another example showing the component set-up to handle multiple loads of the store without being recreated, it also assumes that the store is not loaded at the time when you create the view:
Example 2
Ext.define('YourApp.view.TemplateGrid', { // or whatever you are calling it
extend: 'Ext.grid.Panel',
// column configs etc...
totalAllocation = 0, // add this as a view property
initComponent: function() {
var me = this,
templateStore = Ext.create('YourApp.store.TemplateStore');
templateStore.on('load', function() {
me.totalAllocation = templateStore.proxy.reader.rawData.totalAllocation;
}
me.viewConfig.itemTpl = Ext.create('Ext.XTemplate',
'<div>',
'<span>{fundName}</span>',
'<span>{[this.getPercentage(values.allocation)]}%</span>',
'</div>',
{
getPercentage: function (allocation) {
return Ext.Number.toFixed(allocation / me.totalAllocation, 2);
}
}
)
templateStore.load();
me.callParent(arguments);
}
});

Set listener for store events in a controller

I have a controller with a store, a model, and some views.
I need to listen for the beforesync and write event of the store in the controller, but I don't know how to set these listeners in the controllers control-function.
My store looks like this :
Ext.define('DT.store.UsersStore', {
extend : 'Ext.data.Store',
model : 'DT.model.User',
id : 'myStore'
autoSync : true,
proxy : {
type : 'ajax',
api : {
read : '/load_entries',
update : '/update_entry'
},
reader : {
type : 'json',
root : 'user',
successProperty : 'success'
}
}
});
Now I try to listen to the events in my controller :
...
init : function () {
this.control({
'myStore' : {
beforesync : this.doSomething,
write : this.doSomethingElse
}
});
},
...
My expected result is that the functions will be executed, when the events are fired.
But at this time nothing happens when they are fired.
How can I get this to work?
Your way is possible but it's not ideal, IMO. The better way is to use controllers's store getter. In your case the code would be something like this:
init : function () {
// every controller has getters for its stores.
// For store UsersStore getter would be getUsersStoreStore()
this.getUsersStoreStore().addListener('write',this.finishedLoading, this);
this.control({
// widgets event handlers
});
},
Here is an alternative syntax to Molecular Man's answer.
Instead of writing,
init : function () {
this.getUsersStoreStore().addListener('write',this.finishedLoading, this);
this.control({
// widgets event handlers
});
},
You can write
init : function () {
this.getUsersStoreStore().on({
write: this.finishedLoading,
scope: this
});
this.control({
// widgets event handlers
});
},
I think this alternative definition reads a little bit better.
I took this from an answer Izhaki gave me.
As for Extjs 4.2.1, your initial way of accessing the store listener would actually work, if you were using the 'storeId' instead of the id and the 'listen' function instead of 'control':
Ext.define('DT.store.UsersStore', {
extend : 'Ext.data.Store',
model : 'DT.model.User',
storeId : 'myStore'
....
init : function () {
this.listen({
store: {
'#myStore' : {
beforesync : this.doSomething,
...
Ext.define('Store', {
model: 'Model',
extend: 'Ext.data.Store',
listeners: {
'beforesync': function(){
App.getController('somecontroller').onBeforeSync();
}
}
});
App - your application object
The function onBeforeSync you can implement it in the controller ... this is the only way i could assign the event to the store and still implement the logic in the controll. I hope it helps
I solved it by myself.
I added the listener manually in the render-event of my Panel
Ext.getCmp('userPanel').down('gridpanel').getStore().addListener('write',this.finishedLoading, this);
Thank you for the help #nscrob.
Hope this addition will help someone out there to avoid spending some hours on debugging the library core:
Related to this practice of accessing Ext.data.Store instance inside Controller using the controller's getter method: e.g. for the "DT.store.UsersStore" above using this.getUsersStoreStore():
pay attention that if the store is already associated in a view(e.g. was declared as the store property for a "UsersGrid" Grid.panel.Panel widget definition) then this getter method will retrieve in fact another instance of the same Store class and not the instance used by the widget!
The reason is that adding the store in the constructor configuration object like this:
stores: ['UsersStore']
will in fact add a new store instance in Ext.data.StoreManager.map hash so - supposing that 'UsersStore' is the only Store object instantiated so far - the map keys now look like:
0: "ext-empty-store"
1: "UsersStore"
2: "myStore"
Now imagine you want to read some new data using your store'proxy and display this new data in the "UsersGrid" and you want to do all these when user clicks on something, so inside controller you will have a handler method for the user event with the code:
'user-selector' : {
click: function(){
var oStoreReference = this.getUsersStoreStore();
oStoreReference.load( {params:{} });
}
}
That call to get the reference will be translated internally in this.getStore('UsersStore') and will return a reference to the controller generated - 1: "UsersStore" - and not - 2: "myStore" - as one might expected. Furthermore, the load() call will load the UsersStore instance with the new Models and this will not be reflected in your grid view(because the grid is bound and listens to the events generated by "myStore" store instance).
So better access the store by its itemId using the general getStore method: this.getStore('storeItemId')
Why not just relay the store's events? For example:
this.getUsersGrid().relayEvents(this.getUsersStoreStore(), ['write'], 'store')
And then be able to
this.control('somegrid-selector': {storeWrite: function(){...}})
Just in case somebody stumbels over this.
ExtJS 7.3
Ext.define('DT.controller.UsersStore', {
extend: 'Ext.app.Controller',
listen: {
store: {
UsersStore: {
beforesync : 'doSomething',
write : 'doSomethingElse'
}
}
},
doSomething() {
console.log(arguments)
},
doSomethingElse() {
console.log(arguments)
}
});

Javascript/ExtJS: "Conditional Inheritance"?

We are using ExtJS for a webapplication. In that application, we use the standard Ext.form.ComboBox control when a simple dropdown is required, and the Ext.us.Andrie.Select control when we need a dropdown where you can select multiple values and/or clear the value. Creating either of these always requires a bunch of boilerplate code for the config options, so I wanted a class that reduced boilerplate code and while I was at it, I wanted this class to be able to produce either the simple dropdown, or the more advanced one, depending on a config option (multi: true or clearable: true), but this turned out to be a lot harder than expected.
This is the closest I came to a working result:
MyComboBox = (function() {
var singleDefaults = {
typeAhead: false,
triggerAction: 'all',
selectOnFocus: false,
allowBlank: true,
editable: false,
delay: 700
};
var multiDefaults = {
typeAhead: false,
triggerAction: 'all',
selectOnFocus: false,
allowBlank: true,
editable: false,
delay: 700
};
var constructor = function(config) {
if (config.multi || config.clearable) {
config = Ext.apply(this, config, multiDefaults);
Ext.apply(this, Ext.ux.Andrie.Select.prototype);
Ext.apply(this, Ext.ux.Andrie.Select(config));
Ext.ux.Andrie.Select.prototype.constructor.call(this, config);
} else {
config = Ext.apply(this, config, singleDefaults);
Ext.apply(this, Ext.form.ComboBox.prototype);
Ext.apply(this, Ext.form.ComboBox(config));
Ext.form.ComboBox.prototype.constructor.call(this, config);
}
};
return function(config) {
this.constructor = constructor;
this.constructor(config);
};
})();
Well, it doesn't crash, but it doesn't really work either. When set to behave like Ext.ux.Andrie.Select, it wants to load the store even when it's loaded, doesn't expand the dropdown unless you start typing in the field.
Another approach that was tried was something like:
MyComboBox = Ext.extend(Ext.form.ComboBox, {
constructor: function(config){
if (config.multi || config.clearable) {
Ext.form.ComboBox.prototype.constructor.call(this, config);
} else {
Ext.ux.Andrie.Select.prototype.constructor.call(this, config);
}
}
});
That doesn't work because the Andrie dropdown doesn't define a constructor function of its own so it ends up calling the constructor function of Ext.form.ComboBox, which it inherits from, which results in a normal dropdown, not the multiselect dropdown.
I suppose this is ExtJS specific, but if you have a framework agnostic approach to doing this, I can probably translate it to ExtJS.
When building medium to large scale JavaScript applications, I find it convenient to move all control creation/instantiation into a single class were each method is a factory for that control (i.e.: combo box, text area, buttons, tool tips, check boxes, etc). Since only your factory class has the knowledge of how to create controls, this has the added benefit of decoupling your view logic from control instantiation themselves (that is, of course, as long as the control constructors and interfaces stay the same).
WidgetFactory = {
comboBox: function(config) {
return config.multi || config.clearable
? this.comboBoxMultiple(config)
: this.comboBoxSingle(config)
},
comboBoxSingle: function(config) {
// ... boiler plate goes here ...
},
comboBoxMultiple: function(config) {
// ... boiler plate goes here ...
},
textArea : function(config) {},
textBox : function(config) {},
checkbox : function(config) {},
radioButton : function(config) {},
button : function(config) {},
slider : function(config) {},
colorPicker : function(config) {}
// etc
};
Decoupling control creation from the view allows you quickly and easily swap out on implementation of a control for another without having to hunt through the whole of your application to find/replace all instances of that control's creation boilerplate.
Edit:
I didn't mean to imply that this would replace the use of objects for creating/instantiating controls, but rather, to be used in addition to. Using the WidgetFactory as a single gateway to all controls allows more flexibility than just being able to change super classes. You can swap out the entire class without altering the original class, allowing you to have multiple, different implementations if necessary.
For example, if your single combo box is defined by some class, namespace.ui.combobox.single, then you can do
comboBoxSingle: function(config) {
return new namespace.ui.combobox.single(config);
},
However, if you need to suddenly use a different class, for testing perhaps, you can change it once in the factory
comboBoxSingle: function(config) {
return new namespace.ui.combobox.single2(config);
},
and easily switch back and forth without having to alter the actual widget classes.
Almost forgot about this question. I solved this and if anyone is wondering, here's how to do it:
var constructor = function(config) {
if (config && (config.multi || config.clearable)) {
config = Ext.apply(this, config, multiDefaults);
Ext.applyIf(this, Ext.ux.Andrie.Select.prototype);
Ext.ux.Andrie.Select.createDelegate(this)(config);
} else {
config = Ext.apply(this, config, singleDefaults);
Ext.applyIf(this, Ext.form.ComboBox.prototype);
Ext.form.ComboBox.createDelegate(this)(config);
}
};

Categories

Resources