A lot of code examples use Ext.apply when setting properties on a component in the initComponent method.
Example :
initComponent: function(){
Ext.apply(this, {
items: {
xtype: 'button'
}
})},
My question is, what is the difference in doing it that way, compared to doing it this way :
initComponent: function(){
this.items = {
xtype: 'button'
}
}
For me it seems more readable that way. But am I missing something that I get from Ext.apply?
Ext.apply() is used to simplify the copying of many properties from a source to a target object (most of the time the source and target objects have different sets of properties) and it can in addition be used to apply default values (third argument).
Note that it will not make deep clones! Meaning if you have a array or a object as property value it will apply the reference!
There is also a applyIf() which only copies properties that do not already exist in the target object. In some cases both implementations have also the benefit of dropping the reference of the copied object.
Note:
Your second way won't work because you are missing this.
initComponent: function(){
items = {
xtype: 'button'
}
}
wouldn't initialize anything, you mean
initComponent: function(){
this.items = {
xtype: 'button'
}
}
which does the same like your example using Ext.apply. But Ext.apply shows its power in more complex cases, e.g.
var x = {a: 1, c:3, e:5};
Ext.apply(x, {b:2, d:4, f:6});
console.log(x); // Object {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}
This is often used to overwrite default options of components with given init parameters.
Related
I've created a fiddle just to simulate my problem with class property not being reset on window destroy.
How to test:
Open fiddle, press OPEN button, ADD 3 panels, close ext window, press OPEN button again, and add a some more panels.
Panel numbers represent the length of the _panels array property in window.
Now to the problem.
As you can see panel NUMBER when adding new panels is not reset. So if you add 3 panels and close the window, reopen the window panels count shows 3 and then 4 and then 5 instead of 0 1 2 ...
My question is, why?
Fiddle example
Kind regards
Armando
EDIT : so one can see the solution
I ended fixing my application to work like this fiddle. I moved properties to constructor.
constructor: function() {
Ext.apply(this, {
width: 800,
height: 600,
layout: 'vbox',
_panels : []
});
this.callParent(arguments);
},
When you define
Ext.define('TestWindow', {
extend: 'Ext.window.Window',
_panel: []
});
The TestWindow definition class gets empty array property (no-primitive datatype). When you create an instance by var win = Ext.create('TestWindow'), the instance gets that property. However, when you set:
onDestroy: function() {
this._panels = [];
},
it sets empty array to property _panels of the instance win, not on the definition class TestWindow; TestWindow keeps the existing mutated _panel. And when next time you create new instance, it gets same _panel from class definition.
I understand you did it for demo purpose to show the problem. However, I prefer to let framework do all heavy-lifting (create and destroy etc):
Ext.define('TestWindow', {
extend: 'Ext.window.Window',
width: 800,
height: 600,
defaultListenerScope: true,
layout: 'vbox',
initComponent: function() {
this._panels = [];
this.callParent(arguments);
},
addPanel: function() {
console.log(this._panels.length);
var panels = this._panels;
panels.push(this.add({
xtype: 'panel',
title: 'Panel ' + panels.length,
height: 50,
width: '100%'
}));
},
tbar: [{
xtype : 'button',
text: 'add',
handler: 'addPanel'
}]
});
The simple answer for this is prototypal inheritance (see this MDN article). Basically your non-primitives will carry over to new instances because they exist on your prototype class, and because they're non-primitives, it's the same exact reference that's used. To fix this, I would recommend wrapping your _panels variable in the config block, like below, and encourage you to use the appropriate set/get methods, instead of accessing it directly:
config: {
_panels : []
}
A less than correct answer if the prototype behavior in the previous answer is "A feature/intentional bad code/ legacy code with unintended consequences"
Manually overwrite the prototype Value, following instantiation of the panel
I have a question when defining 1 panel.
Ex1:
Ext.define('AppTest.view.AppMain', {
extend: 'Ext.panel.Panel',
xFile: "File",
// Init
initComponent: function () {
Ext.apply(this, {
items: [
{
xtype: 'button',
action: 'file',
text: this.xFile // Using variable here
}
]
});
this.callParent();
}
});
Ex2:
Ext.define('AppTest.view.AppMain', {
extend: 'Ext.panel.Panel',
xFile: "File",
items: [
{
xtype: 'button',
action: 'file',
text: this.xFile // Using variable at here
}
]
});
When I run 2nd Example, only Example 1 create "File" is text of button, and Example 2 just creating button, but "File" is not text of button.
Please help me explain the difference between the two ways of define, and how to use Example 2 still using this.xFile.
It is because of javascript closures. In first example this keyword refers to the defined object. ( here is your class instance). In simple words, you should find the closest function wrapper (here initComponent).
But in your second example, this refers to the window object which it has not xFile property. So it returns null. For test put the following line at the top of the second example and see the result :
this.xFile = 'hello!';
Finally I strongly recommend you to read in dept about closures and prototypes in javascript before coding complex scripts.
I one project I see :
initComponent: function() {
var me = this;
Ext.apply(me, {
items: [
{
xtype: 'stockform'
}
]
});
me.callParent(arguments);
}
What's this means? Tell me please.
I know Ext.apply() is used to simplify the copying of many properties from a source to a target object
var x = {a: 1, c:3, e:5};
Ext.apply(x, {b:2, d:4, f:6});
console.log(x); // Object {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}
It for applying given properties for given object.
In your case your componentn will be extending with property items which has internal component with type stockform. xtype is type shortcat.
Somwthere in ExtJs library OR in your project exists component with defined xtype stockform. Defining xtype stockform for component goes via adding property alias: "widget.stockform".
These are two questions in one, in fact:
What Ext.apply does?
What is an xtype?
Re 1: Ext.apply takes (usually) 2 objects as arguments: target and source and it copies all properties from the source object to the target object.
Re 2: xtype is a short name for a component (class), an alias. See "What is an xtype ... and other types" article for a detailed explanation.
I got this template (default)
<span class="x-legend-item-marker {[values.disabled?'x-legend-inactive':'']}" style="background:{mark};"></span>{name}
that produce this :
I want to have the same template with every of it's functionnality. But, I need one more if-clause to it. I don't want an item to be 'legendarize' if it's value is 0.
Here is the complete code
{
xtype: 'container',
title: 'Chart',
iconCls: 'chart',
itemId: 'chart_Tab',
layout: {
type: 'fit'
},
items: [
{
xtype: 'polar',
itemId: 'pie',
colors: [
'#115fa6',
'#94ae0a',
'#a61120',
'#ff8809',
'#ffd13e',
'#a61187',
'#24ad9a',
'#7c7474',
'#a66111',
'#222222',
'#115ea6',
'#94cc0a',
'#b61120',
'#dd8809',
'#11d13e',
'#a68887',
'#94df9d',
'#7f74f4',
'#112341',
'#abcdef1'
],
store: 'relativedata',
series: [
{
type: 'pie',
label: {
textBaseline: 'middle',
textAlign: 'center',
font: '9px Helvetica'
},
labelField: 'strName',
labelOverflowPadding: 0,
xField: 'numValue'
}
],
interactions: [
{
type: 'rotate'
}
],
listeners: [
{
fn: function(element, eOpts) {
var relStore = Ext.getStore('relativedata');
var eleStore = Ext.getStore('element');
var relModel;
var eleModel;
relStore.removeAll();
//Convert to CO2 qty
for(var i = 0; i< eleStore.getCount();i++)
{
eleModel = eleStore.getAt(i);
relModel = Ext.create(APPNAME + '.model.RelativeElement');
relModel.set('strName',eleModel.get('strName'));
relModel.set('numValue', eleModel.get('numValue')*eleModel.getFactor());
relStore.add(relModel);
}
relStore.sync();
//Hide arrows-legend
this._series[0]._label.attr.hidden=true;
},
event: 'painted'
}
],
legend: {
xtype: 'legend',
docked: 'bottom',
itemId: 'pie_legend',
itemTpl: [
'<span class="x-legend-item-marker {[values.disabled?\'x-legend-inactive\':\'\']}" style="background:{mark};"></span>{name}'
],
maxItemCache: 100,
store: 'element'
}
}
]
}
I ask for help because i'm not that good with templates. I would not dare say I understand everything of the default one actually.
I'm back! Yet, nobody's calling me slim shaddy for that... Unluckily!
So, to answer your initial question, the template you need would be something like the following:
// Configuration of the chart legend
legend: {
// Finally, we can use the value field to customize our templates.
itemTpl: [
'<tpl if="value != 0">', // <= template condition
'<span class="x-legend-item-marker {[values.disabled?\'x-legend-inactive\':\'\']}" style="background:{mark};"></span>{name}',
'</tpl>'
]
// ...
}
Unfortunately, as I've said in my previous comment, quick debugger inspection shows that this value variable, or any equivalence, is not available at the time this template is applied.
Now I'm going to give you a detailed explanation about how I was able to overcome this vexation. In part because this is such an involved hack that you'd better know what you're doing if you decide to apply it, and in part because you'll learn a lot more by witnessing the fishing techniques than by being given the fish right away -- in this case, the fish is not available for retail anyway. And also in a large part, I must confess, because I like to be lyrical about things I've put some energy in, and it's late, and my defenses against self congratulation have gotten a bit weak...
So, looking at Ext.chart.Legend's code shows that there's nothing to be done there, it's just a somewhat lightweight extension of Ext.dataview.Dataview. As such it must have a store bounded to it, which, obviously (and unfortunately), is not the one bound to the chart to provide its data.
Another judicious breakpoint (in the Legend's setStore method) shows that this store comes from Ext.chart.AbstractChart, and in the code of this class we can see two things: a dedicated legend store is created in the constructor, and chart series implement a method to feed this store, namely provideLegendInfo.
We're getting closer to our goal. What we need to do is add a value field to the legend store, and have our serie provide the data for this field. Great!
The wise approach now would be to implement these modifications with the minimal amount of replication of Ext's code... But after having spent an inconsiderate amount of time trying to do that with no luck, I'll just settle for wildly overriding these two methods, and giving the advice to put a big bold warning to check that the code of these methods doesn't change with the next versions of Touch:
if (Ext.getVersion().isGreaterThan('2.2.1')) {
// Give yourself a big warning to check that the overridden methods' code
// bellow has not changed (see further comments).
}
With that out of the way, let's go to the point without any further consideration for future generations.
That is, first we add a value field to the legend store:
/**
* Adds a value field to legend store.
*/
Ext.define(null, {
override: 'Ext.chart.AbstractChart'
// Berk, what a lot of code replication :( Let's just hope that this method's code
// won't change in the future...
,constructor: function() {
var me = this;
me.itemListeners = {};
me.surfaceMap = {};
me.legendStore = new Ext.data.Store({
storeId: this.getId() + '-legendStore',
autoDestroy: true,
fields: [
'id', 'name', 'mark', 'disabled', 'series', 'index'
// Adding my value field
,'value'
]
});
me.suspendLayout();
// For whatever reason, AbstractChart doesn't want to call its superclass
// (Ext.draw.Component) constructor and, by using callSuper, skips directly to
// Ext.Container's one. So well... I respect, but I must do it old school since
// callSuper would go to Ext.draw.Component from here.
Ext.Container.prototype.constructor.apply(this, arguments);
// me.callSuper(arguments);
me.refreshLegendStore();
me.getLegendStore().on('updaterecord', 'onUpdateLegendStore', me);
me.resumeLayout();
}
}, function() {
// Post-create functions are not called for overrides in touch as they are
// in ExtJS? Hmm... That would have been the perfect place to issue a big
// warning in case the version has changed, but we'll live with it :(
});
And, second, we make our chart serie feed that value. From your code, I can deduce that you're working with a pie chart, so I'm only giving the code for that, as a matter of illustration... But, if you've followed until here, it should be trivial to implement it for other kind of series. Anyway, here's the code:
/**
* Overrides `provideLegendInfo` to add the value to the legend records.
*
* Here again, let us all cross our fingers very hard, hoping for Sencha's team to not decide
* to add their own extra fields too soon...
*/
Ext.define(null, {
override: 'Ext.chart.series.Pie'
,provideLegendInfo: function(target) {
var store = this.getStore();
if (store) {
var items = store.getData().items,
labelField = this.getLabelField(),
field = this.getField(),
hidden = this.getHidden();
for (var i = 0; i < items.length; i++) {
target.push({
name: labelField ? String(items[i].get(labelField)) : field + " " + i,
mark: this.getStyleByIndex(i).fillStyle || this.getStyleByIndex(i).strokeStyle || 'black',
disabled: hidden[i],
series: this.getId(),
index: i
// Providing actual data value to the legend record
,value: items[i].get(field)
});
}
}
}
});
Let's sum it up. We've got two overrides and a custom template. We could hope that we'd be done by now. But here's what we get:
So, the DataView is adding some markup of its own around the itemTpl's markup. Well, well, well... At this point, I'm tired of tracking Ext's internals and, fortunately (for once!), I envision a quick patch for this. So that is without an hesitation that I'm throwing this CSS rule in:
.x-legend-item:empty {
display: none;
}
And finally we're done. I guess my line of thought and code might be a little tricky to replicate, so let me provide you with a definitive proof that this all works.
In this demo, there is a "metric four" that has a value of 0.
{
'name': 'metric four',
'data': 0
}
But you won't see it. Because that was the point of all this, wasn't it?
I'm trying to set an array value in a backbone.js model initialize function. In the line that starts with 'this.set...' I get a 'unexpected string' error. Is it not possible to set array values this way?
Thanks!
var bGenericItem = Backbone.Model.extend({
defaults: {
attrArray: new Array({'item_id': '', 'type': '', 'name':''})
},
initialize: function(){
// Set the id to cid for now
this.set({ attrArray["item_id"]: this.cid });
}
});
What you're trying to do doesn't make any sense. Your defaults is an array which holds a single object:
defaults: {
attrArray: [
{ item_id: '', type: '', name: '' }
]
},
You'd use an array if you wanted to hold a list of attribute objects. But, if you had a list of attribute objects, which one's item_id would you expect attrArray['item_id'] to refer to? Are you assuming that attrArray will always be initialized to the default value and that no one would ever send an attrArray in as part of your model's initial data? If so, you'd want something more like this:
// Use a function so that each instance gets its own array,
// otherwise the default array will be attached to the prototype
// and shared by all instances.
defaults: function() {
return {
attrArray: [
{ item_id: '', type: '', name: '' }
]
};
},
initialize: function() {
// get will return a reference to the array (not a copy!) so
// we can modify it in-place.
this.get('attrArray')[0]['item_id'] = this.cid;
}
Note that you'll run into some issues with array attributes that require special handling:
get('attrArray') will return a reference to the array that is inside the model so modifying that return value will change the model.
Things like a = m.get('attrArray'); a.push({ ... }); m.set('attrArray', a) won't work the way you expect them to, the set won't notice that the array has changed (because it hasn't, a == a is true after all) so you won't get "change" events unless you clone the attrArray somewhere between get and set.
There are several problems with your code
1: The defaults setting is an object literal which means the value that you assign to it is set as soon as it's defined. You need to set your defaults to a function, instead of a literal value. This will ensure each model instance gets it's own copy of the default values, instead of sharing a copy across every model instance.
2: You should also not use new Array, just use an array literal syntax []. But you're not really using an array in this code, so I removed the array wrapper for now.
3: You can't access attrArray directly. You must get it from the model's attributes and then update it
var bGenericItem = Backbone.Model.extend({
defaults: function(){
return {
attrArray: {'item_id': '', 'type': '', 'name':''}
};
},
initialize: function(){
// Set the id to cid for now
var arr = this.get("attrArray");
arr["item_id"] = this.cid;
}
});