- * @markdown
*/
Ext.define('Ext.toolbar.Toolbar', {
extend: 'Ext.container.Container',
@@ -28265,24 +29187,23 @@ Ext.define('Ext.toolbar.Toolbar', {
],
alias: 'widget.toolbar',
alternateClassName: 'Ext.Toolbar',
-
+
isToolbar: true,
baseCls : Ext.baseCSSPrefix + 'toolbar',
ariaRole : 'toolbar',
-
+
defaultType: 'button',
-
+
/**
* @cfg {Boolean} vertical
* Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
- * (defaults to `false`)
*/
vertical: false,
/**
* @cfg {String/Object} layout
- * This class assigns a default layout (layout:'hbox '
).
- * Developers may override this configuration option if another layout
+ * This class assigns a default layout (`layout: 'hbox'`).
+ * Developers _may_ override this configuration option if another layout
* is required (the constructor must be passed a configuration object in this
* case instead of an array).
* See {@link Ext.container.Container#layout} for additional information.
@@ -28290,16 +29211,22 @@ Ext.define('Ext.toolbar.Toolbar', {
/**
* @cfg {Boolean} enableOverflow
- * Defaults to false. Configure true
to make the toolbar provide a button
- * which activates a dropdown Menu to show items which overflow the Toolbar's width.
+ * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
+ * items which overflow the Toolbar's width.
*/
enableOverflow: false,
+
+ /**
+ * @cfg {String} menuTriggerCls
+ * Configure the icon class of the overflow button.
+ */
+ menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
// private
trackMenus: true,
-
+
itemCls: Ext.baseCSSPrefix + 'toolbar-item',
-
+
initComponent: function() {
var me = this,
keys;
@@ -28308,7 +29235,7 @@ Ext.define('Ext.toolbar.Toolbar', {
if (!me.layout && me.enableOverflow) {
me.layout = { overflowHandler: 'Menu' };
}
-
+
if (me.dock === 'right' || me.dock === 'left') {
me.vertical = true;
}
@@ -28320,16 +29247,16 @@ Ext.define('Ext.toolbar.Toolbar', {
align: me.vertical ? 'stretchmax' : 'middle',
clearInnerCtOnLayout: true
});
-
+
if (me.vertical) {
me.addClsWithUI('vertical');
}
-
+
// @TODO: remove this hack and implement a more general solution
if (me.ui === 'footer') {
me.ignoreBorderManagement = true;
}
-
+
me.callParent();
/**
@@ -28339,7 +29266,7 @@ Ext.define('Ext.toolbar.Toolbar', {
* @param {Boolean} lastOverflow overflow state
*/
me.addEvents('overflowchange');
-
+
// Subscribe to Ext.FocusManager for key navigation
keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
Ext.FocusManager.subscribe(me, {
@@ -28347,24 +29274,38 @@ Ext.define('Ext.toolbar.Toolbar', {
});
},
+ getRefItems: function(deep) {
+ var me = this,
+ items = me.callParent(arguments),
+ layout = me.layout,
+ handler;
+
+ if (deep && me.enableOverflow) {
+ handler = layout.overflowHandler;
+ if (handler && handler.menu) {
+ items = items.concat(handler.menu.getRefItems(deep));
+ }
+ }
+ return items;
+ },
+
/**
- * Adds element(s) to the toolbar -- this function takes a variable number of
- * arguments of mixed type and adds them to the toolbar.
- * Note : See the notes within {@link Ext.container.Container#add}.
- * @param {Mixed} arg1 The following types of arguments are all valid:
- *
- * {@link Ext.button.Button} config: A valid button config object (equivalent to {@link #addButton})
- * HtmlElement: Any standard HTML element (equivalent to {@link #addElement})
- * Field: Any form field (equivalent to {@link #addField})
- * Item: Any subclass of {@link Ext.toolbar.Item} (equivalent to {@link #addItem})
- * String: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}, equivalent to {@link #addText}).
- * Note that there are a few special strings that are treated differently as explained next.
- * '-': Creates a separator element (equivalent to {@link #addSeparator})
- * ' ': Creates a spacer element (equivalent to {@link #addSpacer})
- * '->': Creates a fill element (equivalent to {@link #addFill})
- *
- * @param {Mixed} arg2
- * @param {Mixed} etc.
+ * Adds element(s) to the toolbar -- this function takes a variable number of
+ * arguments of mixed type and adds them to the toolbar.
+ *
+ * **Note**: See the notes within {@link Ext.container.Container#add}.
+ *
+ * @param {Object...} args The following types of arguments are all valid:
+ * - `{@link Ext.button.Button config}`: A valid button config object
+ * - `HtmlElement`: Any standard HTML element
+ * - `Field`: Any form field
+ * - `Item`: Any subclass of {@link Ext.toolbar.Item}
+ * - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
+ * Note that there are a few special strings that are treated differently as explained next.
+ * - `'-'`: Creates a separator element
+ * - `' '`: Creates a spacer element
+ * - `'->'`: Creates a fill element
+ *
* @method add
*/
@@ -28408,7 +29349,7 @@ Ext.define('Ext.toolbar.Toolbar', {
var method = remove ? 'mun' : 'mon',
me = this;
- me[method](item, 'menutriggerover', me.onButtonTriggerOver, me);
+ me[method](item, 'mouseover', me.onButtonOver, me);
me[method](item, 'menushow', me.onButtonMenuShow, me);
me[method](item, 'menuhide', me.onButtonMenuHide, me);
}
@@ -28424,12 +29365,12 @@ Ext.define('Ext.toolbar.Toolbar', {
if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
component.ui = component.ui + '-toolbar';
}
-
+
// Any separators needs to know if is vertical or not
if (component instanceof Ext.toolbar.Separator) {
component.setUI((this.vertical) ? 'vertical' : 'horizontal');
}
-
+
this.callParent(arguments);
},
@@ -28450,7 +29391,7 @@ Ext.define('Ext.toolbar.Toolbar', {
},
// private
- onButtonTriggerOver: function(btn){
+ onButtonOver: function(btn){
if (this.activeMenuBtn && this.activeMenuBtn != btn) {
this.activeMenuBtn.hideMenu();
btn.showMenu();
@@ -28477,8 +29418,8 @@ Ext.define('Ext.toolbar.Toolbar', {
/**
* @class Ext.panel.AbstractPanel
* @extends Ext.container.Container
- * A base class which provides methods common to Panel classes across the Sencha product range.
- * Please refer to sub class's documentation
+ * A base class which provides methods common to Panel classes across the Sencha product range.
+ * @private
*/
Ext.define('Ext.panel.AbstractPanel', {
@@ -28486,13 +29427,13 @@ Ext.define('Ext.panel.AbstractPanel', {
extend: 'Ext.container.Container',
- requires: ['Ext.util.MixedCollection', 'Ext.core.Element', 'Ext.toolbar.Toolbar'],
+ requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
/* End Definitions */
/**
- * @cfg {String} baseCls
- * The base CSS class to apply to this panel's element (defaults to 'x-panel'
).
+ * @cfg {String} [baseCls='x-panel']
+ * The base CSS class to apply to this panel's element.
*/
baseCls : Ext.baseCSSPrefix + 'panel',
@@ -28500,13 +29441,12 @@ Ext.define('Ext.panel.AbstractPanel', {
* @cfg {Number/String} bodyPadding
* A shortcut for setting a padding style on the body element. The value can either be
* a number to be applied to all sides, or a normal css string describing padding.
- * Defaults to undefined
.
*/
/**
* @cfg {Boolean} bodyBorder
- * A shortcut to add or remove the border on the body of a panel. This only applies to a panel which has the {@link #frame} configuration set to `true`.
- * Defaults to undefined
.
+ * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
+ * which has the {@link #frame} configuration set to `true`.
*/
/**
@@ -28524,7 +29464,7 @@ bodyStyle: {
*/
/**
- * @cfg {String/Array} bodyCls
+ * @cfg {String/String[]} bodyCls
* A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
* The following examples are all valid:
bodyCls: 'foo'
@@ -28542,7 +29482,7 @@ bodyCls: ['foo', 'bar']
* This object holds the default weights applied to dockedItems that have no weight. These start with a
* weight of 1, to allow negative weights to insert before top items and are odd numbers
* so that even weights can be used to get between different dock orders.
- *
+ *
* To make default docking order match border layout, do this:
*
Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
@@ -28567,11 +29507,17 @@ initComponent: function () {
*/
defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
- renderTpl: [' {bodyCls} {baseCls}-body-{ui} {parent.baseCls}-body-{parent.ui}-{.} " style="{bodyStyle}" >
'],
+ renderTpl: [
+ ' {bodyCls}',
+ ' {baseCls}-body-{ui}',
+ ' {parent.baseCls}-body-{parent.ui}-{.} ',
+ ' " style="{bodyStyle}" >',
+ '
'
+ ],
// TODO: Move code examples into product-specific files. The code snippet below is Touch only.
/**
- * @cfg {Object/Array} dockedItems
+ * @cfg {Object/Object[]} dockedItems
* A component or series of components to be added as docked items to this panel.
* The docked items can be docked to either the top, right, left or bottom of a panel.
* This is typically used for things like toolbars or tab bars:
@@ -28608,9 +29554,7 @@ var panel = new Ext.panel.Panel({
// 'deactivate'
);
- Ext.applyIf(me.renderSelectors, {
- body: '.' + me.baseCls + '-body'
- });
+ me.addChildEls('body');
//!frame
//!border
@@ -28651,7 +29595,7 @@ var panel = new Ext.panel.Panel({
/**
* Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
- * items, the dockedItems are searched and the matched component (if any) returned (see {@loink #getDockedComponent}). Note that docked
+ * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
* items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
* @param {String/Number} comp The component id, itemId or position to find
* @return {Ext.Component} The component (if found)
@@ -28675,7 +29619,7 @@ var panel = new Ext.panel.Panel({
var me = this,
bodyStyle = me.bodyStyle,
styles = [],
- Element = Ext.core.Element,
+ Element = Ext.Element,
prop;
if (Ext.isFunction(bodyStyle)) {
@@ -28738,7 +29682,7 @@ var panel = new Ext.panel.Panel({
/**
* Adds docked item(s) to the panel.
- * @param {Object/Array} component The Component or array of components to add. The components
+ * @param {Object/Object[]} component The Component or array of components to add. The components
* must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
* 'bottom', 'left').
* @param {Number} pos (optional) The index at which the Component will be added
@@ -28785,7 +29729,7 @@ var panel = new Ext.panel.Panel({
/**
* Inserts docked item(s) to the panel at the indicated position.
* @param {Number} pos The index at which the Component will be inserted
- * @param {Object/Array} component. The Component or array of components to add. The components
+ * @param {Object/Object[]} component. The Component or array of components to add. The components
* must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
* 'bottom', 'left').
*/
@@ -28820,10 +29764,9 @@ var panel = new Ext.panel.Panel({
if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
item.destroy();
- }
-
- if (hasLayout && !autoDestroy) {
- layout.afterRemove(item);
+ } else if (hasLayout) {
+ // not destroying, make any layout related removals
+ layout.afterRemove(item);
}
@@ -28839,7 +29782,7 @@ var panel = new Ext.panel.Panel({
/**
* Retrieve an array of all currently docked Components.
* @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
- * @return {Array} An array of components.
+ * @return {Ext.Component[]} An array of components.
*/
getDockedItems : function(cqSelector) {
var me = this,
@@ -29045,10 +29988,26 @@ Ext.define('Ext.panel.Header', {
indicateDrag : false,
weight : -1,
- renderTpl: [' {bodyCls} {parent.baseCls}-body-{parent.ui}-{.} " style="{bodyStyle}" >
'],
+ renderTpl: [
+ ' {bodyCls}',
+ '',
+ ' {parent.baseCls}-body-{parent.ui}-{.} ',
+ ' "',
+ ' style="{bodyStyle}" >
'],
+
+ /**
+ * @cfg {String} title
+ * The title text to display
+ */
+
+ /**
+ * @cfg {String} iconCls
+ * CSS class for icon in header. Used for displaying an icon to the left of a title.
+ */
initComponent: function() {
var me = this,
+ ruleStyle,
rule,
style,
titleTextEl,
@@ -29066,9 +30025,7 @@ Ext.define('Ext.panel.Header', {
me.addClsWithUI(me.orientation);
me.addClsWithUI(me.dock);
- Ext.applyIf(me.renderSelectors, {
- body: '.' + me.baseCls + '-body'
- });
+ me.addChildEls('body');
// Add Icon
if (!Ext.isEmpty(me.iconCls)) {
@@ -29103,7 +30060,11 @@ Ext.define('Ext.panel.Header', {
if (Ext.isArray(ui)) {
ui = ui[0];
}
- rule = Ext.util.CSS.getRule('.' + me.baseCls + '-text-' + ui);
+ ruleStyle = '.' + me.baseCls + '-text-' + ui;
+ if (Ext.scopeResetCSS) {
+ ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
+ }
+ rule = Ext.util.CSS.getRule(ruleStyle);
if (rule) {
style = rule.style;
}
@@ -29123,6 +30084,8 @@ Ext.define('Ext.panel.Header', {
autoSize: true,
margins: '5 0 0 0',
items: [ me.textConfig ],
+ // this is a bit of a cheat: we are not selecting an element of titleCmp
+ // but rather of titleCmp.items[0] (so we cannot use childEls)
renderSelectors: {
textEl: '.' + me.baseCls + '-text'
}
@@ -29139,15 +30102,16 @@ Ext.define('Ext.panel.Header', {
ariaRole : 'heading',
focusable: false,
flex : 1,
- renderTpl : ['{title} '],
+ cls: me.baseCls + '-text-container',
+ renderTpl : [
+ '{title} '
+ ],
renderData: {
title: me.title,
cls : me.baseCls,
ui : me.ui
},
- renderSelectors: {
- textEl: '.' + me.baseCls + '-text'
- }
+ childEls: ['textEl']
});
}
me.items.push(me.titleCmp);
@@ -29160,16 +30124,16 @@ Ext.define('Ext.panel.Header', {
initIconCmp: function() {
this.iconCmp = Ext.create('Ext.Component', {
focusable: false,
- renderTpl : [' '],
+ renderTpl : [
+ ' '
+ ],
renderData: {
blank : Ext.BLANK_IMAGE_URL,
cls : this.baseCls,
iconCls: this.iconCls,
orientation: this.orientation
},
- renderSelectors: {
- iconEl: '.' + this.baseCls + '-icon'
- },
+ childEls: ['iconEl'],
iconCls: this.iconCls
});
},
@@ -29226,7 +30190,7 @@ Ext.define('Ext.panel.Header', {
me.bodyCls = classes.join(' ');
}
}
-
+
return result;
},
@@ -29377,24 +30341,25 @@ Ext.define('Ext.panel.Header', {
},
/**
- * Sets the CSS class that provides the icon image for this panel. This method will replace any existing
- * icon class if one has already been set and fire the {@link #iconchange} event after completion.
+ * Sets the CSS class that provides the icon image for this header. This method will replace any existing
+ * icon class if one has already been set.
* @param {String} cls The new CSS class name
*/
setIconCls: function(cls) {
- this.iconCls = cls;
- if (!this.iconCmp) {
- this.initIconCmp();
- this.insert(0, this.iconCmp);
- }
- else {
- if (!cls || !cls.length) {
- this.iconCmp.destroy();
- }
- else {
- var iconCmp = this.iconCmp,
- el = iconCmp.iconEl;
-
+ var me = this,
+ isEmpty = !cls || !cls.length,
+ iconCmp = me.iconCmp,
+ el;
+
+ me.iconCls = cls;
+ if (!me.iconCmp && !isEmpty) {
+ me.initIconCmp();
+ me.insert(0, me.iconCmp);
+ } else if (iconCmp) {
+ if (isEmpty) {
+ me.iconCmp.destroy();
+ } else {
+ el = iconCmp.iconEl;
el.removeCls(iconCmp.iconCls);
el.addCls(cls);
iconCmp.iconCls = cls;
@@ -29430,8 +30395,8 @@ Ext.define('Ext.panel.Header', {
* @class Ext.fx.target.Element
* @extends Ext.fx.target.Target
*
- * This class represents a animation target for an {@link Ext.core.Element}. In general this class will not be
- * created directly, the {@link Ext.core.Element} will be passed to the animation and
+ * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
+ * created directly, the {@link Ext.Element} will be passed to the animation and
* and the appropriate target will be created.
*/
Ext.define('Ext.fx.target.Element', {
@@ -29516,7 +30481,7 @@ Ext.define('Ext.fx.target.Element', {
* @extends Ext.fx.target.Element
*
* This class represents a animation target for a {@link Ext.CompositeElement}. It allows
- * each {@link Ext.core.Element} in the group to be animated as a whole. In general this class will not be
+ * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
* created directly, the {@link Ext.CompositeElement} will be passed to the animation and
* and the appropriate target will be created.
*/
@@ -29853,60 +30818,60 @@ Ext.define('Ext.fx.Manager', {
/**
* @class Ext.fx.Animator
- * Animation instance
-
-This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
-Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
-at various points throughout the animation.
-
-__Using Keyframes__
-The {@link #keyframes} option is the most important part of specifying an animation when using this
-class. A key frame is a point in a particular animation. We represent this as a percentage of the
-total animation duration. At each key frame, we can specify the target values at that time. Note that
-you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
-event that fires after each key frame is reached.
-
-__Example Usage__
-In the example below, we modify the values of the element at each fifth throughout the animation.
-
- Ext.create('Ext.fx.Animator', {
- target: Ext.getBody().createChild({
- style: {
- width: '100px',
- height: '100px',
- 'background-color': 'red'
- }
- }),
- duration: 10000, // 10 seconds
- keyframes: {
- 0: {
- opacity: 1,
- backgroundColor: 'FF0000'
- },
- 20: {
- x: 30,
- opacity: 0.5
- },
- 40: {
- x: 130,
- backgroundColor: '0000FF'
- },
- 60: {
- y: 80,
- opacity: 0.3
- },
- 80: {
- width: 200,
- y: 200
- },
- 100: {
- opacity: 1,
- backgroundColor: '00FF00'
- }
- }
- });
-
- * @markdown
+ *
+ * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
+ * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
+ * at various points throughout the animation.
+ *
+ * ## Using Keyframes
+ *
+ * The {@link #keyframes} option is the most important part of specifying an animation when using this
+ * class. A key frame is a point in a particular animation. We represent this as a percentage of the
+ * total animation duration. At each key frame, we can specify the target values at that time. Note that
+ * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
+ * event that fires after each key frame is reached.
+ *
+ * ## Example
+ *
+ * In the example below, we modify the values of the element at each fifth throughout the animation.
+ *
+ * @example
+ * Ext.create('Ext.fx.Animator', {
+ * target: Ext.getBody().createChild({
+ * style: {
+ * width: '100px',
+ * height: '100px',
+ * 'background-color': 'red'
+ * }
+ * }),
+ * duration: 10000, // 10 seconds
+ * keyframes: {
+ * 0: {
+ * opacity: 1,
+ * backgroundColor: 'FF0000'
+ * },
+ * 20: {
+ * x: 30,
+ * opacity: 0.5
+ * },
+ * 40: {
+ * x: 130,
+ * backgroundColor: '0000FF'
+ * },
+ * 60: {
+ * y: 80,
+ * opacity: 0.3
+ * },
+ * 80: {
+ * width: 200,
+ * y: 200
+ * },
+ * 100: {
+ * opacity: 1,
+ * backgroundColor: '00FF00'
+ * }
+ * }
+ * });
*/
Ext.define('Ext.fx.Animator', {
@@ -29945,36 +30910,34 @@ Ext.define('Ext.fx.Animator', {
/**
* @cfg {String} easing
-
-This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
-speed over its duration.
-
-- backIn
-- backOut
-- bounceIn
-- bounceOut
-- ease
-- easeIn
-- easeOut
-- easeInOut
-- elasticIn
-- elasticOut
-- cubic-bezier(x1, y1, x2, y2)
-
-Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
-specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
-be in the range [0, 1] or the definition is invalid.
-
-[0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
-
- * @markdown
+ *
+ * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
+ * speed over its duration.
+ *
+ * - backIn
+ * - backOut
+ * - bounceIn
+ * - bounceOut
+ * - ease
+ * - easeIn
+ * - easeOut
+ * - easeInOut
+ * - elasticIn
+ * - elasticOut
+ * - cubic-bezier(x1, y1, x2, y2)
+ *
+ * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
+ * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
+ * be in the range [0, 1] or the definition is invalid.
+ *
+ * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
*/
easing: 'ease',
/**
* Flag to determine if the animation has started
* @property running
- * @type boolean
+ * @type Boolean
*/
running: false,
@@ -29982,7 +30945,7 @@ be in the range [0, 1] or the definition is invalid.
* Flag to determine if the animation is paused. Only set this to true if you need to
* keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
* @property paused
- * @type boolean
+ * @type Boolean
*/
paused: false,
@@ -30000,7 +30963,7 @@ be in the range [0, 1] or the definition is invalid.
/**
* Current iteration the animation is running.
* @property currentIteration
- * @type int
+ * @type Number
*/
currentIteration: 0,
@@ -30017,7 +30980,7 @@ be in the range [0, 1] or the definition is invalid.
animKeyFramesRE: /^(from|to|\d+%?)$/,
/**
- * @cfg {Ext.fx.target} target
+ * @cfg {Ext.fx.target.Target} target
* The Ext.fx.target to apply the animation to. If not specified during initialization, this can be passed to the applyAnimator
* method to apply the same animation to many targets.
*/
@@ -30138,7 +31101,7 @@ keyframes : {
* Applies animation to the Ext.fx.target
* @private
* @param target
- * @type string/object
+ * @type String/Object
*/
applyAnimator: function(target) {
var me = this,
@@ -30184,7 +31147,7 @@ keyframes : {
}
},
- /*
+ /**
* @private
* Fires beforeanimate and sets the running flag.
*/
@@ -30216,7 +31179,7 @@ keyframes : {
}
},
- /*
+ /**
* @private
* Perform lastFrame cleanup and handle iterations
* @returns a hash of the new attributes.
@@ -30240,7 +31203,7 @@ keyframes : {
}
},
- /*
+ /**
* Fire afteranimate event and end the animation. Usually called automatically when the
* animation reaches its final frame, but can also be called manually to pre-emptively
* stop and destroy the running animation.
@@ -30252,31 +31215,30 @@ keyframes : {
});
/**
* @class Ext.fx.Easing
- *
-This class contains a series of function definitions used to modify values during an animation.
-They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
-speed over its duration. The following options are available:
-
-- linear The default easing type
-- backIn
-- backOut
-- bounceIn
-- bounceOut
-- ease
-- easeIn
-- easeOut
-- easeInOut
-- elasticIn
-- elasticOut
-- cubic-bezier(x1, y1, x2, y2)
-
-Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
-specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
-be in the range [0, 1] or the definition is invalid.
-
-[0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
-
- * @markdown
+ *
+ * This class contains a series of function definitions used to modify values during an animation.
+ * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
+ * speed over its duration. The following options are available:
+ *
+ * - linear The default easing type
+ * - backIn
+ * - backOut
+ * - bounceIn
+ * - bounceOut
+ * - ease
+ * - easeIn
+ * - easeOut
+ * - easeInOut
+ * - elasticIn
+ * - elasticOut
+ * - cubic-bezier(x1, y1, x2, y2)
+ *
+ * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
+ * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
+ * be in the range [0, 1] or the definition is invalid.
+ *
+ * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
+ *
* @singleton
*/
Ext.ns('Ext.fx');
@@ -31170,12 +32132,12 @@ Ext.define('Ext.draw.Draw', {
* @param {Number} nextX X coordinate of the next point in the path
* @param {Number} nextY Y coordinate of the next point in the path
* @param {Number} value A value to control the smoothness of the curve; this is used to
- * divide the distance between points, so a value of 2 corresponds to
- * half the distance between points (a very smooth line) while higher values
- * result in less smooth curves. Defaults to 4.
+ * divide the distance between points, so a value of 2 corresponds to
+ * half the distance between points (a very smooth line) while higher values
+ * result in less smooth curves. Defaults to 4.
* @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
- * are the control point for the curve toward the previous path point, and
- * x2 and y2 are the control point for the curve toward the next path point.
+ * are the control point for the curve toward the previous path point, and
+ * x2 and y2 are the control point for the curve toward the next path point.
*/
getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
value = value || 4;
@@ -31314,7 +32276,25 @@ Ext.define('Ext.draw.Draw', {
};
},
+ /**
+ * A utility method to deduce an appropriate tick configuration for the data set of given
+ * feature.
+ *
+ * @param {Number/Date} from The minimum value in the data
+ * @param {Number/Date} to The maximum value in the data
+ * @param {Number} stepsMax The maximum number of ticks
+ * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
+ * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
+ * @return {Number} return.from The result start value, which may be lower than the original start value
+ * @return {Number} return.to The result end value, which may be higher than the original end value
+ * @return {Number} return.power The calculate power.
+ * @return {Number} return.step The value size of each step
+ * @return {Number} return.steps The number of steps.
+ */
snapEnds: function (from, to, stepsMax) {
+ if (Ext.isDate(from)) {
+ return this.snapEndsByDate(from, to, stepsMax);
+ }
var step = (to - from) / stepsMax,
level = Math.floor(Math.log(step) / Math.LN10) + 1,
m = Math.pow(10, level),
@@ -31352,6 +32332,119 @@ Ext.define('Ext.draw.Draw', {
};
},
+ /**
+ * A utility method to deduce an appropriate tick configuration for the data set of given
+ * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
+ *
+ * @param {Date} from The minimum value in the data
+ * @param {Date} to The maximum value in the data
+ * @param {Number} stepsMax The maximum number of ticks
+ * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
+ * and will not be adjusted
+ * @return {Object} The calculated step and ends info; properties are:
+ * @return {Date} return.from The result start value, which may be lower than the original start value
+ * @return {Date} return.to The result end value, which may be higher than the original end value
+ * @return {Number} return.step The value size of each step
+ * @return {Number} return.steps The number of steps.
+ * NOTE: the steps may not divide the from/to range perfectly evenly;
+ * there may be a smaller distance between the last step and the end value than between prior
+ * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
+ * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
+ * `from` to find the correct point for each tick.
+ */
+ snapEndsByDate: function (from, to, stepsMax, lockEnds) {
+ var selectedStep = false, scales = [
+ [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
+ [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
+ [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
+ [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
+ [Ext.Date.DAY, [1, 2, 3, 7, 14]],
+ [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
+ ], j, yearDiff;
+
+ // Find the most desirable scale
+ Ext.each(scales, function(scale, i) {
+ for (j = 0; j < scale[1].length; j++) {
+ if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
+ selectedStep = [scale[0], scale[1][j]];
+ return false;
+ }
+ }
+ });
+ if (!selectedStep) {
+ yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
+ selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
+ }
+ return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
+ },
+
+
+ /**
+ * A utility method to deduce an appropriate tick configuration for the data set of given
+ * feature and specific step size.
+ * @param {Date} from The minimum value in the data
+ * @param {Date} to The maximum value in the data
+ * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
+ * The second one is the number of units for the step (1, 2, etc.).
+ * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
+ * and will not be adjusted
+ * @return {Object} See the return value of {@link #snapEndsByDate}.
+ */
+ snapEndsByDateAndStep: function(from, to, step, lockEnds) {
+ var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
+ from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
+ steps = 0, testFrom, testTo;
+ if (lockEnds) {
+ testFrom = from;
+ } else {
+ switch (step[0]) {
+ case Ext.Date.MILLI:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
+ fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
+ break;
+ case Ext.Date.SECOND:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
+ fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
+ break;
+ case Ext.Date.MINUTE:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
+ Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
+ break;
+ case Ext.Date.HOUR:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
+ Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
+ break;
+ case Ext.Date.DAY:
+ testFrom = new Date(fromStat[0], fromStat[1],
+ Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
+ break;
+ case Ext.Date.MONTH:
+ testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
+ break;
+ default: // Ext.Date.YEAR
+ testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
+ break;
+ }
+ }
+
+ testTo = testFrom;
+ // TODO(zhangbei) : We can do it better somehow...
+ while (testTo < to) {
+ testTo = Ext.Date.add(testTo, step[0], step[1]);
+ steps++;
+ }
+
+ if (lockEnds) {
+ testTo = to;
+ }
+ return {
+ from : +testFrom,
+ to : +testTo,
+ step : (testTo - testFrom) / steps,
+ steps : steps
+ };
+ },
+
sorter: function (a, b) {
return a.offset - b.offset;
},
@@ -31432,6 +32525,7 @@ Ext.define('Ext.draw.Draw', {
}
});
+
/**
* @class Ext.fx.PropertyHandler
* @ignore
@@ -31766,36 +32860,37 @@ Ext.define('Ext.fx.PropertyHandler', {
});
/**
* @class Ext.fx.Anim
- *
+ *
* This class manages animation for a specific {@link #target}. The animation allows
* animation of various properties on the target, such as size, position, color and others.
- *
+ *
* ## Starting Conditions
* The starting conditions for the animation are provided by the {@link #from} configuration.
* Any/all of the properties in the {@link #from} configuration can be specified. If a particular
* property is not defined, the starting value for that property will be read directly from the target.
- *
+ *
* ## End Conditions
* The ending conditions for the animation are provided by the {@link #to} configuration. These mark
* the final values once the animations has finished. The values in the {@link #from} can mirror
* those in the {@link #to} configuration to provide a starting point.
- *
+ *
* ## Other Options
* - {@link #duration}: Specifies the time period of the animation.
* - {@link #easing}: Specifies the easing of the animation.
* - {@link #iterations}: Allows the animation to repeat a number of times.
* - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
- *
+ *
* ## Example Code
- *
+ *
+ * @example
* var myComponent = Ext.create('Ext.Component', {
* renderTo: document.body,
* width: 200,
* height: 200,
* style: 'border: 1px solid red;'
* });
- *
- * new Ext.fx.Anim({
+ *
+ * Ext.create('Ext.fx.Anim', {
* target: myComponent,
* duration: 1000,
* from: {
@@ -31820,6 +32915,17 @@ Ext.define('Ext.fx.Anim', {
/* End Definitions */
isAnimation: true,
+
+ /**
+ * @cfg {Function} callback
+ * A function to be run after the animation has completed.
+ */
+
+ /**
+ * @cfg {Function} scope
+ * The scope that the {@link #callback} function will be called with
+ */
+
/**
* @cfg {Number} duration
* Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
@@ -31845,7 +32951,7 @@ Ext.define('Ext.fx.Anim', {
/**
* @cfg {String} easing
This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
-speed over its duration.
+speed over its duration.
-backIn
-backOut
@@ -31914,7 +33020,7 @@ keyframes : {
/**
* Flag to determine if the animation has started
* @property running
- * @type boolean
+ * @type Boolean
*/
running: false,
@@ -31922,13 +33028,13 @@ keyframes : {
* Flag to determine if the animation is paused. Only set this to true if you need to
* keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
* @property paused
- * @type boolean
+ * @type Boolean
*/
paused: false,
/**
* Number of times to execute the animation. Defaults to 1.
- * @cfg {int} iterations
+ * @cfg {Number} iterations
*/
iterations: 1,
@@ -31942,7 +33048,7 @@ keyframes : {
/**
* Current iteration the animation is running.
* @property currentIteration
- * @type int
+ * @type Number
*/
currentIteration: 0,
@@ -31995,7 +33101,9 @@ from : {
// @private
constructor: function(config) {
- var me = this;
+ var me = this,
+ curve;
+
config = config || {};
// If keyframes are passed, they really want an Animator instead.
if (config.keyframes) {
@@ -32015,8 +33123,8 @@ from : {
if (!me.easingFn) {
me.easingFn = String(me.easing).match(me.bezierRE);
if (me.easingFn && me.easingFn.length == 5) {
- var curve = me.easingFn;
- me.easingFn = Ext.fx.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
+ curve = me.easingFn;
+ me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
}
}
me.id = Ext.id(null, 'ext-anim-');
@@ -32058,7 +33166,7 @@ from : {
return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
},
- /*
+ /**
* @private
* Set up the initial currentAttrs hash.
*/
@@ -32092,7 +33200,7 @@ from : {
me.currentAttrs = out;
},
- /*
+ /**
* @private
* Fires beforeanimate and sets the running flag.
*/
@@ -32126,7 +33234,7 @@ from : {
}
},
- /*
+ /**
* @private
* Calculate attribute value at the passed timestamp.
* @returns a hash of the new attributes.
@@ -32158,7 +33266,7 @@ from : {
return ret;
},
- /*
+ /**
* @private
* Perform lastFrame cleanup and handle iterations
* @returns a hash of the new attributes.
@@ -32185,7 +33293,7 @@ from : {
}
},
- /*
+ /**
* Fire afteranimate event and end the animation. Usually called automatically when the
* animation reaches its final frame, but can also be called manually to pre-emptively
* stop and destroy the running animation.
@@ -32251,14 +33359,14 @@ Ext.enableFx = true;
*/
Ext.define('Ext.dd.DragDrop', {
requires: ['Ext.dd.DragDropManager'],
-
+
/**
* Creates new DragDrop.
* @param {String} id of the element that is linked to this instance
* @param {String} sGroup the group of related DragDrop objects
- * @param {object} config an object containing configurable attributes.
+ * @param {Object} config an object containing configurable attributes.
* Valid properties for DragDrop:
- *
+ *
* - padding
* - isTarget
* - maintainOffset
@@ -32269,7 +33377,7 @@ Ext.define('Ext.dd.DragDrop', {
this.init(id, sGroup, config);
}
},
-
+
/**
* Set to false to enable a DragDrop object to fire drag events while dragging
* over its own Element. Defaults to true - DragDrop objects do not by default
@@ -32291,7 +33399,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Configuration attributes passed into the constructor
* @property config
- * @type object
+ * @type Object
*/
config: null,
@@ -32318,7 +33426,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* An object who's property names identify HTML tags to be considered invalid as drag handles.
- * A non-null property value identifies the tag as invalid. Defaults to the
+ * A non-null property value identifies the tag as invalid. Defaults to the
* following value which prevents drag operations from being initiated by <a> elements:
{
A: "A"
@@ -32342,8 +33450,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* An Array of CSS class names for elements to be considered in valid as drag handles.
- * @property invalidHandleClasses
- * @type Array
+ * @property {String[]} invalidHandleClasses
*/
invalidHandleClasses: null,
@@ -32351,7 +33458,7 @@ Ext.define('Ext.dd.DragDrop', {
* The linked element's absolute X position at the time the drag was
* started
* @property startPageX
- * @type int
+ * @type Number
* @private
*/
startPageX: 0,
@@ -32360,7 +33467,7 @@ Ext.define('Ext.dd.DragDrop', {
* The linked element's absolute X position at the time the drag was
* started
* @property startPageY
- * @type int
+ * @type Number
* @private
*/
startPageY: 0,
@@ -32371,7 +33478,7 @@ Ext.define('Ext.dd.DragDrop', {
* DragDrop object in the same group. This lets us define multiple
* groups using a single DragDrop subclass if we want.
* @property groups
- * @type object An object in the format {'group1':true, 'group2':true}
+ * @type Object An object in the format {'group1':true, 'group2':true}
*/
groups: null,
@@ -32379,7 +33486,7 @@ Ext.define('Ext.dd.DragDrop', {
* Individual drag/drop instances can be locked. This will prevent
* onmousedown start drag.
* @property locked
- * @type boolean
+ * @type Boolean
* @private
*/
locked: false,
@@ -32395,7 +33502,7 @@ Ext.define('Ext.dd.DragDrop', {
* When set to true, other DD objects in cooperating DDGroups do not receive
* notification events when this DD object is dragged over them. Defaults to false.
* @property moveOnly
- * @type boolean
+ * @type Boolean
*/
moveOnly: false,
@@ -32410,7 +33517,7 @@ Ext.define('Ext.dd.DragDrop', {
* By default, all instances can be a drop target. This can be disabled by
* setting isTarget to false.
* @property isTarget
- * @type boolean
+ * @type Boolean
*/
isTarget: true,
@@ -32418,7 +33525,7 @@ Ext.define('Ext.dd.DragDrop', {
* The padding configured for this drag and drop object for calculating
* the drop zone intersection with this object.
* An array containing the 4 padding values: [top, right, bottom, left]
- * @property {[int]} padding
+ * @property {Number[]} padding
*/
padding: null,
@@ -32439,7 +33546,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Set to true when horizontal contraints are applied
* @property constrainX
- * @type boolean
+ * @type Boolean
* @private
*/
constrainX: false,
@@ -32447,7 +33554,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Set to true when vertical contraints are applied
* @property constrainY
- * @type boolean
+ * @type Boolean
* @private
*/
constrainY: false,
@@ -32455,7 +33562,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* The left constraint
* @property minX
- * @type int
+ * @type Number
* @private
*/
minX: 0,
@@ -32463,7 +33570,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* The right constraint
* @property maxX
- * @type int
+ * @type Number
* @private
*/
maxX: 0,
@@ -32471,7 +33578,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* The up constraint
* @property minY
- * @type int
+ * @type Number
* @private
*/
minY: 0,
@@ -32479,7 +33586,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* The down constraint
* @property maxY
- * @type int
+ * @type Number
* @private
*/
maxY: 0,
@@ -32490,7 +33597,7 @@ Ext.define('Ext.dd.DragDrop', {
* when the page changes
*
* @property maintainOffset
- * @type boolean
+ * @type Boolean
*/
maintainOffset: false,
@@ -32498,7 +33605,7 @@ Ext.define('Ext.dd.DragDrop', {
* Array of pixel locations the element will snap to if we specified a
* horizontal graduation/interval. This array is generated automatically
* when you define a tick interval.
- * @property {[int]} xTicks
+ * @property {Number[]} xTicks
*/
xTicks: null,
@@ -32506,7 +33613,7 @@ Ext.define('Ext.dd.DragDrop', {
* Array of pixel locations the element will snap to if we specified a
* vertical graduation/interval. This array is generated automatically
* when you define a tick interval.
- * @property {[int]} yTicks
+ * @property {Number[]} yTicks
*/
yTicks: null,
@@ -32516,14 +33623,14 @@ Ext.define('Ext.dd.DragDrop', {
* allow drag and drop to start with any mouse click that is propogated
* by the browser
* @property primaryButtonOnly
- * @type boolean
+ * @type Boolean
*/
primaryButtonOnly: true,
/**
* The available property is false until the linked dom element is accessible.
* @property available
- * @type boolean
+ * @type Boolean
*/
available: false,
@@ -32535,7 +33642,7 @@ Ext.define('Ext.dd.DragDrop', {
* if outer handles are defined. Defaults to false.
*
* @property hasOuterHandles
- * @type boolean
+ * @type Boolean
*/
hasOuterHandles: false,
@@ -32548,8 +33655,8 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Abstract method called after a drag/drop object is clicked
* and the drag or mousedown time thresholds have beeen met.
- * @param {int} X click location
- * @param {int} Y click location
+ * @param {Number} X click location
+ * @param {Number} Y click location
*/
startDrag: function(x, y) { /* override this */ },
@@ -32570,7 +33677,7 @@ Ext.define('Ext.dd.DragDrop', {
* Abstract method called when this element fist begins hovering over
* another DragDrop obj
* @param {Event} e the mousemove event
- * @param {String/[DragDrop]} id In POINT mode, the element
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
* id this is hovering over. In INTERSECT mode, an array of one or more
* dragdrop items being hovered over.
*/
@@ -32586,7 +33693,7 @@ Ext.define('Ext.dd.DragDrop', {
* Abstract method called when this element is hovering over another
* DragDrop obj
* @param {Event} e the mousemove event
- * @param {String|DragDrop[]} id In POINT mode, the element
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
* id this is hovering over. In INTERSECT mode, an array of dd items
* being hovered over.
*/
@@ -32601,7 +33708,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Abstract method called when we are no longer hovering over an element
* @param {Event} e the mousemove event
- * @param {String/[DragDrop]} id In POINT mode, the element
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
* id this was hovering over. In INTERSECT mode, an array of dd items
* that the mouse is no longer over.
*/
@@ -32617,7 +33724,7 @@ Ext.define('Ext.dd.DragDrop', {
* Abstract method called when this item is dropped on another DragDrop
* obj
* @param {Event} e the mouseup event
- * @param {String/[DragDrop]} id In POINT mode, the element
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
* id this was dropped on. In INTERSECT mode, an array of dd items this
* was dropped on.
*/
@@ -32669,8 +33776,8 @@ Ext.define('Ext.dd.DragDrop', {
},
/**
- * Provides default constraint padding to "constrainTo" elements (defaults to `{left:0, right:0, top:0, bottom:0}`).
- * @type Object
+ * @property {Object} defaultPadding
+ * Provides default constraint padding to "constrainTo" elements.
*/
defaultPadding: {
left: 0,
@@ -32690,7 +33797,7 @@ Ext.define('Ext.dd.DragDrop', {
* this.constrainTo("parent-id");
* };
*
- * Or you can initalize it using the {@link Ext.core.Element} object:
+ * Or you can initalize it using the {@link Ext.Element} object:
*
* Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
* startDrag : function(){
@@ -32698,7 +33805,7 @@ Ext.define('Ext.dd.DragDrop', {
* }
* });
*
- * @param {Mixed} constrainTo The element to constrain to.
+ * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
* @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
* and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
* an object containing the sides to pad. For example: `{right:10, bottom:10}`
@@ -32712,10 +33819,10 @@ Ext.define('Ext.dd.DragDrop', {
var b = Ext.get(this.getEl()).getBox(),
ce = Ext.get(constrainTo),
s = ce.getScroll(),
- c,
+ c,
cd = ce.dom;
if(cd == document.body){
- c = { x: s.left, y: s.top, width: Ext.core.Element.getViewWidth(), height: Ext.core.Element.getViewHeight()};
+ c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
}else{
var xy = ce.getXY();
c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
@@ -32850,10 +33957,10 @@ Ext.define('Ext.dd.DragDrop', {
* Supports css-style shorthand; if only one parameter is passed, all sides
* will have that padding, and if only two are passed, the top and bottom
* will have the first param, the left and right the second.
- * @param {int} iTop Top pad
- * @param {int} iRight Right pad
- * @param {int} iBot Bot pad
- * @param {int} iLeft Left pad
+ * @param {Number} iTop Top pad
+ * @param {Number} iRight Right pad
+ * @param {Number} iBot Bot pad
+ * @param {Number} iLeft Left pad
*/
setPadding: function(iTop, iRight, iBot, iLeft) {
// this.padding = [iLeft, iRight, iTop, iBot];
@@ -32868,8 +33975,8 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Stores the initial placement of the linked element.
- * @param {int} diffX the X offset, default 0
- * @param {int} diffY the Y offset, default 0
+ * @param {Number} diffX the X offset, default 0
+ * @param {Number} diffY the Y offset, default 0
*/
setInitPosition: function(diffX, diffY) {
var el = this.getEl();
@@ -32881,7 +33988,7 @@ Ext.define('Ext.dd.DragDrop', {
var dx = diffX || 0;
var dy = diffY || 0;
- var p = Ext.core.Element.getXY( el );
+ var p = Ext.Element.getXY( el );
this.initPageX = p[0] - dx;
this.initPageY = p[1] - dy;
@@ -32899,7 +34006,7 @@ Ext.define('Ext.dd.DragDrop', {
* @private
*/
setStartPosition: function(pos) {
- var p = pos || Ext.core.Element.getXY( this.getEl() );
+ var p = pos || Ext.Element.getXY( this.getEl() );
this.deltaSetXY = null;
this.startPageX = p[0];
@@ -33191,10 +34298,10 @@ Ext.define('Ext.dd.DragDrop', {
* By default, the element can be dragged any place on the screen. Use
* this method to limit the horizontal travel of the element. Pass in
* 0,0 for the parameters if you want to lock the drag to the y axis.
- * @param {int} iLeft the number of pixels the element can move to the left
- * @param {int} iRight the number of pixels the element can move to the
+ * @param {Number} iLeft the number of pixels the element can move to the left
+ * @param {Number} iRight the number of pixels the element can move to the
* right
- * @param {int} iTickSize optional parameter for specifying that the
+ * @param {Number} iTickSize (optional) parameter for specifying that the
* element should move iTickSize pixels at a time.
*/
setXConstraint: function(iLeft, iRight, iTickSize) {
@@ -33232,9 +34339,9 @@ Ext.define('Ext.dd.DragDrop', {
* By default, the element can be dragged any place on the screen. Set
* this to limit the vertical travel of the element. Pass in 0,0 for the
* parameters if you want to lock the drag to the x axis.
- * @param {int} iUp the number of pixels the element can move up
- * @param {int} iDown the number of pixels the element can move down
- * @param {int} iTickSize optional parameter for specifying that the
+ * @param {Number} iUp the number of pixels the element can move up
+ * @param {Number} iDown the number of pixels the element can move down
+ * @param {Number} iTickSize (optional) parameter for specifying that the
* element should move iTickSize pixels at a time.
*/
setYConstraint: function(iUp, iDown, iTickSize) {
@@ -33251,7 +34358,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* Must be called if you manually reposition a dd element.
- * @param {boolean} maintainOffset
+ * @param {Boolean} maintainOffset
*/
resetConstraints: function() {
// Maintain offsets if necessary
@@ -33284,9 +34391,9 @@ Ext.define('Ext.dd.DragDrop', {
* Normally the drag element is moved pixel by pixel, but we can specify
* that it move a number of pixels at a time. This method resolves the
* location when we have it set up like this.
- * @param {int} val where we want to place the object
- * @param {int[]} tickArray sorted array of valid points
- * @return {int} the closest tick
+ * @param {Number} val where we want to place the object
+ * @param {Number[]} tickArray sorted array of valid points
+ * @return {Number} the closest tick
* @private
*/
getTick: function(val, tickArray) {
@@ -33316,7 +34423,7 @@ Ext.define('Ext.dd.DragDrop', {
/**
* toString method
- * @return {string} string representation of the dd obj
+ * @return {String} string representation of the dd obj
*/
toString: function() {
return ("DragDrop " + this.id);
@@ -33347,7 +34454,7 @@ Ext.define('Ext.dd.DD', {
* Creates new DD instance.
* @param {String} id the id of the linked element
* @param {String} sGroup the group of related DragDrop items
- * @param {object} config an object containing configurable attributes.
+ * @param {Object} config an object containing configurable attributes.
* Valid properties for DD: scroll
*/
constructor: function(id, sGroup, config) {
@@ -33361,7 +34468,7 @@ Ext.define('Ext.dd.DD', {
* window when a drag and drop element is dragged near the viewport boundary.
* Defaults to true.
* @property scroll
- * @type boolean
+ * @type Boolean
*/
scroll: true,
@@ -33369,8 +34476,8 @@ Ext.define('Ext.dd.DD', {
* Sets the pointer offset to the distance between the linked element's top
* left corner and the location the element was clicked
* @method autoOffset
- * @param {int} iPageX the X coordinate of the click
- * @param {int} iPageY the Y coordinate of the click
+ * @param {Number} iPageX the X coordinate of the click
+ * @param {Number} iPageY the Y coordinate of the click
*/
autoOffset: function(iPageX, iPageY) {
var x = iPageX - this.startPageX;
@@ -33383,8 +34490,8 @@ Ext.define('Ext.dd.DD', {
* offset to be in a particular location (e.g., pass in 0,0 to set it
* to the center of the object)
* @method setDelta
- * @param {int} iDeltaX the distance from the left
- * @param {int} iDeltaY the distance from the top
+ * @param {Number} iDeltaX the distance from the left
+ * @param {Number} iDeltaY the distance from the top
*/
setDelta: function(iDeltaX, iDeltaY) {
this.deltaX = iDeltaX;
@@ -33397,8 +34504,8 @@ Ext.define('Ext.dd.DD', {
* that was clicked. Override this if you want to place the element in a
* location other than where the cursor is.
* @method setDragElPos
- * @param {int} iPageX the X coordinate of the mousedown or drag event
- * @param {int} iPageY the Y coordinate of the mousedown or drag event
+ * @param {Number} iPageX the X coordinate of the mousedown or drag event
+ * @param {Number} iPageY the Y coordinate of the mousedown or drag event
*/
setDragElPos: function(iPageX, iPageY) {
// the first time we do this, we are going to check to make sure
@@ -33415,14 +34522,14 @@ Ext.define('Ext.dd.DD', {
* location other than where the cursor is.
* @method alignElWithMouse
* @param {HTMLElement} el the element to move
- * @param {int} iPageX the X coordinate of the mousedown or drag event
- * @param {int} iPageY the Y coordinate of the mousedown or drag event
+ * @param {Number} iPageX the X coordinate of the mousedown or drag event
+ * @param {Number} iPageY the Y coordinate of the mousedown or drag event
*/
alignElWithMouse: function(el, iPageX, iPageY) {
var oCoord = this.getTargetCoord(iPageX, iPageY),
fly = el.dom ? el : Ext.fly(el, '_dd'),
elSize = fly.getSize(),
- EL = Ext.core.Element,
+ EL = Ext.Element,
vpSize;
if (!this.deltaSetXY) {
@@ -33453,9 +34560,9 @@ Ext.define('Ext.dd.DD', {
* tick marks on-demand. We need to know this so that we can calculate the
* number of pixels the element is offset from its original position.
* @method cachePosition
- * @param iPageX the current x position (optional, this just makes it so we
+ * @param {Number} iPageX (optional) the current x position (this just makes it so we
* don't have to look it up again)
- * @param iPageY the current y position (optional, this just makes it so we
+ * @param {Number} iPageY (optional) the current y position (this just makes it so we
* don't have to look it up again)
*/
cachePosition: function(iPageX, iPageY) {
@@ -33463,7 +34570,7 @@ Ext.define('Ext.dd.DD', {
this.lastPageX = iPageX;
this.lastPageY = iPageY;
} else {
- var aCoord = Ext.core.Element.getXY(this.getEl());
+ var aCoord = Ext.Element.getXY(this.getEl());
this.lastPageX = aCoord[0];
this.lastPageY = aCoord[1];
}
@@ -33473,20 +34580,20 @@ Ext.define('Ext.dd.DD', {
* Auto-scroll the window if the dragged object has been moved beyond the
* visible window boundary.
* @method autoScroll
- * @param {int} x the drag element's x position
- * @param {int} y the drag element's y position
- * @param {int} h the height of the drag element
- * @param {int} w the width of the drag element
+ * @param {Number} x the drag element's x position
+ * @param {Number} y the drag element's y position
+ * @param {Number} h the height of the drag element
+ * @param {Number} w the width of the drag element
* @private
*/
autoScroll: function(x, y, h, w) {
if (this.scroll) {
// The client height
- var clientH = Ext.core.Element.getViewHeight();
+ var clientH = Ext.Element.getViewHeight();
// The client width
- var clientW = Ext.core.Element.getViewWidth();
+ var clientW = Ext.Element.getViewWidth();
// The amt scrolled down
var st = this.DDMInstance.getScrollTop();
@@ -33548,8 +34655,8 @@ Ext.define('Ext.dd.DD', {
* Finds the location the element should be placed if we want to move
* it to where the mouse location less the click offset would place us.
* @method getTargetCoord
- * @param {int} iPageX the X coordinate of the click
- * @param {int} iPageY the Y coordinate of the click
+ * @param {Number} iPageX the X coordinate of the click
+ * @param {Number} iPageY the Y coordinate of the click
* @return an object that contains the coordinates (Object.x and Object.y)
* @private
*/
@@ -33677,7 +34784,7 @@ Ext.define('Ext.dd.DDProxy', {
* Creates new DDProxy.
* @param {String} id the id of the linked html element
* @param {String} sGroup the group of related DragDrop objects
- * @param {object} config an object containing configurable attributes.
+ * @param {Object} config an object containing configurable attributes.
* Valid properties for DDProxy in addition to those in DragDrop:
*
* - resizeFrame
@@ -33696,7 +34803,7 @@ Ext.define('Ext.dd.DDProxy', {
* we want to drag (this is to get the frame effect). We can turn it off
* if we want a different behavior.
* @property resizeFrame
- * @type boolean
+ * @type Boolean
*/
resizeFrame: true,
@@ -33706,7 +34813,7 @@ Ext.define('Ext.dd.DDProxy', {
* you do not have constraints on the obj is to have the drag frame centered
* around the cursor. Set centerFrame to true for this effect.
* @property centerFrame
- * @type boolean
+ * @type Boolean
*/
centerFrame: false,
@@ -33764,8 +34871,8 @@ Ext.define('Ext.dd.DDProxy', {
* Resizes the drag frame to the dimensions of the clicked object, positions
* it over the object, and finally displays it
* @method showFrame
- * @param {int} iPageX X click position
- * @param {int} iPageY Y click position
+ * @param {Number} iPageX X click position
+ * @param {Number} iPageY Y click position
* @private
*/
showFrame: function(iPageX, iPageY) {
@@ -33868,30 +34975,30 @@ Ext.define('Ext.dd.DragSource', {
/**
* @cfg {String} ddGroup
* A named drag drop group to which this object belongs. If a group is specified, then this object will only
- * interact with other drag drop objects in the same group (defaults to undefined).
+ * interact with other drag drop objects in the same group.
*/
/**
- * @cfg {String} dropAllowed
- * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
+ * @cfg {String} [dropAllowed="x-dd-drop-ok"]
+ * The CSS class returned to the drag source when drop is allowed.
*/
-
dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
/**
- * @cfg {String} dropNotAllowed
- * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
+ * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
+ * The CSS class returned to the drag source when drop is not allowed.
*/
dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
/**
* @cfg {Boolean} animRepair
- * Defaults to true. If true, animates the proxy element back to the position of the handle element used to trigger the drag.
+ * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
*/
animRepair: true,
/**
- * @cfg {String} repairHighlightColor The color to use when visually highlighting the drag source in the afterRepair
- * method after a failed drop (defaults to 'c3daf9' - light blue). The color must be a 6 digit hex value, without
+ * @cfg {String} repairHighlightColor
+ * The color to use when visually highlighting the drag source in the afterRepair
+ * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
* a preceding '#'.
*/
repairHighlightColor: 'c3daf9',
@@ -33899,7 +35006,7 @@ Ext.define('Ext.dd.DragSource', {
/**
* Creates new drag-source.
* @constructor
- * @param {Mixed} el The container element
+ * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
* @param {Object} config (optional) Config object.
*/
constructor: function(el, config) {
@@ -34094,7 +35201,7 @@ Ext.define('Ext.dd.DragSource', {
* @param {Object} target The target DD
* @param {Event} e The event object
* @param {String} id The id of the dropped element
- * @method afterInvalidDrop
+ * @method afterValidDrop
*/
this.afterValidDrop(target, e, id);
}
@@ -34318,91 +35425,110 @@ Ext.define('Ext.layout.component.Dock', {
});
/**
- * @class Ext.panel.Panel
- * @extends Ext.panel.AbstractPanel
- * Panel is a container that has specific functionality and structural components that make
- * it the perfect building block for application-oriented user interfaces.
- * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable
- * of being configured with a {@link Ext.container.Container#layout layout}, and containing child Components.
- * When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.container.Container#add adding} Components
- * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
- * those child elements need to be sized using one of Ext's built-in {@link Ext.container.Container#layout layout}
schemes. By
- * default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders
- * child components, appending them one after the other inside the Container, and does not apply any sizing
- * at all.
+ * Panel is a container that has specific functionality and structural components that make it the perfect building
+ * block for application-oriented user interfaces.
+ *
+ * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
+ * {@link Ext.container.Container#layout layout}, and containing child Components.
+ *
+ * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
+ * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
+ * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
+ * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
+ * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
+ *
* {@img Ext.panel.Panel/panel.png Panel components}
- * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
- * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
- * information).
- * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior.
- * Panels can be easily dropped into any {@link Ext.container.Container Container} or layout, and the
- * layout and rendering pipeline is {@link Ext.container.Container#add completely managed by the framework}.
- * Note: By default, the {@link #closable close}
header tool destroys the Panel resulting in removal of the Panel
- * and the destruction of any descendant Components. This makes the Panel object, and all its descendants unusable . To enable the close
- * tool to simply hide a Panel for later re-use, configure the Panel with {@link #closeAction closeAction: 'hide'}
.
- * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of Containers,
- * and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a Panel into the document,
- * here's how to do it:
-Ext.create('Ext.panel.Panel', {
- title: 'Hello',
- width: 200,
- html: '<p>World!</p>',
- renderTo: document.body
-});
-
- * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a constituent part of a Container:
-var filterPanel = Ext.create('Ext.panel.Panel', {
- bodyPadding: 5, // Don't want content to crunch against the borders
- title: 'Filters',
- items: [{
- xtype: 'datefield',
- fieldLabel: 'Start date'
- }, {
- xtype: 'datefield',
- fieldLabel: 'End date'
- }]
-});
-
- * Note that the Panel above is not configured to render into the document, nor is it configured with a size or position. In a real world scenario,
- * the Container into which the Panel is added will use a {@link #layout} to render, size and position its child Components.
- * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and arranging child
- * Components:
-var resultsPanel = Ext.create('Ext.panel.Panel', {
- title: 'Results',
- width: 600,
- height: 400,
- renderTo: document.body,
- layout: {
- type: 'vbox', // Arrange child items vertically
- align: 'stretch', // Each takes up full width
- padding: 5
- },
- items: [{ // Results grid specified as a config object with an xtype of 'grid'
- xtype: 'grid',
- columns: [{header: 'Column One'}], // One header just for show. There's no data,
- store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
- flex: 1 // Use 1/3 of Container's height (hint to Box layout)
- }, {
- xtype: 'splitter' // A splitter between the two child items
- }, { // Details Panel specified as a config object (no xtype defaults to 'panel').
- title: 'Details',
- bodyPadding: 5,
- items: [{
- fieldLabel: 'Data item',
- xtype: 'textfield'
- }], // An array of form fields
- flex: 2 // Use 2/3 of Container's height (hint to Box layout)
- }]
-});
-
- * The example illustrates one possible method of displaying search results. The Panel contains a grid with the resulting data arranged
- * in rows. Each selected row may be displayed in detail in the Panel below. The {@link Ext.layout.container.VBox vbox} layout is used
- * to arrange the two vertically. It is configured to stretch child items horizontally to full width. Child items may either be configured
- * with a numeric height, or with a flex
value to distribute available space proportionately.
- * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit within its
- * content area.
- * Using these techniques, as long as the layout is chosen and configured correctly, an application may have any level of
- * nested containment, all dynamically sized according to configuration, the user's preference and available browser size.
+ *
+ * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
+ * Ext.panel.Header header}, {@link #fbar footer} and body sections.
+ *
+ * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
+ * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
+ * is {@link Ext.container.Container#add completely managed by the framework}.
+ *
+ * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
+ * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
+ * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
+ * `{@link #closeAction closeAction}: 'hide'`.
+ *
+ * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
+ * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
+ * Panel into the document, here's how to do it:
+ *
+ * @example
+ * Ext.create('Ext.panel.Panel', {
+ * title: 'Hello',
+ * width: 200,
+ * html: 'World!
',
+ * renderTo: Ext.getBody()
+ * });
+ *
+ * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
+ * constituent part of a Container:
+ *
+ * @example
+ * var filterPanel = Ext.create('Ext.panel.Panel', {
+ * bodyPadding: 5, // Don't want content to crunch against the borders
+ * width: 300,
+ * title: 'Filters',
+ * items: [{
+ * xtype: 'datefield',
+ * fieldLabel: 'Start date'
+ * }, {
+ * xtype: 'datefield',
+ * fieldLabel: 'End date'
+ * }],
+ * renderTo: Ext.getBody()
+ * });
+ *
+ * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
+ * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
+ * size and position its child Components.
+ *
+ * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
+ * arranging child Components:
+ *
+ * @example
+ * var resultsPanel = Ext.create('Ext.panel.Panel', {
+ * title: 'Results',
+ * width: 600,
+ * height: 400,
+ * renderTo: Ext.getBody(),
+ * layout: {
+ * type: 'vbox', // Arrange child items vertically
+ * align: 'stretch', // Each takes up full width
+ * padding: 5
+ * },
+ * items: [{ // Results grid specified as a config object with an xtype of 'grid'
+ * xtype: 'grid',
+ * columns: [{header: 'Column One'}], // One header just for show. There's no data,
+ * store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
+ * flex: 1 // Use 1/3 of Container's height (hint to Box layout)
+ * }, {
+ * xtype: 'splitter' // A splitter between the two child items
+ * }, { // Details Panel specified as a config object (no xtype defaults to 'panel').
+ * title: 'Details',
+ * bodyPadding: 5,
+ * items: [{
+ * fieldLabel: 'Data item',
+ * xtype: 'textfield'
+ * }], // An array of form fields
+ * flex: 2 // Use 2/3 of Container's height (hint to Box layout)
+ * }]
+ * });
+ *
+ * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
+ * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
+ * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
+ * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
+ * distribute available space proportionately.
+ *
+ * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
+ * within its content area.
+ *
+ * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
+ * level of nested containment, all dynamically sized according to configuration, the user's preference and available
+ * browser size.
*/
Ext.define('Ext.panel.Panel', {
extend: 'Ext.panel.AbstractPanel',
@@ -34420,166 +35546,182 @@ Ext.define('Ext.panel.Panel', {
/**
* @cfg {String} collapsedCls
- * A CSS class to add to the panel's element after it has been collapsed (defaults to
- * 'collapsed'
).
+ * A CSS class to add to the panel's element after it has been collapsed.
*/
collapsedCls: 'collapsed',
/**
* @cfg {Boolean} animCollapse
- * true
to animate the transition when the panel is collapsed, false
to skip the
- * animation (defaults to true
if the {@link Ext.fx.Anim} class is available, otherwise false
).
- * May also be specified as the animation duration in milliseconds.
+ * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
+ * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
+ * duration in milliseconds.
*/
animCollapse: Ext.enableFx,
/**
* @cfg {Number} minButtonWidth
- * Minimum width of all footer toolbar buttons in pixels (defaults to 75 ). If set, this will
- * be used as the default value for the {@link Ext.button.Button#minWidth} config of
- * each Button added to the footer toolbar via the {@link #fbar} or {@link #buttons} configurations.
- * It will be ignored for buttons that have a minWidth configured some other way, e.g. in their own config
- * object or via the {@link Ext.container.Container#config-defaults defaults} of their parent container.
+ * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
+ * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
+ * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
+ * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
+ * their parent container.
*/
minButtonWidth: 75,
/**
* @cfg {Boolean} collapsed
- * true
to render the panel collapsed, false
to render it expanded (defaults to
- * false
).
+ * `true` to render the panel collapsed, `false` to render it expanded.
*/
collapsed: false,
/**
* @cfg {Boolean} collapseFirst
- * true
to make sure the collapse/expand toggle button always renders first (to the left of)
- * any other tools in the panel's title bar, false
to render it last (defaults to true
).
+ * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
+ * the panel's title bar, `false` to render it last.
*/
collapseFirst: true,
/**
* @cfg {Boolean} hideCollapseTool
- * true
to hide the expand/collapse toggle button when {@link #collapsible} == true
,
- * false
to display it (defaults to false
).
+ * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
*/
hideCollapseTool: false,
/**
* @cfg {Boolean} titleCollapse
- * true
to allow expanding and collapsing the panel (when {@link #collapsible} = true
)
- * by clicking anywhere in the header bar, false
) to allow it only by clicking to tool button
- * (defaults to false
)).
+ * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
+ * the header bar, `false`) to allow it only by clicking to tool butto).
*/
titleCollapse: false,
/**
* @cfg {String} collapseMode
- * Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}.
- * When not a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header remains visible, and the body is collapsed to zero dimensions.
- * If the Panel has no header, then a new header (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-expand tool.
- * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
- *
- * undefined/omitted
When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
- * and to provide a UI with a Tool to allow the user to re-expand the Panel.
- * header
: The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border layout}.
- *
+ * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
+ * {@link Ext.layout.container.Border border layout}.**
+ *
+ * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
+ * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
+ * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
+ * expand tool.
+ *
+ * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
+ *
+ * - **`undefined/omitted`**
+ *
+ * When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
+ * and to provide a UI with a Tool to allow the user to re-expand the Panel.
+ *
+ * - **`header`** :
+ *
+ * The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
+ * layout}.
*/
/**
- * @cfg {Mixed} placeholder
- * Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}
- * when not using the 'header'
{@link #collapseMode}.
- * Optional. A Component (or config object for a Component) to show in place of this Panel when this Panel is collapsed by a
- * {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header Header}
- * containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
+ * @cfg {Ext.Component/Object} placeholder
+ * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
+ * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
+ *
+ * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
+ * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
+ * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
*/
/**
* @cfg {Boolean} floatable
- * Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}.
- * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated
- * above the layout, false to force the user to fully expand a collapsed region by
- * clicking the expand button to see it again (defaults to true ).
+ * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
+ * {@link Ext.layout.container.Border border layout}.**
+ *
+ * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
+ * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
*/
floatable: true,
/**
- * @cfg {Mixed} overlapHeader
- * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and is done automatically for you). Otherwise it is undefined.
- * If you manually add rounded corners to a panel header which does not have frame:true, this will need to be set to true.
+ * @cfg {Boolean} overlapHeader
+ * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
+ * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
+ * which does not have frame:true, this will need to be set to true.
*/
/**
* @cfg {Boolean} collapsible
- * True to make the panel collapsible and have an expand/collapse toggle Tool added into
- * the header tool button area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool (defaults to false).
+ * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
+ * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
+ *
* See {@link #collapseMode} and {@link #collapseDirection}
*/
collapsible: false,
/**
* @cfg {Boolean} collapseDirection
- * The direction to collapse the Panel when the toggle button is clicked.
- * Defaults to the {@link #headerPosition}
- * Important: This config is ignored for {@link #collapsible} Panels which are direct child items of a {@link Ext.layout.container.Border border layout}.
- * Specify as 'top'
, 'bottom'
, 'left'
or 'right'
.
+ * The direction to collapse the Panel when the toggle button is clicked.
+ *
+ * Defaults to the {@link #headerPosition}
+ *
+ * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
+ * Ext.layout.container.Border border layout}.**
+ *
+ * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
*/
/**
* @cfg {Boolean} closable
- * True to display the 'close' tool button and allow the user to close the window, false to
- * hide the button and disallow closing the window (defaults to false
).
- * By default, when close is requested by clicking the close button in the header, the {@link #close}
- * method will be called. This will {@link Ext.Component#destroy destroy} the Panel and its content
- * meaning that it may not be reused.
- * To make closing a Panel hide the Panel so that it may be reused, set
- * {@link #closeAction} to 'hide'.
+ * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
+ * disallow closing the window.
+ *
+ * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
+ * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
+ * reused.
+ *
+ * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
*/
closable: false,
/**
* @cfg {String} closeAction
- * The action to take when the close header tool is clicked:
- *
- * '{@link #destroy}'
: Default
- * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
- * it and all descendant Components. The window will not be available to be
- * redisplayed via the {@link #show} method.
- *
- * '{@link #hide}'
:
- * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
- * The window will be available to be redisplayed via the {@link #show} method.
- *
- *
- * Note: This behavior has changed! setting *does* affect the {@link #close} method
- * which will invoke the approriate closeAction.
+ * The action to take when the close header tool is clicked:
+ *
+ * - **`'{@link #destroy}'`** :
+ *
+ * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
+ * Components. The window will **not** be available to be redisplayed via the {@link #show} method.
+ *
+ * - **`'{@link #hide}'`** :
+ *
+ * {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
+ * available to be redisplayed via the {@link #show} method.
+ *
+ * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
+ * approriate closeAction.
*/
closeAction: 'destroy',
/**
- * @cfg {Object/Array} dockedItems
- * A component or series of components to be added as docked items to this panel.
- * The docked items can be docked to either the top, right, left or bottom of a panel.
- * This is typically used for things like toolbars or tab bars:
- *
-var panel = new Ext.panel.Panel({
- dockedItems: [{
- xtype: 'toolbar',
- dock: 'top',
- items: [{
- text: 'Docked to the top'
- }]
- }]
-});
+ * @cfg {Object/Object[]} dockedItems
+ * A component or series of components to be added as docked items to this panel. The docked items can be docked to
+ * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
+ *
+ * var panel = new Ext.panel.Panel({
+ * dockedItems: [{
+ * xtype: 'toolbar',
+ * dock: 'top',
+ * items: [{
+ * text: 'Docked to the top'
+ * }]
+ * }]
+ * });
*/
/**
- * @cfg {Boolean} preventHeader Prevent a Header from being created and shown. Defaults to false.
+ * @cfg {Boolean} preventHeader
+ * Prevent a Header from being created and shown.
*/
preventHeader: false,
/**
- * @cfg {String} headerPosition Specify as 'top'
, 'bottom'
, 'left'
or 'right'
. Defaults to 'top'
.
+ * @cfg {String} headerPosition
+ * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
*/
headerPosition: 'top',
@@ -34596,45 +35738,60 @@ var panel = new Ext.panel.Panel({
frameHeader: true,
/**
- * @cfg {Array} tools
- * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as child
- * components of the header container. They can be accessed using {@link #down} and {#query}, as well as the other
- * component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
- * Note that, apart from the toggle tool which is provided when a panel is collapsible, these
- * tools only provide the visual button. Any required functionality must be provided by adding
- * handlers that implement the necessary behavior.
- * Example usage:
- *
-tools:[{
- type:'refresh',
- qtip: 'Refresh form Data',
- // hidden:true,
- handler: function(event, toolEl, panel){
- // refresh logic
- }
-},
-{
- type:'help',
- qtip: 'Get Help',
- handler: function(event, toolEl, panel){
- // show help here
- }
-}]
-
+ * @cfg {Object[]/Ext.panel.Tool[]} tools
+ * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
+ * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
+ * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
+ *
+ * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
+ * visual button. Any required functionality must be provided by adding handlers that implement the necessary
+ * behavior.
+ *
+ * Example usage:
+ *
+ * tools:[{
+ * type:'refresh',
+ * tooltip: 'Refresh form Data',
+ * // hidden:true,
+ * handler: function(event, toolEl, panel){
+ * // refresh logic
+ * }
+ * },
+ * {
+ * type:'help',
+ * tooltip: 'Get Help',
+ * handler: function(event, toolEl, panel){
+ * // show help here
+ * }
+ * }]
*/
/**
- * @cfg {String} title
- * The title text to be used to display in the {@link Ext.panel.Header panel header} (defaults to '').
- * When a `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
+ * @cfg {String} [title='']
+ * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
+ * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
* {@link #preventHeader} is set to `true`.
*/
+ /**
+ * @cfg {String} iconCls
+ * CSS class for icon in header. Used for displaying an icon to the left of a title.
+ */
+
initComponent: function() {
var me = this,
cls;
me.addEvents(
+
+ /**
+ * @event beforeclose
+ * Fires before the user closes the panel. Return false from any listener to stop the close event being
+ * fired
+ * @param {Ext.panel.Panel} panel The Panel object
+ */
+ 'beforeclose',
+
/**
* @event beforeexpand
* Fires before this panel is expanded. Return false to prevent the expand.
@@ -34647,11 +35804,13 @@ tools:[{
* @event beforecollapse
* Fires before this panel is collapsed. Return false to prevent the collapse.
* @param {Ext.panel.Panel} p The Panel being collapsed.
- * @param {String} direction. The direction of the collapse. One of
- * Ext.Component.DIRECTION_TOP
- * Ext.Component.DIRECTION_RIGHT
- * Ext.Component.DIRECTION_BOTTOM
- * Ext.Component.DIRECTION_LEFT
+ * @param {String} direction . The direction of the collapse. One of
+ *
+ * - Ext.Component.DIRECTION_TOP
+ * - Ext.Component.DIRECTION_RIGHT
+ * - Ext.Component.DIRECTION_BOTTOM
+ * - Ext.Component.DIRECTION_LEFT
+ *
* @param {Boolean} animate True if the collapse is animated, else false.
*/
"beforecollapse",
@@ -34700,12 +35859,11 @@ tools:[{
me.setUI(me.ui + '-framed');
}
- me.callParent();
-
- me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
-
// Backwards compatibility
me.bridgeToolbars();
+
+ me.callParent();
+ me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
},
setBorder: function(border) {
@@ -34755,7 +35913,7 @@ tools:[{
},
/**
- * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
+ * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
* @param {String} newTitle
*/
setTitle: function(newTitle) {
@@ -34776,8 +35934,9 @@ tools:[{
},
/**
- * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}.
- * @param {String} newIconCls
+ * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
+ * {@link #iconchange} event after completion.
+ * @param {String} newIconCls The new CSS class name
*/
setIconCls: function(newIconCls) {
var me = this,
@@ -34793,6 +35952,7 @@ tools:[{
bridgeToolbars: function() {
var me = this,
+ docked = [],
fbar,
fbarDefaults,
minButtonWidth = me.minButtonWidth;
@@ -34824,94 +35984,85 @@ tools:[{
// Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
- /**
- * @cfg {String} buttonAlign
- * The alignment of any buttons added to this panel. Valid values are 'right',
- * 'left' and 'center' (defaults to 'right' for buttons/fbar, 'left' for other toolbar types).
- * NOTE: The newer way to specify toolbars is to use the dockedItems config, and
- * instead of buttonAlign you would add the layout: { pack: 'start' | 'center' | 'end' }
- * option to the dockedItem config.
- */
-
/**
- * @cfg {Object/Array} tbar
-
-Convenience method. Short for 'Top Bar'.
-
- tbar: [
- { xtype: 'button', text: 'Button 1' }
- ]
-
-is equivalent to
-
- dockedItems: [{
- xtype: 'toolbar',
- dock: 'top',
- items: [
- { xtype: 'button', text: 'Button 1' }
- ]
- }]
+ * @cfg {String} buttonAlign
+ * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
+ * 'right' for buttons/fbar, 'left' for other toolbar types).
+ *
+ * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
+ * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
+ */
- * @markdown
+ /**
+ * @cfg {Object/Object[]} tbar
+ * Convenience config. Short for 'Top Bar'.
+ *
+ * tbar: [
+ * { xtype: 'button', text: 'Button 1' }
+ * ]
+ *
+ * is equivalent to
+ *
+ * dockedItems: [{
+ * xtype: 'toolbar',
+ * dock: 'top',
+ * items: [
+ * { xtype: 'button', text: 'Button 1' }
+ * ]
+ * }]
*/
if (me.tbar) {
- me.addDocked(initToolbar(me.tbar, 'top'));
+ docked.push(initToolbar(me.tbar, 'top'));
me.tbar = null;
}
/**
- * @cfg {Object/Array} bbar
-
-Convenience method. Short for 'Bottom Bar'.
-
- bbar: [
- { xtype: 'button', text: 'Button 1' }
- ]
-
-is equivalent to
-
- dockedItems: [{
- xtype: 'toolbar',
- dock: 'bottom',
- items: [
- { xtype: 'button', text: 'Button 1' }
- ]
- }]
-
- * @markdown
+ * @cfg {Object/Object[]} bbar
+ * Convenience config. Short for 'Bottom Bar'.
+ *
+ * bbar: [
+ * { xtype: 'button', text: 'Button 1' }
+ * ]
+ *
+ * is equivalent to
+ *
+ * dockedItems: [{
+ * xtype: 'toolbar',
+ * dock: 'bottom',
+ * items: [
+ * { xtype: 'button', text: 'Button 1' }
+ * ]
+ * }]
*/
if (me.bbar) {
- me.addDocked(initToolbar(me.bbar, 'bottom'));
+ docked.push(initToolbar(me.bbar, 'bottom'));
me.bbar = null;
}
/**
- * @cfg {Object/Array} buttons
-
-Convenience method used for adding buttons docked to the bottom of the panel. This is a
-synonym for the {@link #fbar} config.
-
- buttons: [
- { text: 'Button 1' }
- ]
-
-is equivalent to
-
- dockedItems: [{
- xtype: 'toolbar',
- dock: 'bottom',
- ui: 'footer',
- defaults: {minWidth: {@link #minButtonWidth}},
- items: [
- { xtype: 'component', flex: 1 },
- { xtype: 'button', text: 'Button 1' }
- ]
- }]
-
-The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
-each of the buttons in the buttons toolbar.
-
- * @markdown
+ * @cfg {Object/Object[]} buttons
+ * Convenience config used for adding buttons docked to the bottom of the panel. This is a
+ * synonym for the {@link #fbar} config.
+ *
+ * buttons: [
+ * { text: 'Button 1' }
+ * ]
+ *
+ * is equivalent to
+ *
+ * dockedItems: [{
+ * xtype: 'toolbar',
+ * dock: 'bottom',
+ * ui: 'footer',
+ * defaults: {minWidth: {@link #minButtonWidth}},
+ * items: [
+ * { xtype: 'component', flex: 1 },
+ * { xtype: 'button', text: 'Button 1' }
+ * ]
+ * }]
+ *
+ * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
+ * each of the buttons in the buttons toolbar.
*/
if (me.buttons) {
me.fbar = me.buttons;
@@ -34919,31 +36070,28 @@ each of the buttons in the buttons toolbar.
}
/**
- * @cfg {Object/Array} fbar
-
-Convenience method used for adding items to the bottom of the panel. Short for Footer Bar.
-
- fbar: [
- { type: 'button', text: 'Button 1' }
- ]
-
-is equivalent to
-
- dockedItems: [{
- xtype: 'toolbar',
- dock: 'bottom',
- ui: 'footer',
- defaults: {minWidth: {@link #minButtonWidth}},
- items: [
- { xtype: 'component', flex: 1 },
- { xtype: 'button', text: 'Button 1' }
- ]
- }]
-
-The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
-each of the buttons in the fbar.
-
- * @markdown
+ * @cfg {Object/Object[]} fbar
+ * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
+ *
+ * fbar: [
+ * { type: 'button', text: 'Button 1' }
+ * ]
+ *
+ * is equivalent to
+ *
+ * dockedItems: [{
+ * xtype: 'toolbar',
+ * dock: 'bottom',
+ * ui: 'footer',
+ * defaults: {minWidth: {@link #minButtonWidth}},
+ * items: [
+ * { xtype: 'component', flex: 1 },
+ * { xtype: 'button', text: 'Button 1' }
+ * ]
+ * }]
+ *
+ * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
+ * each of the buttons in the fbar.
*/
if (me.fbar) {
fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
@@ -34962,14 +36110,13 @@ each of the buttons in the fbar.
};
}
- me.addDocked(fbar);
+ docked.push(fbar);
me.fbar = null;
}
/**
- * @cfg {Object/Array} lbar
- *
- * Convenience method. Short for 'Left Bar' (left-docked, vertical toolbar).
+ * @cfg {Object/Object[]} lbar
+ * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
*
* lbar: [
* { xtype: 'button', text: 'Button 1' }
@@ -34984,18 +36131,15 @@ each of the buttons in the fbar.
* { xtype: 'button', text: 'Button 1' }
* ]
* }]
- *
- * @markdown
*/
if (me.lbar) {
- me.addDocked(initToolbar(me.lbar, 'left'));
+ docked.push(initToolbar(me.lbar, 'left'));
me.lbar = null;
}
/**
- * @cfg {Object/Array} rbar
- *
- * Convenience method. Short for 'Right Bar' (right-docked, vertical toolbar).
+ * @cfg {Object/Object[]} rbar
+ * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
*
* rbar: [
* { xtype: 'button', text: 'Button 1' }
@@ -35010,13 +36154,20 @@ each of the buttons in the fbar.
* { xtype: 'button', text: 'Button 1' }
* ]
* }]
- *
- * @markdown
*/
if (me.rbar) {
- me.addDocked(initToolbar(me.rbar, 'right'));
+ docked.push(initToolbar(me.rbar, 'right'));
me.rbar = null;
}
+
+ if (me.dockedItems) {
+ if (!Ext.isArray(me.dockedItems)) {
+ me.dockedItems = [me.dockedItems];
+ }
+ me.dockedItems = me.dockedItems.concat(docked);
+ } else {
+ me.dockedItems = docked;
+ }
},
/**
@@ -35027,7 +36178,7 @@ each of the buttons in the fbar.
initTools: function() {
var me = this;
- me.tools = me.tools || [];
+ me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
// Add a collapse tool unless configured to not show a collapse tool
// or to not even show a header.
@@ -35067,17 +36218,18 @@ each of the buttons in the fbar.
/**
* @private
+ * @template
* Template method to be implemented in subclasses to add their tools after the collapsible tool.
*/
addTools: Ext.emptyFn,
/**
- * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s
- * the Panel object and all its descendant Components. The {@link #beforeclose beforeclose}
- * event is fired before the close happens and will cancel the close action if it returns false.
- *
Note: This method is not affected by the {@link #closeAction} setting which
- * only affects the action triggered when clicking the {@link #closable 'close' tool in the header}.
- * To hide the Panel without destroying it, call {@link #hide}.
+ * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
+ * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
+ * close happens and will cancel the close action if it returns false.
+ *
+ * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
+ * {@link #destroy} and {@link #hide} methods.
*/
close: function() {
if (this.fireEvent('beforeclose', this) !== false) {
@@ -35108,7 +36260,12 @@ each of the buttons in the fbar.
afterRender: function() {
var me = this;
+
me.callParent(arguments);
+
+ // Instate the collapsed state after render. We need to wait for
+ // this moment so that we have established at least some of our size (from our
+ // configured dimensions or from content via the component layout)
if (me.collapsed) {
me.collapsed = false;
me.collapse(null, false, true);
@@ -35180,13 +36337,52 @@ each of the buttons in the fbar.
return this.body || this.frameBody || this.el;
},
+ // the overrides below allow for collapsed regions inside the border layout to be hidden
+
+ // inherit docs
+ isVisible: function(deep){
+ var me = this;
+ if (me.collapsed && me.placeholder) {
+ return me.placeholder.isVisible(deep);
+ }
+ return me.callParent(arguments);
+ },
+
+ // inherit docs
+ onHide: function(){
+ var me = this;
+ if (me.collapsed && me.placeholder) {
+ me.placeholder.hide();
+ } else {
+ me.callParent(arguments);
+ }
+ },
+
+ // inherit docs
+ onShow: function(){
+ var me = this;
+ if (me.collapsed && me.placeholder) {
+ // force hidden back to true, since this gets set by the layout
+ me.hidden = true;
+ me.placeholder.show();
+ } else {
+ me.callParent(arguments);
+ }
+ },
+
addTool: function(tool) {
- this.tools.push(tool);
- var header = this.header;
+ var me = this,
+ header = me.header;
+
+ if (Ext.isArray(tool)) {
+ Ext.each(tool, me.addTool, me);
+ return;
+ }
+ me.tools.push(tool);
if (header) {
header.addTool(tool);
}
- this.updateHeader();
+ me.updateHeader();
},
getOppositeDirection: function(d) {
@@ -35204,15 +36400,18 @@ each of the buttons in the fbar.
},
/**
- * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the
- * border towards which the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will
- * cancel the collapse action if it returns false.
- * @param {String} direction. The direction to collapse towards. Must be one of
- * Ext.Component.DIRECTION_TOP
- * Ext.Component.DIRECTION_RIGHT
- * Ext.Component.DIRECTION_BOTTOM
- * Ext.Component.DIRECTION_LEFT
- * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
+ * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
+ * collapse action if it returns false.
+ *
+ * @param {String} direction . The direction to collapse towards. Must be one of
+ *
+ * - Ext.Component.DIRECTION_TOP
+ * - Ext.Component.DIRECTION_RIGHT
+ * - Ext.Component.DIRECTION_BOTTOM
+ * - Ext.Component.DIRECTION_LEFT
+ *
+ * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
* {@link #animCollapse} panel config)
* @return {Ext.panel.Panel} this
*/
@@ -35247,7 +36446,6 @@ each of the buttons in the fbar.
reExpanderOrientation,
reExpanderDock,
getDimension,
- setDimension,
collapseDimension;
if (!direction) {
@@ -35270,23 +36468,22 @@ each of the buttons in the fbar.
switch (direction) {
case c.DIRECTION_TOP:
case c.DIRECTION_BOTTOM:
- me.expandedSize = me.getHeight();
reExpanderOrientation = 'horizontal';
collapseDimension = 'height';
getDimension = 'getHeight';
- setDimension = 'setHeight';
- // Collect the height of the visible header.
- // Hide all docked items except the header.
- // Hide *ALL* docked items if we're going to end up hiding the whole Panel anyway
+ // Attempt to find a reExpander Component (docked in a horizontal orientation)
+ // Also, collect all other docked items which we must hide after collapse.
for (; i < dockedItemCount; i++) {
comp = dockedItems[i];
if (comp.isVisible()) {
- if (comp.isHeader && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
+ if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
reExpander = comp;
} else {
me.hiddenDocked.push(comp);
}
+ } else if (comp === me.reExpander) {
+ reExpander = comp;
}
}
@@ -35298,15 +36495,12 @@ each of the buttons in the fbar.
case c.DIRECTION_LEFT:
case c.DIRECTION_RIGHT:
- me.expandedSize = me.getWidth();
reExpanderOrientation = 'vertical';
collapseDimension = 'width';
getDimension = 'getWidth';
- setDimension = 'setWidth';
- // Collect the height of the visible header.
- // Hide all docked items except the header.
- // Hide *ALL* docked items if we're going to end up hiding the whole Panel anyway
+ // Attempt to find a reExpander Component (docked in a vecrtical orientation)
+ // Also, collect all other docked items which we must hide after collapse.
for (; i < dockedItemCount; i++) {
comp = dockedItems[i];
if (comp.isVisible()) {
@@ -35315,6 +36509,8 @@ each of the buttons in the fbar.
} else {
me.hiddenDocked.push(comp);
}
+ } else if (comp === me.reExpander) {
+ reExpander = comp;
}
}
@@ -35328,12 +36524,6 @@ each of the buttons in the fbar.
throw('Panel collapse must be passed a valid Component collapse direction');
}
- // No scrollbars when we shrink this Panel
- // And no laying out of any children... we're effectively *hiding* the body
- me.setAutoScroll(false);
- me.suspendLayout = true;
- me.body.setVisibilityMode(Ext.core.Element.DISPLAY);
-
// Disable toggle tool during animated collapse
if (animate && me.collapseTool) {
me.collapseTool.disable();
@@ -35346,7 +36536,8 @@ each of the buttons in the fbar.
// }
// We found a header: Measure it to find the collapse-to size.
- if (reExpander) {
+ if (reExpander && reExpander.rendered) {
+
//we must add the collapsed cls to the header and then remove to get the proper height
reExpander.addClsWithUI(me.collapsedCls);
reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
@@ -35428,13 +36619,14 @@ each of the buttons in the fbar.
if (!me.collapseMemento) {
me.collapseMemento = new Ext.util.Memento(me);
}
- me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight']);
+ me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
// Remove any flex config before we attempt to collapse.
me.savedFlex = me.flex;
me.minWidth = 0;
me.minHeight = 0;
delete me.flex;
+ me.suspendLayout = true;
if (animate) {
me.animate(anim);
@@ -35455,7 +36647,22 @@ each of the buttons in the fbar.
me.collapseMemento.restore(['minWidth', 'minHeight']);
- me.body.hide();
+ // Now we can restore the dimension we don't control to its original state
+ // Leave the value in the memento so that it can be correctly restored
+ // if it is set by animation.
+ if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
+ me.layoutManagedHeight = 2;
+ me.collapseMemento.restore('width', false);
+ } else {
+ me.layoutManagedWidth = 2;
+ me.collapseMemento.restore('height', false);
+ }
+
+ // We must hide the body, otherwise it overlays docked items which come before
+ // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
+ me.saveScrollTop = me.body.dom.scrollTop;
+ me.body.setStyle('display', 'none');
+
for (; i < l; i++) {
me.hiddenDocked[i].hide();
}
@@ -35464,22 +36671,24 @@ each of the buttons in the fbar.
me.reExpander.show();
}
me.collapsed = true;
+ me.suspendLayout = false;
if (!internal) {
- me.doComponentLayout();
+ if (me.ownerCt) {
+ // Because Component layouts only inform upstream containers if they have changed size,
+ // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
+ if (animated) {
+ me.ownerCt.layout.layout();
+ }
+ } else if (me.reExpander.temporary) {
+ me.doComponentLayout();
+ }
}
if (me.resizer) {
me.resizer.disable();
}
- // Now we can restore the dimension we don't control to its original state
- if (Ext.Component.VERTICAL_DIRECTION.test(me.expandDirection)) {
- me.collapseMemento.restore('width');
- } else {
- me.collapseMemento.restore('height');
- }
-
// If me Panel was configured with a collapse tool in its header, flip it's type
if (me.collapseTool) {
me.collapseTool.setType('expand-' + me.expandDirection);
@@ -35495,9 +36704,9 @@ each of the buttons in the fbar.
},
/**
- * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will
- * cancel the expand action if it returns false.
- * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
+ * expand action if it returns false.
+ * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
* {@link #animCollapse} panel config)
* @return {Ext.panel.Panel} this
*/
@@ -35543,12 +36752,13 @@ each of the buttons in the fbar.
me.collapseTool.setType('collapse-' + me.collapseDirection);
}
+ // Restore body display and scroll position
+ me.body.setStyle('display', '');
+ me.body.dom.scrollTop = me.saveScrollTop;
+
// Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
me.collapsed = false;
- // Collapsed means body element was hidden
- me.body.show();
-
// Remove any collapsed styling before any animation begins
me.removeClsWithUI(me.collapsedCls);
// if (me.border === false) {
@@ -35570,8 +36780,12 @@ each of the buttons in the fbar.
if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
+ // Restore the collapsed dimension.
+ // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
+ me.collapseMemento.restore('height', false);
+
// If autoHeight, measure the height now we have shown the body element.
- if (me.autoHeight) {
+ if (me.height === undefined) {
me.setCalculatedSize(me.width, null);
anim.to.height = me.getHeight();
@@ -35587,7 +36801,7 @@ each of the buttons in the fbar.
}
// Else, restore to saved height
else {
- anim.to.height = me.expandedSize;
+ anim.to.height = me.height;
}
// top needs animating upwards
@@ -35598,8 +36812,12 @@ each of the buttons in the fbar.
}
} else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
+ // Restore the collapsed dimension.
+ // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
+ me.collapseMemento.restore('width', false);
+
// If autoWidth, measure the width now we have shown the body element.
- if (me.autoWidth) {
+ if (me.width === undefined) {
me.setCalculatedSize(null, me.height);
anim.to.width = me.getWidth();
@@ -35615,7 +36833,7 @@ each of the buttons in the fbar.
}
// Else, restore to saved width
else {
- anim.to.width = me.expandedSize;
+ anim.to.width = me.width;
}
// left needs animating leftwards
@@ -35645,15 +36863,6 @@ each of the buttons in the fbar.
afterExpand: function(animated) {
var me = this;
- if (me.collapseMemento) {
- // collapse has to use setSize (since it takes control of the component's size in
- // collapsed mode) and so we restore the original size now that the component has
- // been expanded.
- me.collapseMemento.restoreAll();
- }
-
- me.setAutoScroll(me.initialConfig.autoScroll);
-
// Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
if (me.savedFlex) {
me.flex = me.savedFlex;
@@ -35662,8 +36871,11 @@ each of the buttons in the fbar.
delete me.height;
}
- // Reinstate layout out after Panel has re-expanded
- delete me.suspendLayout;
+ // Restore width/height and dimension management flags to original values
+ if (me.collapseMemento) {
+ me.collapseMemento.restoreAll();
+ }
+
if (animated && me.ownerCt) {
// IE 6 has an intermittent repaint issue in this case so give
// it a little extra time to catch up before laying out.
@@ -35706,12 +36918,12 @@ each of the buttons in the fbar.
// private
initDraggable : function(){
/**
- * If this Panel is configured {@link #draggable}, this property will contain
- * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.
- * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
- * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
- * @type Ext.dd.DragSource.
- * @property dd
+ * @property {Ext.dd.DragSource} dd
+ * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
+ * Ext.dd.DragSource} which handles dragging the Panel.
+ *
+ * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
+ * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
*/
this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
},
@@ -35719,10 +36931,10 @@ each of the buttons in the fbar.
// private - helper function for ghost
ghostTools : function() {
var tools = [],
- origTools = this.initialConfig.tools;
+ headerTools = this.header.query('tool[hidden=false]');
- if (origTools) {
- Ext.each(origTools, function(tool) {
+ if (headerTools.length) {
+ Ext.each(headerTools, function(tool) {
// Some tools can be full components, and copying them into the ghost
// actually removes them from the owning panel. You could also potentially
// end up with duplicate DOM ids as well. To avoid any issues we just make
@@ -35731,8 +36943,7 @@ each of the buttons in the fbar.
type: tool.type
});
});
- }
- else {
+ } else {
tools = [{
type: 'placeholder'
}];
@@ -35744,23 +36955,19 @@ each of the buttons in the fbar.
ghost: function(cls) {
var me = this,
ghostPanel = me.ghostPanel,
- box = me.getBox();
+ box = me.getBox(),
+ header;
if (!ghostPanel) {
ghostPanel = Ext.create('Ext.panel.Panel', {
- renderTo: document.body,
+ renderTo: me.floating ? me.el.dom.parentNode : document.body,
floating: {
shadow: false
},
frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
- title: me.title,
overlapHeader: me.overlapHeader,
headerPosition: me.headerPosition,
- width: me.getWidth(),
- height: me.getHeight(),
- iconCls: me.iconCls,
baseCls: me.baseCls,
- tools: me.ghostTools(),
cls: me.baseCls + '-ghost ' + (cls ||'')
});
me.ghostPanel = ghostPanel;
@@ -35771,6 +36978,19 @@ each of the buttons in the fbar.
} else {
ghostPanel.toFront();
}
+ header = ghostPanel.header;
+ // restore options
+ if (header) {
+ header.suspendLayout = true;
+ Ext.Array.forEach(header.query('tool'), function(tool){
+ header.remove(tool);
+ });
+ header.suspendLayout = false;
+ }
+ ghostPanel.addTool(me.ghostTools());
+ ghostPanel.setTitle(me.title);
+ ghostPanel.setIconCls(me.iconCls);
+
ghostPanel.el.show();
ghostPanel.setPosition(box.x, box.y);
ghostPanel.setSize(box.width, box.height);
@@ -35806,6 +37026,8 @@ each of the buttons in the fbar.
}
this.callParent([resizable]);
}
+}, function(){
+ this.prototype.animCollapse = Ext.enableFx;
});
/**
@@ -35898,7 +37120,8 @@ Ext.define('Ext.tip.Tip', {
requires: [ 'Ext.layout.component.Tip' ],
alternateClassName: 'Ext.Tip',
/**
- * @cfg {Boolean} closable True to render a close tool button into the tooltip header (defaults to false).
+ * @cfg {Boolean} [closable=false]
+ * True to render a close tool button into the tooltip header.
*/
/**
* @cfg {Number} width
@@ -35906,33 +37129,32 @@ Ext.define('Ext.tip.Tip', {
* {@link #minWidth} or {@link #maxWidth}. The maximum supported value is 500.
*/
/**
- * @cfg {Number} minWidth The minimum width of the tip in pixels (defaults to 40).
+ * @cfg {Number} minWidth The minimum width of the tip in pixels.
*/
minWidth : 40,
/**
- * @cfg {Number} maxWidth The maximum width of the tip in pixels (defaults to 300). The maximum supported value is 500.
+ * @cfg {Number} maxWidth The maximum width of the tip in pixels. The maximum supported value is 500.
*/
maxWidth : 300,
/**
* @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
- * for bottom-right shadow (defaults to "sides").
+ * for bottom-right shadow.
*/
shadow : "sides",
/**
- * @cfg {String} defaultAlign Experimental . The default {@link Ext.core.Element#alignTo} anchor position value
- * for this tip relative to its element of origin (defaults to "tl-bl?").
+ * @cfg {String} defaultAlign
+ * Experimental . The default {@link Ext.Element#alignTo} anchor position value for this tip relative
+ * to its element of origin.
*/
defaultAlign : "tl-bl?",
/**
- * @cfg {Boolean} constrainPosition If true, then the tooltip will be automatically constrained to stay within
- * the browser viewport. Defaults to false.
+ * @cfg {Boolean} constrainPosition
+ * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
*/
constrainPosition : true,
- /**
- * @inherited
- */
+ // @inherited
frame: false,
// private panel overrides
@@ -35969,10 +37191,13 @@ Ext.define('Ext.tip.Tip', {
ariaRole: 'tooltip',
initComponent: function() {
- this.callParent(arguments);
+ var me = this;
+
+ me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
+ me.callParent(arguments);
// Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
- this.constrain = this.constrain || this.constrainPosition;
+ me.constrain = me.constrain || me.constrainPosition;
},
/**
@@ -35981,11 +37206,11 @@ Ext.define('Ext.tip.Tip', {
// Show the tip at x:50 and y:100
tip.showAt([50,100]);
- * @param {Array} xy An array containing the x and y coordinates
+ * @param {Number[]} xy An array containing the x and y coordinates
*/
showAt : function(xy){
var me = this;
- this.callParent();
+ this.callParent(arguments);
// Show may have been vetoed.
if (me.isVisible()) {
me.setPagePosition(xy[0], xy[1]);
@@ -35997,7 +37222,7 @@ tip.showAt([50,100]);
},
/**
- * Experimental . Shows this tip at a position relative to another element using a standard {@link Ext.core.Element#alignTo}
+ * Experimental . Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
* anchor position value. Example usage:
*
// Show the tip at the default position ('tl-br?')
@@ -36006,8 +37231,8 @@ tip.showBy('my-el');
// Show the tip's top-left corner anchored to the element's top-right corner
tip.showBy('my-el', 'tl-tr');
- * @param {Mixed} el An HTMLElement, Ext.core.Element or string id of the target element to align to
- * @param {String} position (optional) A valid {@link Ext.core.Element#alignTo} anchor position (defaults to 'tl-br?' or
+ * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
+ * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
* {@link #defaultAlign} if specified).
*/
showBy : function(el, pos) {
@@ -36025,7 +37250,7 @@ tip.showBy('my-el', 'tl-tr');
el: me.getDragEl(),
delegate: me.header.el,
constrain: me,
- constrainTo: me.el.dom.parentNode
+ constrainTo: me.el.getScopeParent()
};
// Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
Ext.Component.prototype.initDraggable.call(me);
@@ -36041,35 +37266,35 @@ tip.showBy('my-el', 'tl-tr');
* tooltip when hovering over a certain element or elements on the page. It allows fine-grained
* control over the tooltip's alignment relative to the target element or mouse, and the timing
* of when it is automatically shown and hidden.
- *
+ *
* This implementation does **not** have a built-in method of automatically populating the tooltip's
* text based on the target element; you must either configure a fixed {@link #html} value for each
* ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
* generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
* convenient way of automatically populating and configuring a tooltip based on specific DOM
* attributes of each target element.
- *
+ *
* # Basic Example
- *
+ *
* var tip = Ext.create('Ext.tip.ToolTip', {
* target: 'clearButton',
* html: 'Press this button to clear the form'
* });
- *
+ *
* {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
- *
+ *
* # Delegation
- *
+ *
* In addition to attaching a ToolTip to a single element, you can also use delegation to attach
* one ToolTip to many elements under a common parent. This is more efficient than creating many
* ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
* elements, and then set the {@link #delegate} config to a CSS selector that will select all the
* appropriate sub-elements.
- *
+ *
* When using delegation, it is likely that you will want to programmatically change the content
* of the ToolTip based on each delegate element; you can do this by implementing a custom
* listener for the {@link #beforeshow} event. Example:
- *
+ *
* var store = Ext.create('Ext.data.ArrayStore', {
* fields: ['company', 'price', 'change'],
* data: [
@@ -36081,7 +37306,7 @@ tip.showBy('my-el', 'tl-tr');
* ['AT&T Inc.', 31.61, -0.48]
* ]
* });
- *
+ *
* var grid = Ext.create('Ext.grid.Panel', {
* title: 'Array Grid',
* store: store,
@@ -36094,7 +37319,7 @@ tip.showBy('my-el', 'tl-tr');
* width: 400,
* renderTo: Ext.getBody()
* });
- *
+ *
* grid.getView().on('render', function(view) {
* view.tip = Ext.create('Ext.tip.ToolTip', {
* // The overall target element.
@@ -36113,30 +37338,30 @@ tip.showBy('my-el', 'tl-tr');
* }
* });
* });
- *
+ *
* {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
- *
+ *
* # Alignment
- *
+ *
* The following configuration properties allow control over how the ToolTip is aligned relative to
* the target element and/or mouse pointer:
- *
+ *
* - {@link #anchor}
* - {@link #anchorToTarget}
* - {@link #anchorOffset}
* - {@link #trackMouse}
* - {@link #mouseOffset}
- *
+ *
* # Showing/Hiding
- *
+ *
* The following configuration properties allow control over how and when the ToolTip is automatically
* shown and hidden:
- *
+ *
* - {@link #autoHide}
* - {@link #showDelay}
* - {@link #hideDelay}
* - {@link #dismissDelay}
- *
+ *
* @docauthor Jason Johnston
*/
Ext.define('Ext.tip.ToolTip', {
@@ -36144,71 +37369,70 @@ Ext.define('Ext.tip.ToolTip', {
alias: 'widget.tooltip',
alternateClassName: 'Ext.ToolTip',
/**
+ * @property {HTMLElement} triggerElement
* When a ToolTip is configured with the `{@link #delegate}`
* option to cause selected child elements of the `{@link #target}`
* Element to each trigger a seperate show event, this property is set to
* the DOM element which triggered the show.
- * @type DOMElement
- * @property triggerElement
*/
/**
- * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to monitor
- * for mouseover events to trigger showing this ToolTip.
+ * @cfg {HTMLElement/Ext.Element/String} target
+ * The target element or string id to monitor for mouseover events to trigger
+ * showing this ToolTip.
*/
/**
- * @cfg {Boolean} autoHide True to automatically hide the tooltip after the
+ * @cfg {Boolean} [autoHide=true]
+ * True to automatically hide the tooltip after the
* mouse exits the target element or after the `{@link #dismissDelay}`
- * has expired if set (defaults to true). If `{@link #closable} = true`
+ * has expired if set. If `{@link #closable} = true`
* a close tool button will be rendered into the tooltip header.
*/
/**
- * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays
- * after the mouse enters the target element (defaults to 500)
+ * @cfg {Number} showDelay
+ * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
*/
showDelay: 500,
/**
- * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the
- * target element but before the tooltip actually hides (defaults to 200).
+ * @cfg {Number} hideDelay
+ * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
* Set to 0 for the tooltip to hide immediately.
*/
hideDelay: 200,
/**
- * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip
- * automatically hides (defaults to 5000). To disable automatic hiding, set
+ * @cfg {Number} dismissDelay
+ * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
* dismissDelay = 0.
*/
dismissDelay: 5000,
/**
- * @cfg {Array} mouseOffset An XY offset from the mouse position where the
- * tooltip should be shown (defaults to [15,18]).
+ * @cfg {Number[]} [mouseOffset=[15,18]]
+ * An XY offset from the mouse position where the tooltip should be shown.
*/
/**
- * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it
- * moves over the target element (defaults to false).
+ * @cfg {Boolean} trackMouse
+ * True to have the tooltip follow the mouse as it moves over the target element.
*/
trackMouse: false,
/**
- * @cfg {String} anchor If specified, indicates that the tip should be anchored to a
+ * @cfg {String} anchor
+ * If specified, indicates that the tip should be anchored to a
* particular side of the target element or mouse pointer ("top", "right", "bottom",
* or "left"), with an arrow pointing back at the target or mouse pointer. If
* {@link #constrainPosition} is enabled, this will be used as a preferred value
* only and may be flipped as needed.
*/
/**
- * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target
- * element, false to anchor it relative to the mouse coordinates (defaults
- * to true). When `anchorToTarget` is true, use
- * `{@link #defaultAlign}` to control tooltip alignment to the
- * target element. When `anchorToTarget` is false, use
- * `{@link #anchorPosition}` instead to control alignment.
+ * @cfg {Boolean} anchorToTarget
+ * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
+ * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
+ * target element. When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
*/
anchorToTarget: true,
/**
- * @cfg {Number} anchorOffset A numeric pixel value used to offset the
- * default position of the anchor arrow (defaults to 0). When the anchor
- * position is on the top or bottom of the tooltip, `anchorOffset`
- * will be used as a horizontal offset. Likewise, when the anchor position
- * is on the left or right side, `anchorOffset` will be used as
+ * @cfg {Number} anchorOffset
+ * A numeric pixel value used to offset the default position of the anchor arrow. When the anchor
+ * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
+ * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
* a vertical offset.
*/
anchorOffset: 0,
@@ -36224,7 +37448,7 @@ Ext.define('Ext.tip.ToolTip', {
*
* This may be useful when a Component has regular, repeating elements in it, each of which need a
* ToolTip which contains information specific to that element.
- *
+ *
* See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
*/
@@ -36258,12 +37482,12 @@ Ext.define('Ext.tip.ToolTip', {
me.callParent(arguments);
zIndex = parseInt(me.el.getZIndex(), 10) || 0;
- me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.core.Element.DISPLAY);
+ me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
},
/**
* Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
- * @param {Mixed} t The Element, HtmlElement, or ID of an element to bind to
+ * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
*/
setTarget: function(target) {
var me = this,
@@ -36276,10 +37500,10 @@ Ext.define('Ext.tip.ToolTip', {
me.mun(tg, 'mouseout', me.onTargetOut, me);
me.mun(tg, 'mousemove', me.onMouseMove, me);
}
-
+
me.target = t;
if (t) {
-
+
me.mon(t, {
// TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
// breaking QuickTip#onTargetOver (EXTJSIV-1608)
@@ -36307,7 +37531,7 @@ Ext.define('Ext.tip.ToolTip', {
if (!me.hidden && me.trackMouse) {
xy = me.getTargetXY();
if (me.constrainPosition) {
- xy = me.el.adjustForConstraints(xy, me.el.dom.parentNode);
+ xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
}
me.setPagePosition(xy);
}
@@ -36332,8 +37556,8 @@ Ext.define('Ext.tip.ToolTip', {
me.targetCounter++;
var offsets = me.getOffsets(),
xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
- dw = Ext.core.Element.getViewWidth() - 5,
- dh = Ext.core.Element.getViewHeight() - 5,
+ dw = Ext.Element.getViewWidth() - 5,
+ dh = Ext.Element.getViewHeight() - 5,
de = document.documentElement,
bd = document.body,
scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
@@ -36731,17 +37955,17 @@ Ext.define('Ext.tip.ToolTip', {
* @class Ext.tip.QuickTip
* @extends Ext.tip.ToolTip
* A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
- * {@link Ext.tip.QuickTipManager} instance. See the QuickTipManager class header for additional usage details and examples.
+ * {@link Ext.tip.QuickTipManager} instance. See the QuickTipManager documentation for additional usage details and examples.
* @xtype quicktip
*/
Ext.define('Ext.tip.QuickTip', {
extend: 'Ext.tip.ToolTip',
alternateClassName: 'Ext.QuickTip',
/**
- * @cfg {Mixed} target The target HTMLElement, Ext.core.Element or id to associate with this Quicktip (defaults to the document).
+ * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
*/
/**
- * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available (defaults to false).
+ * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
*/
interceptTitles : false,
@@ -36764,7 +37988,7 @@ Ext.define('Ext.tip.QuickTip', {
// private
initComponent : function(){
var me = this;
-
+
me.target = me.target || Ext.getDoc();
me.targets = me.targets || {};
me.callParent();
@@ -36788,7 +38012,7 @@ Ext.define('Ext.tip.QuickTip', {
i = 0,
len = configs.length,
target, j, targetLen;
-
+
for (; i < len; i++) {
config = configs[i];
target = config.target;
@@ -36806,20 +38030,20 @@ Ext.define('Ext.tip.QuickTip', {
/**
* Removes this quick tip from its element and destroys it.
- * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
+ * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
*/
unregister : function(el){
delete this.targets[Ext.id(el)];
},
-
+
/**
* Hides a visible tip or cancels an impending show for a particular element.
- * @param {String/HTMLElement/Element} el The element that is the target of the tip.
+ * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
*/
cancelShow: function(el){
var me = this,
activeTarget = me.activeTarget;
-
+
el = Ext.get(el).dom;
if (me.isVisible()) {
if (activeTarget && activeTarget.el == el) {
@@ -36829,26 +38053,36 @@ Ext.define('Ext.tip.QuickTip', {
me.clearTimer('show');
}
},
-
+
+ /**
+ * @private
+ * Reads the tip text from the closest node to the event target which contains the attribute we
+ * are configured to look for. Returns an object containing the text from the attribute, and the target element from
+ * which the text was read.
+ */
getTipCfg: function(e) {
var t = e.getTarget(),
- ttp,
+ titleText = t.title,
cfg;
-
- if(this.interceptTitles && t.title && Ext.isString(t.title)){
- ttp = t.title;
- t.qtip = ttp;
+
+ if (this.interceptTitles && titleText && Ext.isString(titleText)) {
+ t.qtip = titleText;
t.removeAttribute("title");
e.preventDefault();
- }
- else {
+ return {
+ text: titleText
+ };
+ }
+ else {
cfg = this.tagConfig;
t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
if (t) {
- ttp = t.getAttribute(cfg.namespace + cfg.attribute);
+ return {
+ target: t,
+ text: t.getAttribute(cfg.namespace + cfg.attribute)
+ };
}
}
- return ttp;
},
// private
@@ -36858,9 +38092,9 @@ Ext.define('Ext.tip.QuickTip', {
elTarget,
cfg,
ns,
- ttp,
+ tipConfig,
autoHide;
-
+
if (me.disabled) {
return;
}
@@ -36873,13 +38107,13 @@ Ext.define('Ext.tip.QuickTip', {
if(!target || target.nodeType !== 1 || target == document || target == document.body){
return;
}
-
+
if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
me.clearTimer('hide');
me.show();
return;
}
-
+
if (target) {
Ext.Object.each(me.targets, function(key, value) {
var targetEl = Ext.fly(value.target);
@@ -36902,21 +38136,28 @@ Ext.define('Ext.tip.QuickTip', {
elTarget = Ext.get(target);
cfg = me.tagConfig;
- ns = cfg.namespace;
- ttp = me.getTipCfg(e);
-
- if (ttp) {
+ ns = cfg.namespace;
+ tipConfig = me.getTipCfg(e);
+
+ if (tipConfig) {
+
+ // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
+ // Change our target element to match that from which the tip text attribute was read.
+ if (tipConfig.target) {
+ target = tipConfig.target;
+ elTarget = Ext.get(target);
+ }
autoHide = elTarget.getAttribute(ns + cfg.hide);
-
+
me.activeTarget = {
el: target,
- text: ttp,
+ text: tipConfig.text,
width: +elTarget.getAttribute(ns + cfg.width) || null,
autoHide: autoHide != "user" && autoHide !== 'false',
title: elTarget.getAttribute(ns + cfg.title),
cls: elTarget.getAttribute(ns + cfg.cls),
align: elTarget.getAttribute(ns + cfg.align)
-
+
};
me.anchor = elTarget.getAttribute(ns + cfg.anchor);
if (me.anchor) {
@@ -36929,7 +38170,7 @@ Ext.define('Ext.tip.QuickTip', {
// private
onTargetOut : function(e){
var me = this;
-
+
// If moving within the current target, and it does not have a new tip, ignore the mouseout
if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
return;
@@ -36945,7 +38186,7 @@ Ext.define('Ext.tip.QuickTip', {
showAt : function(xy){
var me = this,
target = me.activeTarget;
-
+
if (target) {
if (!me.rendered) {
me.render(Ext.getBody());
@@ -36970,7 +38211,7 @@ Ext.define('Ext.tip.QuickTip', {
}
me.setWidth(target.width);
-
+
if (me.anchor) {
me.constrainPosition = false;
} else if (target.align) { // TODO: this doesn't seem to work consistently
@@ -37030,20 +38271,17 @@ Ext.define('Ext.tip.QuickTip', {
*
* Here is an example showing how some of these config options could be used:
*
- * {@img Ext.tip.QuickTipManager/Ext.tip.QuickTipManager.png Ext.tip.QuickTipManager component}
- *
- * ## Code
- *
+ * @example
* // Init the singleton. Any tag-based quick tips will start working.
* Ext.tip.QuickTipManager.init();
- *
+ *
* // Apply a set of config properties to the singleton
* Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
* maxWidth: 200,
* minWidth: 100,
* showDelay: 50 // Show 50ms after entering target
* });
- *
+ *
* // Create a small panel to add a quick tip to
* Ext.create('Ext.container.Container', {
* id: 'quickTipContainer',
@@ -37054,8 +38292,8 @@ Ext.define('Ext.tip.QuickTip', {
* },
* renderTo: Ext.getBody()
* });
- *
- *
+ *
+ *
* // Manually register a quick tip for a specific element
* Ext.tip.QuickTipManager.register({
* target: 'quickTipContainer',
@@ -37076,7 +38314,7 @@ Ext.define('Ext.tip.QuickTip', {
* - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
*
* Here is an example of configuring an HTML element to display a tooltip from markup:
- *
+ *
* // Add a quick tip to an HTML button
*
@@ -37094,9 +38332,9 @@ Ext.define('Ext.tip.QuickTipManager', function() {
/**
* Initialize the global QuickTips instance and prepare any quick tips.
- * @param {Boolean} autoRender True to render the QuickTips container immediately to
+ * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
* preload images. (Defaults to true)
- * @param {Object} config An optional config object for the created QuickTip. By
+ * @param {Object} config (optional) config object for the created QuickTip. By
* default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
* be changed by supplying an xtype property or a className property in this object.
* All other properties on this object are configuration for the created component.
@@ -37213,7 +38451,7 @@ Ext.define('Ext.tip.QuickTipManager', function() {
/**
* Removes any registered quick tip from the target element and destroys it.
- * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
+ * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
*/
unregister : function(){
tip.unregister.apply(tip, arguments);
@@ -37229,12 +38467,9 @@ Ext.define('Ext.tip.QuickTipManager', function() {
};
}());
/**
- * @class Ext.app.Application
- * @extend Ext.app.Controller
- *
* Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
* A typical Ext.app.Application might look like this:
- *
+ *
* Ext.application({
* name: 'MyApp',
* launch: function() {
@@ -37245,54 +38480,56 @@ Ext.define('Ext.tip.QuickTipManager', function() {
* });
* }
* });
- *
+ *
* This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
* as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
* of colliding global variables.
- *
+ *
* When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
* at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
* the example above.
- *
- * Telling Application about the rest of the app
- *
+ *
+ * # Telling Application about the rest of the app
+ *
* Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
* the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
* might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
* Here's how we'd tell our Application about all these things:
- *
+ *
* Ext.application({
* name: 'Blog',
* models: ['Post', 'Comment'],
* controllers: ['Posts', 'Comments'],
- *
+ *
* launch: function() {
* ...
* }
* });
- *
+ *
* Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
- * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
- * Controllers using the pathing conventions laid out in the application
- * architecture guide - in this case expecting the controllers to reside in app/controller/Posts.js and
- * app/controller/Comments.js. In turn, each Controller simply needs to list the Views it uses and they will be
+ * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
+ * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
+ * in this case expecting the controllers to reside in `app/controller/Posts.js` and
+ * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
* automatically loaded. Here's how our Posts controller like be defined:
- *
+ *
* Ext.define('MyApp.controller.Posts', {
* extend: 'Ext.app.Controller',
* views: ['posts.List', 'posts.Edit'],
- *
+ *
* //the rest of the Controller here
* });
- *
+ *
* Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
* automatically load all of our app files for us. This means we don't have to manually add script tags into our html
- * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
+ * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
* application using the Ext JS 4 SDK Tools.
- *
+ *
* For more information about writing Ext JS 4 applications, please see the
- * [application architecture guide](#/guide/application_architecture).
- *
+ * [application architecture guide][mvc].
+ *
+ * [mvc]: #!/guide/application_architecture
+ *
* @docauthor Ed Spencer
*/
Ext.define('Ext.app.Application', {
@@ -37319,30 +38556,29 @@ Ext.define('Ext.app.Application', {
scope: undefined,
/**
- * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support (defaults to true)
+ * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
*/
enableQuickTips: true,
/**
- * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to. Defaults to undefined
+ * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
*/
/**
* @cfg {String} appFolder The path to the directory which contains all application's classes.
* This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
- * Defaults to 'app'
*/
appFolder: 'app',
/**
* @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
- * before firing the launch function (defaults to false).
+ * before firing the launch function.
*/
autoCreateViewport: false,
/**
* Creates new Application.
- * @param {Object} config (optional) Config object.
+ * @param {Object} [config] Config object.
*/
constructor: function(config) {
config = config || {};
@@ -37708,7 +38944,7 @@ Ext.define('Ext.draw.CompositeSprite', {
});
},
- /** Add a Sprite to the Group */
+ // Inherit docs from MixedCollection
add: function(key, o) {
var result = this.callParent(arguments);
this.attachEvents(result);
@@ -37719,7 +38955,7 @@ Ext.define('Ext.draw.CompositeSprite', {
return this.callParent(arguments);
},
- /** Remove a Sprite from the Group */
+ // Inherit docs from MixedCollection
remove: function(o) {
var me = this;
@@ -37731,13 +38967,14 @@ Ext.define('Ext.draw.CompositeSprite', {
mouseout: me.onMouseOut,
click: me.onClick
});
- me.callParent(arguments);
+ return me.callParent(arguments);
},
/**
* Returns the group bounding box.
- * Behaves like {@link Ext.draw.Sprite} getBBox method.
- */
+ * Behaves like {@link Ext.draw.Sprite#getBBox} method.
+ * @return {Object} an object with x, y, width, and height properties.
+ */
getBBox: function() {
var i = 0,
sprite,
@@ -37771,10 +39008,11 @@ Ext.define('Ext.draw.CompositeSprite', {
},
/**
- * Iterates through all sprites calling
- * `setAttributes` on each one. For more information
- * {@link Ext.draw.Sprite} provides a description of the
- * attributes that can be set with this method.
+ * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
+ * provides a description of the attributes that can be set with this method.
+ * @param {Object} attrs Attributes to be changed on the sprite.
+ * @param {Boolean} redraw Flag to immediatly draw the change.
+ * @return {Ext.draw.CompositeSprite} this
*/
setAttributes: function(attrs, redraw) {
var i = 0,
@@ -37790,6 +39028,8 @@ Ext.define('Ext.draw.CompositeSprite', {
/**
* Hides all sprites. If the first parameter of the method is true
* then a redraw will be forced for each sprite.
+ * @param {Boolean} redraw Flag to immediatly draw the change.
+ * @return {Ext.draw.CompositeSprite} this
*/
hide: function(redraw) {
var i = 0,
@@ -37805,6 +39045,8 @@ Ext.define('Ext.draw.CompositeSprite', {
/**
* Shows all sprites. If the first parameter of the method is true
* then a redraw will be forced for each sprite.
+ * @param {Boolean} redraw Flag to immediatly draw the change.
+ * @return {Ext.draw.CompositeSprite} this
*/
show: function(redraw) {
var i = 0,
@@ -37906,27 +39148,28 @@ Ext.define('Ext.draw.CompositeSprite', {
});
/**
- * @class Ext.layout.component.Draw
+ * @class Ext.layout.component.Auto
* @extends Ext.layout.component.Component
* @private
*
+ * The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
+ * render any child Elements when no {@link Ext.container.Container#layout layout} is configured.
*/
-Ext.define('Ext.layout.component.Draw', {
+Ext.define('Ext.layout.component.Auto', {
/* Begin Definitions */
- alias: 'layout.draw',
+ alias: 'layout.autocomponent',
- extend: 'Ext.layout.component.Auto',
+ extend: 'Ext.layout.component.Component',
/* End Definitions */
- type: 'draw',
+ type: 'autocomponent',
onLayout : function(width, height) {
- this.owner.surface.setSize(width, height);
- this.callParent(arguments);
+ this.setTargetSize(width, height);
}
});
/**
@@ -38218,6 +39461,7 @@ function() {
*
*/
Ext.define('Ext.chart.Mask', {
+ require: ['Ext.chart.MaskLayer'],
/**
* Creates new Mask.
* @param {Object} config (optional) Config object.
@@ -38364,12 +39608,7 @@ Ext.define('Ext.chart.Mask', {
width: abs(width),
height: abs(height)
};
- me.mask.updateBox({
- x: posX - abs(width),
- y: posY - abs(height),
- width: abs(width),
- height: abs(height)
- });
+ me.mask.updateBox(me.maskSelection);
me.mask.show();
me.maskSprite.setAttributes({
hidden: true
@@ -38408,7 +39647,7 @@ Ext.define('Ext.chart.Mask', {
* @class Ext.chart.Navigation
*
* Handles panning and zooming capabilities.
- *
+ *
* Used as mixin by Ext.chart.Chart.
*/
Ext.define('Ext.chart.Navigation', {
@@ -38416,49 +39655,68 @@ Ext.define('Ext.chart.Navigation', {
constructor: function() {
this.originalStore = this.store;
},
-
- //filters the store to the specified interval(s)
+
+ /**
+ * Zooms the chart to the specified selection range.
+ * Can be used with a selection mask. For example:
+ *
+ * items: {
+ * xtype: 'chart',
+ * animate: true,
+ * store: store1,
+ * mask: 'horizontal',
+ * listeners: {
+ * select: {
+ * fn: function(me, selection) {
+ * me.setZoom(selection);
+ * me.mask.hide();
+ * }
+ * }
+ * }
+ * }
+ */
setZoom: function(zoomConfig) {
var me = this,
- store = me.substore || me.store,
+ axes = me.axes,
bbox = me.chartBBox,
- len = store.getCount(),
- from = (zoomConfig.x / bbox.width * len) >> 0,
- to = Math.ceil(((zoomConfig.x + zoomConfig.width) / bbox.width * len)),
- recFieldsLen, recFields = [], curField, json = [], obj;
-
- store.each(function(rec, i) {
- if (i < from || i > to) {
- return;
- }
- obj = {};
- //get all record field names in a simple array
- if (!recFields.length) {
- rec.fields.each(function(f) {
- recFields.push(f.name);
- });
- recFieldsLen = recFields.length;
- }
- //append record values to an aggregation record
- for (i = 0; i < recFieldsLen; i++) {
- curField = recFields[i];
- obj[curField] = rec.get(curField);
+ xScale = 1 / bbox.width,
+ yScale = 1 / bbox.height,
+ zoomer = {
+ x : zoomConfig.x * xScale,
+ y : zoomConfig.y * yScale,
+ width : zoomConfig.width * xScale,
+ height : zoomConfig.height * yScale
+ };
+ axes.each(function(axis) {
+ var ends = axis.calcEnds();
+ if (axis.position == 'bottom' || axis.position == 'top') {
+ var from = (ends.to - ends.from) * zoomer.x + ends.from,
+ to = (ends.to - ends.from) * zoomer.width + from;
+ axis.minimum = from;
+ axis.maximum = to;
+ } else {
+ var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
+ from = to - (ends.to - ends.from) * zoomer.height;
+ axis.minimum = from;
+ axis.maximum = to;
}
- json.push(obj);
- });
- me.store = me.substore = Ext.create('Ext.data.JsonStore', {
- fields: recFields,
- data: json
});
- me.redraw(true);
+ me.redraw(false);
},
+ /**
+ * Restores the zoom to the original value. This can be used to reset
+ * the previous zoom state set by `setZoom`. For example:
+ *
+ * myChart.restoreZoom();
+ */
restoreZoom: function() {
this.store = this.substore = this.originalStore;
this.redraw(true);
}
-
+
});
+
/**
* @class Ext.chart.Shape
* @ignore
@@ -38605,7 +39863,7 @@ Ext.define('Ext.chart.Shape', {
*
* drawComponent.surface.on({
* 'mousemove': function() {
- * console.log('moving the mouse over the surface');
+ * console.log('moving the mouse over the surface');
* }
* });
*
@@ -38616,7 +39874,7 @@ Ext.define('Ext.chart.Shape', {
* height: 600,
* renderTo: document.body
* }), surface = drawComponent.surface;
- *
+ *
* surface.add([{
* type: 'circle',
* radius: 10,
@@ -38663,7 +39921,7 @@ Ext.define('Ext.chart.Shape', {
* y: 100,
* group: 'rectangles'
* }]);
- *
+ *
* // Get references to my groups
* circles = surface.getGroup('circles');
* rectangles = surface.getGroup('rectangles');
@@ -38677,7 +39935,7 @@ Ext.define('Ext.chart.Shape', {
* }
* }
* });
- *
+ *
* // Animate the rectangles across
* rectangles.animate({
* duration: 1000,
@@ -38705,7 +39963,7 @@ Ext.define('Ext.draw.Surface', {
/**
* Creates and returns a new concrete Surface instance appropriate for the current environment.
* @param {Object} config Initial configuration for the Surface instance
- * @param {[String]} enginePriority Optional order of implementations to use; the first one that is
+ * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
* available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
* @return {Object} The created Surface or false.
* @static
@@ -38774,12 +40032,10 @@ Ext.define('Ext.draw.Surface', {
/**
* @cfg {Number} height
* The height of this component in pixels (defaults to auto).
- * **Note** to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
*/
/**
* @cfg {Number} width
* The width of this component in pixels (defaults to auto).
- * **Note** to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
*/
container: undefined,
@@ -38788,6 +40044,13 @@ Ext.define('Ext.draw.Surface', {
x: 0,
y: 0,
+ /**
+ * @private Flag indicating that the surface implementation requires sprites to be maintained
+ * in order of their zIndex. Impls that don't require this can set it to false.
+ */
+ orderSpritesByZIndex: true,
+
+
/**
* Creates new Surface.
* @param {Object} config (optional) Config object.
@@ -38798,7 +40061,7 @@ Ext.define('Ext.draw.Surface', {
Ext.apply(me, config);
me.domRef = Ext.getDoc().dom;
-
+
me.customAttributes = {};
me.addEvents(
@@ -38836,7 +40099,12 @@ Ext.define('Ext.draw.Surface', {
renderItems: Ext.emptyFn,
// @private
- setViewBox: Ext.emptyFn,
+ setViewBox: function (x, y, width, height) {
+ if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
+ this.viewBox = {x: x, y: y, width: width, height: height};
+ this.applyViewBox();
+ }
+ },
/**
* Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
@@ -38846,7 +40114,7 @@ Ext.define('Ext.draw.Surface', {
* drawComponent.surface.addCls(sprite, 'x-visible');
*
* @param {Object} sprite The sprite to add the class to.
- * @param {String/[String]} className The CSS class to add, or an array of classes
+ * @param {String/String[]} className The CSS class to add, or an array of classes
* @method
*/
addCls: Ext.emptyFn,
@@ -38859,7 +40127,7 @@ Ext.define('Ext.draw.Surface', {
* drawComponent.surface.removeCls(sprite, 'x-visible');
*
* @param {Object} sprite The sprite to remove the class from.
- * @param {String/[String]} className The CSS class to remove, or an array of classes
+ * @param {String/String[]} className The CSS class to remove, or an array of classes
* @method
*/
removeCls: Ext.emptyFn,
@@ -38896,7 +40164,7 @@ Ext.define('Ext.draw.Surface', {
this.add(items);
}
},
-
+
// @private
initBackground: function(config) {
var me = this,
@@ -38937,7 +40205,7 @@ Ext.define('Ext.draw.Surface', {
}
}
},
-
+
/**
* Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
*
@@ -38946,7 +40214,7 @@ Ext.define('Ext.draw.Surface', {
* drawComponent.surface.setSize(500, 500);
*
* This method is generally called when also setting the size of the draw Component.
- *
+ *
* @param {Number} w The new width of the canvas.
* @param {Number} h The new height of the canvas.
*/
@@ -38958,6 +40226,7 @@ Ext.define('Ext.draw.Surface', {
hidden: false
}, true);
}
+ this.applyViewBox();
},
// @private
@@ -38966,7 +40235,7 @@ Ext.define('Ext.draw.Surface', {
attrs = {},
exclude = {},
sattr = sprite.attr;
- for (i in sattr) {
+ for (i in sattr) {
// Narrow down attributes to the main set
if (this.translateAttrs.hasOwnProperty(i)) {
// Translated attr
@@ -39026,7 +40295,7 @@ Ext.define('Ext.draw.Surface', {
* configuration object please refer to {@link Ext.chart.Chart}.
*
* The gradient object to be passed into this method is composed by:
- *
+ *
* - **id** - string - The unique name of the gradient.
* - **angle** - number, optional - The angle of the gradient in degrees.
* - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
@@ -39085,38 +40354,53 @@ Ext.define('Ext.draw.Surface', {
return results;
}
sprite = this.prepareItems(args[0], true)[0];
- this.normalizeSpriteCollection(sprite);
+ this.insertByZIndex(sprite);
this.onAdd(sprite);
return sprite;
},
/**
* @private
- * Inserts or moves a given sprite into the correct position in the items
- * MixedCollection, according to its zIndex. Will be inserted at the end of
- * an existing series of sprites with the same or lower zIndex. If the sprite
- * is already positioned within an appropriate zIndex group, it will not be moved.
- * This ordering can be used by subclasses to assist in rendering the sprites in
- * the correct order for proper z-index stacking.
+ * Inserts a given sprite into the correct position in the items collection, according to
+ * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
+ * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
+ * the sprites in the correct order for proper z-index stacking.
* @param {Ext.draw.Sprite} sprite
* @return {Number} the sprite's new index in the list
*/
- normalizeSpriteCollection: function(sprite) {
- var items = this.items,
+ insertByZIndex: function(sprite) {
+ var me = this,
+ sprites = me.items.items,
+ len = sprites.length,
+ ceil = Math.ceil,
zIndex = sprite.attr.zIndex,
- idx = items.indexOf(sprite);
-
- if (idx < 0 || (idx > 0 && items.getAt(idx - 1).attr.zIndex > zIndex) ||
- (idx < items.length - 1 && items.getAt(idx + 1).attr.zIndex < zIndex)) {
- items.removeAt(idx);
- idx = items.findIndexBy(function(otherSprite) {
- return otherSprite.attr.zIndex > zIndex;
- });
- if (idx < 0) {
- idx = items.length;
+ idx = len,
+ high = idx - 1,
+ low = 0,
+ otherZIndex;
+
+ if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
+ // Find the target index via a binary search for speed
+ while (low <= high) {
+ idx = ceil((low + high) / 2);
+ otherZIndex = sprites[idx].attr.zIndex;
+ if (otherZIndex > zIndex) {
+ high = idx - 1;
+ }
+ else if (otherZIndex < zIndex) {
+ low = idx + 1;
+ }
+ else {
+ break;
+ }
+ }
+ // Step forward to the end of a sequence of the same or lower z-index
+ while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
+ idx++;
}
- items.insert(idx, sprite);
}
+
+ me.items.insert(idx, sprite);
return idx;
},
@@ -39188,6 +40472,52 @@ Ext.define('Ext.draw.Surface', {
onDestroy: Ext.emptyFn,
+ /**
+ * @private Using the current viewBox property and the surface's width and height, calculate the
+ * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
+ */
+ applyViewBox: function() {
+ var me = this,
+ viewBox = me.viewBox,
+ width = me.width,
+ height = me.height,
+ viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
+ relativeHeight, relativeWidth, size;
+
+ if (viewBox && (width || height)) {
+ viewBoxX = viewBox.x;
+ viewBoxY = viewBox.y;
+ viewBoxWidth = viewBox.width;
+ viewBoxHeight = viewBox.height;
+ relativeHeight = height / viewBoxHeight;
+ relativeWidth = width / viewBoxWidth;
+
+ if (viewBoxWidth * relativeHeight < width) {
+ viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
+ }
+ if (viewBoxHeight * relativeWidth < height) {
+ viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
+ }
+
+ size = 1 / Math.min(viewBoxWidth, relativeHeight);
+
+ me.viewBoxShift = {
+ dx: -viewBoxX,
+ dy: -viewBoxY,
+ scale: size
+ };
+ }
+ },
+
+ transformToViewBox: function (x, y) {
+ if (this.viewBoxShift) {
+ var me = this, shift = me.viewBoxShift;
+ return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
+ } else {
+ return [x, y];
+ }
+ },
+
// @private
applyTransformations: function(sprite) {
sprite.bbox.transform = 0;
@@ -39373,7 +40703,7 @@ Ext.define('Ext.draw.Surface', {
}
return items;
},
-
+
/**
* Changes the text in the sprite element. The sprite must be a `text` sprite.
* This method can also be called from {@link Ext.draw.Sprite}.
@@ -39387,7 +40717,7 @@ Ext.define('Ext.draw.Surface', {
* @method
*/
setText: Ext.emptyFn,
-
+
//@private Creates an item and appends it to the surface. Called
//as an internal method when calling `add`.
createItem: Ext.emptyFn,
@@ -39413,6 +40743,30 @@ Ext.define('Ext.draw.Surface', {
this.removeAll();
}
});
+/**
+ * @class Ext.layout.component.Draw
+ * @extends Ext.layout.component.Component
+ * @private
+ *
+ */
+
+Ext.define('Ext.layout.component.Draw', {
+
+ /* Begin Definitions */
+
+ alias: 'layout.draw',
+
+ extend: 'Ext.layout.component.Auto',
+
+ /* End Definitions */
+
+ type: 'draw',
+
+ onLayout : function(width, height) {
+ this.owner.surface.setSize(width, height);
+ this.callParent(arguments);
+ }
+});
/**
* @class Ext.draw.Component
* @extends Ext.Component
@@ -39421,9 +40775,10 @@ Ext.define('Ext.draw.Surface', {
* manages and holds a `Surface` instance: an interface that has
* an SVG or VML implementation depending on the browser capabilities and where
* Sprites can be appended.
- * {@img Ext.draw.Component/Ext.draw.Component.png Ext.draw.Component component}
+ *
* One way to create a draw component is:
- *
+ *
+ * @example
* var drawComponent = Ext.create('Ext.draw.Component', {
* viewBox: false,
* items: [{
@@ -39434,21 +40789,21 @@ Ext.define('Ext.draw.Surface', {
* y: 100
* }]
* });
- *
+ *
* Ext.create('Ext.Window', {
* width: 215,
* height: 235,
* layout: 'fit',
* items: [drawComponent]
* }).show();
- *
+ *
* In this case we created a draw component and added a sprite to it.
* The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
* circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
- * dimensions accordingly.
- *
+ * dimensions accordingly.
+ *
* You can also add sprites by using the surface's add method:
- *
+ *
* drawComponent.surface.add({
* type: 'circle',
* fill: '#79BB3F',
@@ -39456,7 +40811,7 @@ Ext.define('Ext.draw.Surface', {
* x: 100,
* y: 100
* });
- *
+ *
* For more information on Sprites, the core elements added to a draw component's surface,
* refer to the Ext.draw.Sprite documentation.
*/
@@ -39476,7 +40831,7 @@ Ext.define('Ext.draw.Component', {
/* End Definitions */
/**
- * @cfg {Array} enginePriority
+ * @cfg {String[]} enginePriority
* Defines the priority order for which Surface implementation to use. The first
* one supported by the current environment will be used.
*/
@@ -39498,60 +40853,50 @@ Ext.define('Ext.draw.Component', {
* Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
*/
autoSize: false,
-
+
/**
- * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
+ * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
* The gradients array is an array of objects with the following properties:
*
- *
- * id - string - The unique name of the gradient.
- * angle - number, optional - The angle of the gradient in degrees.
- * stops - object - An object with numbers as keys (from 0 to 100) and style objects
- * as values
- *
- *
-
- For example:
-
-
- gradients: [{
- id: 'gradientId',
- angle: 45,
- stops: {
- 0: {
- color: '#555'
- },
- 100: {
- color: '#ddd'
- }
- }
- }, {
- id: 'gradientId2',
- angle: 0,
- stops: {
- 0: {
- color: '#590'
- },
- 20: {
- color: '#599'
- },
- 100: {
- color: '#ddd'
- }
- }
- }]
-
-
- Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
-
-
- sprite.setAttributes({
- fill: 'url(#gradientId)'
- }, true);
-
-
+ * - `id` - string - The unique name of the gradient.
+ * - `angle` - number, optional - The angle of the gradient in degrees.
+ * - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
+ *
+ * ## Example
+ *
+ * gradients: [{
+ * id: 'gradientId',
+ * angle: 45,
+ * stops: {
+ * 0: {
+ * color: '#555'
+ * },
+ * 100: {
+ * color: '#ddd'
+ * }
+ * }
+ * }, {
+ * id: 'gradientId2',
+ * angle: 0,
+ * stops: {
+ * 0: {
+ * color: '#590'
+ * },
+ * 20: {
+ * color: '#599'
+ * },
+ * 100: {
+ * color: '#ddd'
+ * }
+ * }
+ * }]
+ *
+ * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
+ *
+ * sprite.setAttributes({
+ * fill: 'url(#gradientId)'
+ * }, true);
*/
-
initComponent: function() {
this.callParent(arguments);
@@ -39577,22 +40922,22 @@ Ext.define('Ext.draw.Component', {
bbox, items, width, height, x, y;
me.callParent(arguments);
- me.createSurface();
+ if (me.createSurface() !== false) {
+ items = me.surface.items;
- items = me.surface.items;
-
- if (viewBox || autoSize) {
- bbox = items.getBBox();
- width = bbox.width;
- height = bbox.height;
- x = bbox.x;
- y = bbox.y;
- if (me.viewBox) {
- me.surface.setViewBox(x, y, width, height);
- }
- else {
- // AutoSized
- me.autoSizeSurface();
+ if (viewBox || autoSize) {
+ bbox = items.getBBox();
+ width = bbox.width;
+ height = bbox.height;
+ x = bbox.x;
+ y = bbox.y;
+ if (me.viewBox) {
+ me.surface.setViewBox(x, y, width, height);
+ }
+ else {
+ // AutoSized
+ me.autoSizeSurface();
+ }
}
}
},
@@ -39626,9 +40971,7 @@ Ext.define('Ext.draw.Component', {
* instantiate based on the 'enginePriority' config. Once the Surface instance is
* created you can use the handle to that instance to add sprites. For example:
*
-
- drawComponent.surface.add(sprite);
-
+ * drawComponent.surface.add(sprite);
*/
createSurface: function() {
var surface = Ext.draw.Surface.create(Ext.apply({}, {
@@ -39636,8 +40979,13 @@ Ext.define('Ext.draw.Component', {
height: this.height,
renderTo: this.el
}, this.initialConfig));
+ if (!surface) {
+ // In case we cannot create a surface, return false so we can stop
+ return false;
+ }
this.surface = surface;
+
function refire(eventName) {
return function(e) {
this.fireEvent(eventName, e);
@@ -39658,7 +41006,7 @@ Ext.define('Ext.draw.Component', {
/**
* @private
- *
+ *
* Clean up the Surface instance on component destruction
*/
onDestroy: function() {
@@ -39881,6 +41229,7 @@ Ext.define('Ext.chart.LegendItem', {
}
}
});
+
/**
* @class Ext.chart.Legend
*
@@ -39897,72 +41246,74 @@ Ext.define('Ext.chart.LegendItem', {
* legend: {
* position: 'right'
* },
- *
- * Full example:
-
- var store = Ext.create('Ext.data.JsonStore', {
- fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- data: [
- {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
- ]
- });
-
- Ext.create('Ext.chart.Chart', {
- renderTo: Ext.getBody(),
- width: 500,
- height: 300,
- animate: true,
- store: store,
- shadow: true,
- theme: 'Category1',
- legend: {
- position: 'top'
- },
- axes: [{
- type: 'Numeric',
- grid: true,
- position: 'left',
- fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
- title: 'Sample Values',
- grid: {
- odd: {
- opacity: 1,
- fill: '#ddd',
- stroke: '#bbb',
- 'stroke-width': 1
- }
- },
- minimum: 0,
- adjustMinimumByMajorUnit: 0
- }, {
- type: 'Category',
- position: 'bottom',
- fields: ['name'],
- title: 'Sample Metrics',
- grid: true,
- label: {
- rotate: {
- degrees: 315
- }
- }
- }],
- series: [{
- type: 'area',
- highlight: false,
- axis: 'left',
- xField: 'name',
- yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
- style: {
- opacity: 0.93
- }
- }]
- });
-
*
+ * ## Example
+ *
+ * @example
+ * var store = Ext.create('Ext.data.JsonStore', {
+ * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ * data: [
+ * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
+ * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
+ * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
+ * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
+ * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
+ * ]
+ * });
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 500,
+ * height: 300,
+ * animate: true,
+ * store: store,
+ * shadow: true,
+ * theme: 'Category1',
+ * legend: {
+ * position: 'top'
+ * },
+ * axes: [
+ * {
+ * type: 'Numeric',
+ * grid: true,
+ * position: 'left',
+ * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * title: 'Sample Values',
+ * grid: {
+ * odd: {
+ * opacity: 1,
+ * fill: '#ddd',
+ * stroke: '#bbb',
+ * 'stroke-width': 1
+ * }
+ * },
+ * minimum: 0,
+ * adjustMinimumByMajorUnit: 0
+ * },
+ * {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics',
+ * grid: true,
+ * label: {
+ * rotate: {
+ * degrees: 315
+ * }
+ * }
+ * }
+ * ],
+ * series: [{
+ * type: 'area',
+ * highlight: false,
+ * axis: 'left',
+ * xField: 'name',
+ * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * style: {
+ * opacity: 0.93
+ * }
+ * }]
+ * });
*/
Ext.define('Ext.chart.Legend', {
@@ -39988,7 +41339,7 @@ Ext.define('Ext.chart.Legend', {
/**
* @cfg {Number} x
- * X-position of the legend box. Used directly if position is set to "float", otherwise
+ * X-position of the legend box. Used directly if position is set to "float", otherwise
* it will be calculated dynamically.
*/
x: 0,
@@ -40062,7 +41413,7 @@ Ext.define('Ext.chart.Legend', {
* @type {Boolean}
*/
me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
-
+
// cache these here since they may get modified later on
me.origX = me.x;
me.origY = me.y;
@@ -40073,9 +41424,9 @@ Ext.define('Ext.chart.Legend', {
*/
create: function() {
var me = this;
+ me.createBox();
me.createItems();
if (!me.created && me.isDisplayed()) {
- me.createBox();
me.created = true;
// Listen for changes to series titles to trigger regeneration of the legend
@@ -40115,8 +41466,8 @@ Ext.define('Ext.chart.Legend', {
math = Math,
mfloor = math.floor,
mmax = math.max,
- index = 0,
- i = 0,
+ index = 0,
+ i = 0,
len = items ? items.length : 0,
x, y, spacing, item, bbox, height, width;
@@ -40142,7 +41493,7 @@ Ext.define('Ext.chart.Legend', {
bbox = item.getBBox();
//always measure from x=0, since not all markers go all the way to the left
- width = bbox.width;
+ width = bbox.width;
height = bbox.height;
if (i + j === 0) {
@@ -40193,13 +41544,20 @@ Ext.define('Ext.chart.Legend', {
*/
createBox: function() {
var me = this,
- box = me.boxSprite = me.chart.surface.add(Ext.apply({
- type: 'rect',
- stroke: me.boxStroke,
- "stroke-width": me.boxStrokeWidth,
- fill: me.boxFill,
- zIndex: me.boxZIndex
- }, me.getBBox()));
+ box;
+
+ if (me.boxSprite) {
+ me.boxSprite.destroy();
+ }
+
+ box = me.boxSprite = me.chart.surface.add(Ext.apply({
+ type: 'rect',
+ stroke: me.boxStroke,
+ "stroke-width": me.boxStrokeWidth,
+ fill: me.boxFill,
+ zIndex: me.boxZIndex
+ }, me.getBBox()));
+
box.redraw();
},
@@ -40221,7 +41579,7 @@ Ext.define('Ext.chart.Legend', {
chartY = chartBBox.y + insets,
surface = chart.surface,
mfloor = Math.floor;
-
+
if (me.isDisplayed()) {
// Find the position based on the dimensions
switch(me.position) {
@@ -40257,37 +41615,143 @@ Ext.define('Ext.chart.Legend', {
}
}
});
+
/**
- * @class Ext.chart.Chart
- * @extends Ext.draw.Component
- *
- * The Ext.chart package provides the capability to visualize data.
- * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.
- * A chart configuration object has some overall styling options as well as an array of axes
- * and series. A chart instance example could look like:
- *
-
- Ext.create('Ext.chart.Chart', {
- renderTo: Ext.getBody(),
- width: 800,
- height: 600,
- animate: true,
- store: store1,
- shadow: true,
- theme: 'Category1',
- legend: {
- position: 'right'
- },
- axes: [ ...some axes options... ],
- series: [ ...some series options... ]
- });
-
- *
- * In this example we set the `width` and `height` of the chart, we decide whether our series are
- * animated or not and we select a store to be bound to the chart. We also turn on shadows for all series,
- * select a color theme `Category1` for coloring the series, set the legend to the right part of the chart and
- * then tell the chart to render itself in the body element of the document. For more information about the axes and
- * series configurations please check the documentation of each series (Line, Bar, Pie, etc).
+ * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
+ * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
+ * updates its display whenever data in the Store changes. In addition, the look and feel
+ * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
+ *
+ * ## Creating a Simple Chart
+ *
+ * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
+ * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
+ * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
+ *
+ * ### 1. Creating a Store
+ *
+ * The first step is to create a {@link Ext.data.Model Model} that represents the type of
+ * data that will be displayed in the Chart. For example the data for a chart that displays
+ * a weather forecast could be represented as a series of "WeatherPoint" data points with
+ * two fields - "temperature", and "date":
+ *
+ * Ext.define('WeatherPoint', {
+ * extend: 'Ext.data.Model',
+ * fields: ['temperature', 'date']
+ * });
+ *
+ * Next a {@link Ext.data.Store Store} must be created. The store contains a collection of "WeatherPoint" Model instances.
+ * The data could be loaded dynamically, but for sake of ease this example uses inline data:
+ *
+ * var store = Ext.create('Ext.data.Store', {
+ * model: 'WeatherPoint',
+ * data: [
+ * { temperature: 58, date: new Date(2011, 1, 1, 8) },
+ * { temperature: 63, date: new Date(2011, 1, 1, 9) },
+ * { temperature: 73, date: new Date(2011, 1, 1, 10) },
+ * { temperature: 78, date: new Date(2011, 1, 1, 11) },
+ * { temperature: 81, date: new Date(2011, 1, 1, 12) }
+ * ]
+ * });
+ *
+ * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
+ *
+ * ### 2. Creating the Chart object
+ *
+ * Now that a Store has been created it can be used in a Chart:
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 400,
+ * height: 300,
+ * store: store
+ * });
+ *
+ * That's all it takes to create a Chart instance that is backed by a Store.
+ * However, if the above code is run in a browser, a blank screen will be displayed.
+ * This is because the two pieces that are responsible for the visual display,
+ * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
+ *
+ * ### 3. Configuring the Axes
+ *
+ * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
+ * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * ...
+ * axes: [
+ * {
+ * title: 'Temperature',
+ * type: 'Numeric',
+ * position: 'left',
+ * fields: ['temperature'],
+ * minimum: 0,
+ * maximum: 100
+ * },
+ * {
+ * title: 'Time',
+ * type: 'Time',
+ * position: 'bottom',
+ * fields: ['date'],
+ * dateFormat: 'ga'
+ * }
+ * ]
+ * });
+ *
+ * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
+ * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
+ * defined above. The minimum value for this axis is "0", and the maximum is "100".
+ *
+ * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
+ * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
+ * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
+ * configuration tells the Time Axis how to format it's labels.
+ *
+ * Here's what the Chart looks like now that it has its Axes configured:
+ *
+ * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
+ *
+ * ### 4. Configuring the Series
+ *
+ * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
+ * Series are responsible for the visual representation of the data points contained in the Store.
+ * This example only has one Series:
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * ...
+ * axes: [
+ * ...
+ * ],
+ * series: [
+ * {
+ * type: 'line',
+ * xField: 'date',
+ * yField: 'temperature'
+ * }
+ * ]
+ * });
+ *
+ * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
+ * from the "WeatherPoint" Models in the Store to plot its data points:
+ *
+ * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
+ *
+ * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
+ *
+ * ## Themes
+ *
+ * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * ...
+ * theme: 'Green',
+ * ...
+ * });
+ *
+ * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
+ *
+ * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
+ *
*/
Ext.define('Ext.chart.Chart', {
@@ -40316,134 +41780,166 @@ Ext.define('Ext.chart.Chart', {
viewBox: false,
/**
- * @cfg {String} theme (optional) The name of the theme to be used. A theme defines the colors and
- * other visual displays of tick marks on axis, text, title text, line colors, marker colors and styles, etc.
- * Possible theme values are 'Base', 'Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes
- * 'Category1' to 'Category6'. Default value is 'Base'.
+ * @cfg {String} theme
+ * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
+ * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
+ * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
+ * is 'Base'.
*/
/**
- * @cfg {Boolean/Object} animate (optional) true for the default animation (easing: 'ease' and duration: 500)
- * or a standard animation config object to be used for default chart animations. Defaults to false.
+ * @cfg {Boolean/Object} animate
+ * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
+ * object to be used for default chart animations. Defaults to false.
*/
animate: false,
/**
- * @cfg {Boolean/Object} legend (optional) true for the default legend display or a legend config object. Defaults to false.
+ * @cfg {Boolean/Object} legend
+ * True for the default legend display or a legend config object. Defaults to false.
*/
legend: false,
/**
- * @cfg {integer} insetPadding (optional) Set the amount of inset padding in pixels for the chart. Defaults to 10.
+ * @cfg {Number} insetPadding
+ * The amount of inset padding in pixels for the chart. Defaults to 10.
*/
insetPadding: 10,
/**
- * @cfg {Array} enginePriority
- * Defines the priority order for which Surface implementation to use. The first
- * one supported by the current environment will be used.
+ * @cfg {String[]} enginePriority
+ * Defines the priority order for which Surface implementation to use. The first one supported by the current
+ * environment will be used. Defaults to `['Svg', 'Vml']`.
*/
enginePriority: ['Svg', 'Vml'],
/**
- * @cfg {Object|Boolean} background (optional) Set the chart background. This can be a gradient object, image, or color.
- * Defaults to false for no background.
+ * @cfg {Object/Boolean} background
+ * The chart background. This can be a gradient object, image, or color. Defaults to false for no
+ * background. For example, if `background` were to be a color we could set the object as
*
- * For example, if `background` were to be a color we could set the object as
+ * background: {
+ * //color string
+ * fill: '#ccc'
+ * }
*
-
- background: {
- //color string
- fill: '#ccc'
- }
-
-
- You can specify an image by using:
-
-
- background: {
- image: 'http://path.to.image/'
- }
-
-
- Also you can specify a gradient by using the gradient object syntax:
-
-
- background: {
- gradient: {
- id: 'gradientId',
- angle: 45,
- stops: {
- 0: {
- color: '#555'
- }
- 100: {
- color: '#ddd'
- }
- }
- }
- }
-
+ * You can specify an image by using:
+ *
+ * background: {
+ * image: 'http://path.to.image/'
+ * }
+ *
+ * Also you can specify a gradient by using the gradient object syntax:
+ *
+ * background: {
+ * gradient: {
+ * id: 'gradientId',
+ * angle: 45,
+ * stops: {
+ * 0: {
+ * color: '#555'
+ * }
+ * 100: {
+ * color: '#ddd'
+ * }
+ * }
+ * }
+ * }
*/
background: false,
/**
- * @cfg {Array} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
- * The gradients array is an array of objects with the following properties:
+ * @cfg {Object[]} gradients
+ * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
+ * array of objects with the following properties:
*
- *
- * id - string - The unique name of the gradient.
- * angle - number, optional - The angle of the gradient in degrees.
- * stops - object - An object with numbers as keys (from 0 to 100) and style objects
- * as values
- *
+ * - **id** - string - The unique name of the gradient.
+ * - **angle** - number, optional - The angle of the gradient in degrees.
+ * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
*
+ * For example:
+ *
+ * gradients: [{
+ * id: 'gradientId',
+ * angle: 45,
+ * stops: {
+ * 0: {
+ * color: '#555'
+ * },
+ * 100: {
+ * color: '#ddd'
+ * }
+ * }
+ * }, {
+ * id: 'gradientId2',
+ * angle: 0,
+ * stops: {
+ * 0: {
+ * color: '#590'
+ * },
+ * 20: {
+ * color: '#599'
+ * },
+ * 100: {
+ * color: '#ddd'
+ * }
+ * }
+ * }]
+ *
+ * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
+ *
+ * sprite.setAttributes({
+ * fill: 'url(#gradientId)'
+ * }, true);
+ */
- For example:
-
-
- gradients: [{
- id: 'gradientId',
- angle: 45,
- stops: {
- 0: {
- color: '#555'
- },
- 100: {
- color: '#ddd'
- }
- }
- }, {
- id: 'gradientId2',
- angle: 0,
- stops: {
- 0: {
- color: '#590'
- },
- 20: {
- color: '#599'
- },
- 100: {
- color: '#ddd'
- }
- }
- }]
-
-
- Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
-
-
- sprite.setAttributes({
- fill: 'url(#gradientId)'
- }, true);
-
+ /**
+ * @cfg {Ext.data.Store} store
+ * The store that supplies data to this chart.
+ */
+ /**
+ * @cfg {Ext.chart.series.Series[]} series
+ * Array of {@link Ext.chart.series.Series Series} instances or config objects. For example:
+ *
+ * series: [{
+ * type: 'column',
+ * axis: 'left',
+ * listeners: {
+ * 'afterrender': function() {
+ * console('afterrender');
+ * }
+ * },
+ * xField: 'category',
+ * yField: 'data1'
+ * }]
*/
+ /**
+ * @cfg {Ext.chart.axis.Axis[]} axes
+ * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects. For example:
+ *
+ * axes: [{
+ * type: 'Numeric',
+ * position: 'left',
+ * fields: ['data1'],
+ * title: 'Number of Hits',
+ * minimum: 0,
+ * //one minor tick between two major ticks
+ * minorTickSteps: 1
+ * }, {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Month of the Year'
+ * }]
+ */
constructor: function(config) {
var me = this,
defaultAnim;
+
+ config = Ext.apply({}, config);
me.initTheme(config.theme || me.theme);
if (me.gradients) {
Ext.apply(config, { gradients: me.gradients });
@@ -40467,6 +41963,10 @@ Ext.define('Ext.chart.Chart', {
me.mixins.navigation.constructor.call(me, config);
me.callParent([config]);
},
+
+ getChartStore: function(){
+ return this.substore || this.store;
+ },
initComponent: function() {
var me = this,
@@ -40484,17 +41984,17 @@ Ext.define('Ext.chart.Chart', {
'itemdrag',
'itemdragend',
/**
- * @event beforerefresh
- * Fires before a refresh to the chart data is called. If the beforerefresh handler returns
- * false the {@link #refresh} action will be cancelled.
- * @param {Chart} this
- */
+ * @event beforerefresh
+ * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
+ * {@link #refresh} action will be cancelled.
+ * @param {Ext.chart.Chart} this
+ */
'beforerefresh',
/**
- * @event refresh
- * Fires after the chart data has been refreshed.
- * @param {Chart} this
- */
+ * @event refresh
+ * Fires after the chart data has been refreshed.
+ * @param {Ext.chart.Chart} this
+ */
'refresh'
);
Ext.applyIf(me, {
@@ -40542,8 +42042,8 @@ Ext.define('Ext.chart.Chart', {
},
/**
- * Redraw the chart. If animations are set this will animate the chart too.
- * @cfg {boolean} resize Optional flag which changes the default origin points of the chart for animations.
+ * Redraws the chart. If animations are set this will animate the chart too.
+ * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
*/
redraw: function(resize) {
var me = this,
@@ -40755,7 +42255,7 @@ Ext.define('Ext.chart.Chart', {
// @private
refresh: function() {
var me = this;
- if (me.rendered && me.curWidth != undefined && me.curHeight != undefined) {
+ if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
if (me.fireEvent('beforerefresh', me) !== false) {
me.redraw();
me.fireEvent('refresh', me);
@@ -40765,13 +42265,13 @@ Ext.define('Ext.chart.Chart', {
/**
* Changes the data store bound to this chart and refreshes it.
- * @param {Store} store The store to bind to this chart
+ * @param {Ext.data.Store} store The store to bind to this chart
*/
bindStore: function(store, initial) {
var me = this;
if (!initial && me.store) {
if (store !== me.store && me.store.autoDestroy) {
- me.store.destroy();
+ me.store.destroyStore();
}
else {
me.store.un('datachanged', me.refresh, me);
@@ -41005,7 +42505,7 @@ Ext.define('Ext.chart.Chart', {
// @private remove gently.
destroy: function() {
- this.surface.destroy();
+ Ext.destroy(this.surface);
this.bindStore(null);
this.callParent(arguments);
}
@@ -41025,7 +42525,7 @@ Ext.define('Ext.chart.Highlight', {
/**
* Highlight the given series item.
- * @param {Boolean|Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius)
+ * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius)
* or just use default styles per series by setting highlight = true.
*/
highlight: false,
@@ -41191,11 +42691,11 @@ Ext.define('Ext.chart.Highlight', {
/**
* @class Ext.chart.Label
*
- * Labels is a mixin whose methods are appended onto the Series class. Labels is an interface with methods implemented
- * in each of the Series (Pie, Bar, etc) for label creation and label placement.
+ * Labels is a mixin to the Series class. Labels methods are implemented
+ * in each of the Series (Pie, Bar, etc) for label creation and placement.
*
* The methods implemented by the Series are:
- *
+ *
* - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
* The arguments of the method are:
* - *`storeItem`* The element of the store that is related to the label sprite.
@@ -41219,57 +42719,59 @@ Ext.define('Ext.chart.Label', {
/* Begin Definitions */
requires: ['Ext.draw.Color'],
-
- /* End Definitions */
-
- /**
- * @cfg {String} display
- * Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
- * "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
- * Default value: 'none'.
- */
-
- /**
- * @cfg {String} color
- * The color of the label text.
- * Default value: '#000' (black).
- */
-
- /**
- * @cfg {String} field
- * The name of the field to be displayed in the label.
- * Default value: 'name'.
- */
-
- /**
- * @cfg {Number} minMargin
- * Specifies the minimum distance from a label to the origin of the visualization.
- * This parameter is useful when using PieSeries width variable pie slice lengths.
- * Default value: 50.
- */
- /**
- * @cfg {String} font
- * The font used for the labels.
- * Defautl value: "11px Helvetica, sans-serif".
- */
-
- /**
- * @cfg {String} orientation
- * Either "horizontal" or "vertical".
- * Dafault value: "horizontal".
- */
+ /* End Definitions */
/**
- * @cfg {Function} renderer
- * Optional function for formatting the label into a displayable value.
- * Default value: function(v) { return v; }
- * @param v
+ * @cfg {Object} label
+ * Object with the following properties:
+ *
+ * - **display** : String
+ *
+ * Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
+ * "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
+ * Default value: 'none'.
+ *
+ * - **color** : String
+ *
+ * The color of the label text.
+ * Default value: '#000' (black).
+ *
+ * - **contrast** : Boolean
+ *
+ * True to render the label in contrasting color with the backround.
+ * Default value: false.
+ *
+ * - **field** : String
+ *
+ * The name of the field to be displayed in the label.
+ * Default value: 'name'.
+ *
+ * - **minMargin** : Number
+ *
+ * Specifies the minimum distance from a label to the origin of the visualization.
+ * This parameter is useful when using PieSeries width variable pie slice lengths.
+ * Default value: 50.
+ *
+ * - **font** : String
+ *
+ * The font used for the labels.
+ * Default value: "11px Helvetica, sans-serif".
+ *
+ * - **orientation** : String
+ *
+ * Either "horizontal" or "vertical".
+ * Dafault value: "horizontal".
+ *
+ * - **renderer** : Function
+ *
+ * Optional function for formatting the label into a displayable value.
+ * Default value: function(v) { return v; }
*/
//@private a regex to parse url type colors.
colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
-
+
//@private the mixin constructor. Used internally by Series.
constructor: function(config) {
var me = this;
@@ -41303,103 +42805,111 @@ Ext.define('Ext.chart.Label', {
color = config.color,
field = [].concat(config.field),
group = me.labelsGroup,
+ groupLength = (group || 0) && group.length,
store = me.chart.store,
len = store.getCount(),
itemLength = (items || 0) && items.length,
ratio = itemLength / len,
gradientsCount = (gradients || 0) && gradients.length,
Color = Ext.draw.Color,
- gradient, i, count, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
+ hides = [],
+ gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
if (display == 'none') {
return;
}
+ // no items displayed, hide all labels
+ if(itemLength == 0){
+ while(groupLength--)
+ hides.push(groupLength);
+ }else{
+ for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
+ index = 0;
+ for (j = 0; j < ratio; j++) {
+ item = items[count];
+ label = group.getAt(groupIndex);
+ storeItem = store.getAt(i);
+ //check the excludes
+ while(this.__excludes && this.__excludes[index] && ratio > 1) {
+ if(field[j]){
+ hides.push(groupIndex);
+ }
+ index++;
- for (i = 0, count = 0; i < len; i++) {
- index = 0;
- for (j = 0; j < ratio; j++) {
- item = items[count];
- label = group.getAt(count);
- storeItem = store.getAt(i);
-
- //check the excludes
- while(this.__excludes && this.__excludes[index]) {
- index++;
- }
-
- if (!item && label) {
- label.hide(true);
- }
+ }
- if (item && field[j]) {
- if (!label) {
- label = me.onCreateLabel(storeItem, item, i, display, j, index);
+ if (!item && label) {
+ label.hide(true);
+ groupIndex++;
}
- me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
-
- //set contrast
- if (config.contrast && item.sprite) {
- sprite = item.sprite;
- //set the color string to the color to be set.
- if (sprite._endStyle) {
- colorString = sprite._endStyle.fill;
- }
- else if (sprite._to) {
- colorString = sprite._to.fill;
- }
- else {
- colorString = sprite.attr.fill;
+
+ if (item && field[j]) {
+ if (!label) {
+ label = me.onCreateLabel(storeItem, item, i, display, j, index);
}
- colorString = colorString || sprite.attr.fill;
-
- spriteColor = Color.fromString(colorString);
- //color wasn't parsed property maybe because it's a gradient id
- if (colorString && !spriteColor) {
- colorString = colorString.match(me.colorStringRe)[1];
- for (k = 0; k < gradientsCount; k++) {
- gradient = gradients[k];
- if (gradient.id == colorString) {
- //avg color stops
- colorStop = 0; colorStopTotal = 0;
- for (colorStopIndex in gradient.stops) {
- colorStop++;
- colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
+ me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
+ groupIndex++;
+
+ //set contrast
+ if (config.contrast && item.sprite) {
+ sprite = item.sprite;
+ //set the color string to the color to be set.
+ if (sprite._endStyle) {
+ colorString = sprite._endStyle.fill;
+ }
+ else if (sprite._to) {
+ colorString = sprite._to.fill;
+ }
+ else {
+ colorString = sprite.attr.fill;
+ }
+ colorString = colorString || sprite.attr.fill;
+
+ spriteColor = Color.fromString(colorString);
+ //color wasn't parsed property maybe because it's a gradient id
+ if (colorString && !spriteColor) {
+ colorString = colorString.match(me.colorStringRe)[1];
+ for (k = 0; k < gradientsCount; k++) {
+ gradient = gradients[k];
+ if (gradient.id == colorString) {
+ //avg color stops
+ colorStop = 0; colorStopTotal = 0;
+ for (colorStopIndex in gradient.stops) {
+ colorStop++;
+ colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
+ }
+ spriteBrightness = (colorStopTotal / colorStop) / 255;
+ break;
}
- spriteBrightness = (colorStopTotal / colorStop) / 255;
- break;
}
}
+ else {
+ spriteBrightness = spriteColor.getGrayscale() / 255;
+ }
+ if (label.isOutside) {
+ spriteBrightness = 1;
+ }
+ labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
+ labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
+ label.setAttributes({
+ fill: String(Color.fromHSL.apply({}, labelColor))
+ }, true);
}
- else {
- spriteBrightness = spriteColor.getGrayscale() / 255;
- }
- if (label.isOutside) {
- spriteBrightness = 1;
- }
- labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
- labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
- label.setAttributes({
- fill: String(Color.fromHSL.apply({}, labelColor))
- }, true);
+
}
+ count++;
+ index++;
}
- count++;
- index++;
}
}
- me.hideLabels(count);
+ me.hideLabels(hides);
},
-
- //@private a method to hide labels.
- hideLabels: function(index) {
- var labelsGroup = this.labelsGroup, len;
- if (labelsGroup) {
- len = labelsGroup.getCount();
- while (len-->index) {
- labelsGroup.getAt(len).hide(true);
- }
- }
+ hideLabels: function(hides){
+ var labelsGroup = this.labelsGroup,
+ hlen = hides.length;
+ while(hlen--)
+ labelsGroup.getAt(hides[hlen]).hide(true);
}
});
Ext.define('Ext.chart.MaskLayer', {
@@ -41515,7 +43025,10 @@ Ext.define('Ext.chart.Tip', {
constrainPosition: false
});
me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
- Ext.getBody().on('mousemove', me.tooltip.onMouseMove, me.tooltip);
+ me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
+ me.chart.surface.on('mouseleave', function() {
+ me.hideTip();
+ });
if (me.tipConfig.surface) {
//initialize a surface
surface = me.tipConfig.surface;
@@ -41579,6 +43092,7 @@ Ext.define('Ext.chart.Tip', {
/**
* @class Ext.chart.axis.Abstract
* Base class for all axis classes.
+ * @private
*/
Ext.define('Ext.chart.axis.Abstract', {
@@ -41638,13 +43152,13 @@ Ext.define('Ext.chart.axis.Abstract', {
/**
* @class Ext.chart.axis.Axis
* @extends Ext.chart.axis.Abstract
- *
+ *
* Defines axis for charts. The axis position, type, style can be configured.
- * The axes are defined in an axes array of configuration objects where the type,
- * field, grid and other configuration options can be set. To know more about how
+ * The axes are defined in an axes array of configuration objects where the type,
+ * field, grid and other configuration options can be set. To know more about how
* to create a Chart please check the Chart class documentation. Here's an example for the axes part:
* An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
- *
+ *
* axes: [{
* type: 'Numeric',
* grid: true,
@@ -41672,10 +43186,10 @@ Ext.define('Ext.chart.axis.Abstract', {
* }
* }
* }]
- *
+ *
* In this case we use a `Numeric` axis for displaying the values of the Area series and a `Category` axis for displaying the names of
- * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
- * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
+ * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
+ * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
* category axis the labels will be rotated so they can fit the space better.
*/
Ext.define('Ext.chart.axis.Axis', {
@@ -41691,7 +43205,7 @@ Ext.define('Ext.chart.axis.Axis', {
/* End Definitions */
/**
- * @cfg {Boolean | Object} grid
+ * @cfg {Boolean/Object} grid
* The grid configuration enables you to set a background grid for an axis.
* If set to *true* on a vertical axis, vertical lines will be drawn.
* If set to *true* on a horizontal axis, horizontal lines will be drawn.
@@ -41728,76 +43242,82 @@ Ext.define('Ext.chart.axis.Axis', {
* title: 'Month of the Year',
* grid: true
* }]
- *
+ *
*/
/**
- * @cfg {Number} majorTickSteps
+ * @cfg {Number} majorTickSteps
* If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
*/
/**
- * @cfg {Number} minorTickSteps
+ * @cfg {Number} minorTickSteps
* The number of small ticks between two major ticks. Default is zero.
*/
-
+
+ /**
+ * @cfg {String} title
+ * The title for the Axis
+ */
+
//@private force min/max values from store
forceMinMax: false,
-
+
/**
- * @cfg {Number} dashSize
+ * @cfg {Number} dashSize
* The size of the dash marker. Default's 3.
*/
dashSize: 3,
-
+
/**
* @cfg {String} position
* Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
*/
position: 'bottom',
-
+
// @private
skipFirst: false,
-
+
/**
* @cfg {Number} length
* Offset axis position. Default's 0.
*/
length: 0,
-
+
/**
* @cfg {Number} width
* Offset axis width. Default's 0.
*/
width: 0,
-
+
majorTickSteps: false,
// @private
applyData: Ext.emptyFn,
- // @private creates a structure with start, end and step points.
- calcEnds: function() {
+ getRange: function () {
var me = this,
+ store = me.chart.getChartStore(),
+ fields = me.fields,
+ ln = fields.length,
math = Math,
mmax = math.max,
mmin = math.min,
- store = me.chart.substore || me.chart.store,
- series = me.chart.series.items,
- fields = me.fields,
- ln = fields.length,
+ aggregate = false,
min = isNaN(me.minimum) ? Infinity : me.minimum,
max = isNaN(me.maximum) ? -Infinity : me.maximum,
- prevMin = me.prevMin,
- prevMax = me.prevMax,
- aggregate = false,
- total = 0,
+ total = 0, i, l, value, values, rec,
excludes = [],
- outfrom, outto,
- i, l, values, rec, out;
+ series = me.chart.series.items;
//if one series is stacked I have to aggregate the values
//for the scale.
+ // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
+ // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
+ // Assuming only stack on y-axis.
+ // CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since
+ // it was constraining the minmax calculation to y-axis stacked
+ // visualizations.
for (i = 0, l = series.length; !aggregate && i < l; i++) {
aggregate = aggregate || series[i].stacked;
excludes = series[i].__excludes || excludes;
@@ -41814,8 +43334,8 @@ Ext.define('Ext.chart.axis.Axis', {
rec = record.get(fields[i]);
values[+(rec > 0)] += math.abs(rec);
}
- max = mmax(max, -values[0], values[1]);
- min = mmin(min, -values[0], values[1]);
+ max = mmax(max, -values[0], +values[1]);
+ min = mmin(min, -values[0], +values[1]);
}
else {
for (i = 0; i < ln; i++) {
@@ -41823,8 +43343,8 @@ Ext.define('Ext.chart.axis.Axis', {
continue;
}
value = record.get(fields[i]);
- max = mmax(max, value);
- min = mmin(min, value);
+ max = mmax(max, +value);
+ min = mmin(min, +value);
}
}
});
@@ -41835,9 +43355,30 @@ Ext.define('Ext.chart.axis.Axis', {
min = me.prevMin || 0;
}
//normalize min max for snapEnds.
- if (min != max && (max != (max >> 0))) {
- max = (max >> 0) + 1;
+ if (min != max && (max != Math.floor(max))) {
+ max = Math.floor(max) + 1;
}
+
+ if (!isNaN(me.minimum)) {
+ min = me.minimum;
+ }
+
+ if (!isNaN(me.maximum)) {
+ max = me.maximum;
+ }
+
+ return {min: min, max: max};
+ },
+
+ // @private creates a structure with start, end and step points.
+ calcEnds: function() {
+ var me = this,
+ fields = me.fields,
+ range = me.getRange(),
+ min = range.min,
+ max = range.max,
+ outfrom, outto, out;
+
out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ? (me.majorTickSteps +1) : me.steps);
outfrom = out.from;
outto = out.to;
@@ -41859,10 +43400,10 @@ Ext.define('Ext.chart.axis.Axis', {
//Clipping should be added to remove lines in the chart which are below the axis.
out.from = me.minimum;
}
-
+
//Adjust after adjusting minimum and maximum
out.step = (out.to - out.from) / (outto - outfrom) * out.step;
-
+
if (me.adjustMaximumByMajorUnit) {
out.to += out.step;
}
@@ -41875,7 +43416,7 @@ Ext.define('Ext.chart.axis.Axis', {
},
/**
- * Renders the axis into the screen and updates it's position.
+ * Renders the axis into the screen and updates its position.
*/
drawAxis: function (init) {
var me = this,
@@ -41904,7 +43445,7 @@ Ext.define('Ext.chart.axis.Axis', {
dashesX,
dashesY,
delta;
-
+
//If no steps are specified
//then don't draw the axis. This generally happens
//when an empty store.
@@ -41924,11 +43465,11 @@ Ext.define('Ext.chart.axis.Axis', {
path = ["M", x, currentY, "l", length, 0];
trueLength = length - (gutterX * 2);
}
-
+
delta = trueLength / (steps || 1);
dashesX = Math.max(subDashesX +1, 0);
dashesY = Math.max(subDashesY +1, 0);
- if (me.type == 'Numeric') {
+ if (me.type == 'Numeric' || me.type == 'Time') {
calcLabels = true;
me.labels = [stepCalcs.from];
}
@@ -42013,7 +43554,7 @@ Ext.define('Ext.chart.axis.Axis', {
*/
drawGrid: function() {
var me = this,
- surface = me.chart.surface,
+ surface = me.chart.surface,
grid = me.grid,
odd = grid.odd,
even = grid.even,
@@ -42027,7 +43568,7 @@ Ext.define('Ext.chart.axis.Axis', {
i = 1,
path = [], styles, lineWidth, dlineWidth,
oddPath = [], evenPath = [];
-
+
if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
(gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
i = 0;
@@ -42042,25 +43583,25 @@ Ext.define('Ext.chart.axis.Axis', {
lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
dlineWidth = 2 * lineWidth;
if (position == 'left') {
- path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
+ path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
"L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
}
else if (position == 'right') {
- path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
+ path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
"L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
}
else if (position == 'top') {
- path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
+ path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
"L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
}
else {
- path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
+ path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
"L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
@@ -42099,7 +43640,7 @@ Ext.define('Ext.chart.axis.Axis', {
type: 'path',
path: evenPath
});
- }
+ }
me.gridEven.setAttributes(Ext.apply({
path: evenPath,
hidden: false
@@ -42156,8 +43697,8 @@ Ext.define('Ext.chart.axis.Axis', {
if (me.label.rotation) {
textLabel.setAttributes({
rotation: {
- degrees: 0
- }
+ degrees: 0
+ }
}, true);
textLabel._ubbox = textLabel.getBBox();
textLabel.setAttributes(me.label, true);
@@ -42166,7 +43707,7 @@ Ext.define('Ext.chart.axis.Axis', {
}
return textLabel;
},
-
+
rect2pointArray: function(sprite) {
var surface = this.chart.surface,
rect = surface.getBBox(sprite, true),
@@ -42182,24 +43723,24 @@ Ext.define('Ext.chart.axis.Axis', {
//transform the points
p1[0] = matrix.x.apply(matrix, p1p);
p1[1] = matrix.y.apply(matrix, p1p);
-
+
p2[0] = matrix.x.apply(matrix, p2p);
p2[1] = matrix.y.apply(matrix, p2p);
-
+
p3[0] = matrix.x.apply(matrix, p3p);
p3[1] = matrix.y.apply(matrix, p3p);
-
+
p4[0] = matrix.x.apply(matrix, p4p);
p4[1] = matrix.y.apply(matrix, p4p);
return [p1, p2, p3, p4];
},
-
+
intersect: function(l1, l2) {
var r1 = this.rect2pointArray(l1),
r2 = this.rect2pointArray(l2);
return !!Ext.draw.Draw.intersect(r1, r2).length;
},
-
+
drawHorizontalLabels: function() {
var me = this,
labelConf = me.label,
@@ -42223,8 +43764,8 @@ Ext.define('Ext.chart.axis.Axis', {
//get a reference to the first text label dimensions
point = inflections[0];
firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
- ratio = Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)) >> 0;
-
+ ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
+
for (i = 0; i < ln; i++) {
point = inflections[i];
text = me.label.renderer(labels[i]);
@@ -42246,7 +43787,7 @@ Ext.define('Ext.chart.axis.Axis', {
else {
y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
}
-
+
textLabel.setAttributes({
hidden: false,
x: x,
@@ -42259,13 +43800,13 @@ Ext.define('Ext.chart.axis.Axis', {
textLabel.hide(true);
continue;
}
-
+
prevLabel = textLabel;
}
return maxHeight;
},
-
+
drawVerticalLabels: function() {
var me = this,
inflections = me.inflections,
@@ -42289,7 +43830,7 @@ Ext.define('Ext.chart.axis.Axis', {
text = me.label.renderer(labels[i]);
textLabel = me.getOrCreateLabel(i, text);
bbox = textLabel._bbox;
-
+
maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
y = point[1];
if (gutterY < bbox.height / 2) {
@@ -42305,7 +43846,7 @@ Ext.define('Ext.chart.axis.Axis', {
}
else {
x = point[0] + me.dashSize + me.label.padding + 2;
- }
+ }
textLabel.setAttributes(Ext.apply({
hidden: false,
x: x,
@@ -42318,7 +43859,7 @@ Ext.define('Ext.chart.axis.Axis', {
}
prevLabel = textLabel;
}
-
+
return maxWidth;
},
@@ -42335,7 +43876,7 @@ Ext.define('Ext.chart.axis.Axis', {
ln, i;
if (position == 'left' || position == 'right') {
- maxWidth = me.drawVerticalLabels();
+ maxWidth = me.drawVerticalLabels();
} else {
maxHeight = me.drawHorizontalLabels();
}
@@ -42447,19 +43988,19 @@ Ext.define('Ext.chart.axis.Axis', {
}, true);
}
});
+
/**
* @class Ext.chart.axis.Category
* @extends Ext.chart.axis.Axis
*
* A type of axis that displays items in categories. This axis is generally used to
* display categorical information like names of items, month names, quarters, etc.
- * but no quantitative values. For that other type of information Number
+ * but no quantitative values. For that other type of information `Number`
* axis are more suitable.
*
* As with other axis you can set the position of the axis and its title. For example:
*
- * {@img Ext.chart.axis.Category/Ext.chart.axis.Category.png Ext.chart.axis.Category chart axis}
- *
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
@@ -42467,10 +44008,10 @@ Ext.define('Ext.chart.axis.Axis', {
* {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
* {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
* {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
@@ -42517,4479 +44058,5375 @@ Ext.define('Ext.chart.axis.Axis', {
* });
*
* In this example with set the category axis to the bottom of the surface, bound the axis to
- * the name property and set as title Month of the Year .
+ * the `name` property and set as title _Month of the Year_.
+ */
+Ext.define('Ext.chart.axis.Category', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.chart.axis.Axis',
+
+ alternateClassName: 'Ext.chart.CategoryAxis',
+
+ alias: 'axis.category',
+
+ /* End Definitions */
+
+ /**
+ * A list of category names to display along this axis.
+ * @property {String} categoryNames
+ */
+ categoryNames: null,
+
+ /**
+ * Indicates whether or not to calculate the number of categories (ticks and
+ * labels) when there is not enough room to display all labels on the axis.
+ * If set to true, the axis will determine the number of categories to plot.
+ * If not, all categories will be plotted.
+ *
+ * @property calculateCategoryCount
+ * @type Boolean
+ */
+ calculateCategoryCount: false,
+
+ // @private creates an array of labels to be used when rendering.
+ setLabels: function() {
+ var store = this.chart.store,
+ fields = this.fields,
+ ln = fields.length,
+ i;
+
+ this.labels = [];
+ store.each(function(record) {
+ for (i = 0; i < ln; i++) {
+ this.labels.push(record.get(fields[i]));
+ }
+ }, this);
+ },
+
+ // @private calculates labels positions and marker positions for rendering.
+ applyData: function() {
+ this.callParent();
+ this.setLabels();
+ var count = this.chart.store.getCount();
+ return {
+ from: 0,
+ to: count,
+ power: 1,
+ step: 1,
+ steps: count - 1
+ };
+ }
+});
+
+/**
+ * @class Ext.chart.axis.Gauge
+ * @extends Ext.chart.axis.Abstract
+ *
+ * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
+ * displays numeric data from an interval defined by the `minimum`, `maximum` and
+ * `step` configuration properties. The placement of the numeric data can be changed
+ * by altering the `margin` option that is set to `10` by default.
+ *
+ * A possible configuration for this axis would look like:
+ *
+ * axes: [{
+ * type: 'gauge',
+ * position: 'gauge',
+ * minimum: 0,
+ * maximum: 100,
+ * steps: 10,
+ * margin: 7
+ * }],
*/
+Ext.define('Ext.chart.axis.Gauge', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.chart.axis.Abstract',
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Number} minimum (required)
+ * The minimum value of the interval to be displayed in the axis.
+ */
+
+ /**
+ * @cfg {Number} maximum (required)
+ * The maximum value of the interval to be displayed in the axis.
+ */
+
+ /**
+ * @cfg {Number} steps (required)
+ * The number of steps and tick marks to add to the interval.
+ */
+
+ /**
+ * @cfg {Number} [margin=10]
+ * The offset positioning of the tick marks and labels in pixels.
+ */
+
+ /**
+ * @cfg {String} title
+ * The title for the Axis.
+ */
+
+ position: 'gauge',
+
+ alias: 'axis.gauge',
+
+ drawAxis: function(init) {
+ var chart = this.chart,
+ surface = chart.surface,
+ bbox = chart.chartBBox,
+ centerX = bbox.x + (bbox.width / 2),
+ centerY = bbox.y + bbox.height,
+ margin = this.margin || 10,
+ rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
+ sprites = [], sprite,
+ steps = this.steps,
+ i, pi = Math.PI,
+ cos = Math.cos,
+ sin = Math.sin;
+
+ if (this.sprites && !chart.resizing) {
+ this.drawLabel();
+ return;
+ }
+
+ if (this.margin >= 0) {
+ if (!this.sprites) {
+ //draw circles
+ for (i = 0; i <= steps; i++) {
+ sprite = surface.add({
+ type: 'path',
+ path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
+ centerY + (rho - margin) * sin(i / steps * pi - pi),
+ 'L', centerX + rho * cos(i / steps * pi - pi),
+ centerY + rho * sin(i / steps * pi - pi), 'Z'],
+ stroke: '#ccc'
+ });
+ sprite.setAttributes({
+ hidden: false
+ }, true);
+ sprites.push(sprite);
+ }
+ } else {
+ sprites = this.sprites;
+ //draw circles
+ for (i = 0; i <= steps; i++) {
+ sprites[i].setAttributes({
+ path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
+ centerY + (rho - margin) * sin(i / steps * pi - pi),
+ 'L', centerX + rho * cos(i / steps * pi - pi),
+ centerY + rho * sin(i / steps * pi - pi), 'Z'],
+ stroke: '#ccc'
+ }, true);
+ }
+ }
+ }
+ this.sprites = sprites;
+ this.drawLabel();
+ if (this.title) {
+ this.drawTitle();
+ }
+ },
+
+ drawTitle: function() {
+ var me = this,
+ chart = me.chart,
+ surface = chart.surface,
+ bbox = chart.chartBBox,
+ labelSprite = me.titleSprite,
+ labelBBox;
+
+ if (!labelSprite) {
+ me.titleSprite = labelSprite = surface.add({
+ type: 'text',
+ zIndex: 2
+ });
+ }
+ labelSprite.setAttributes(Ext.apply({
+ text: me.title
+ }, me.label || {}), true);
+ labelBBox = labelSprite.getBBox();
+ labelSprite.setAttributes({
+ x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
+ y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
+ }, true);
+ },
+
+ /**
+ * Updates the {@link #title} of this axis.
+ * @param {String} title
+ */
+ setTitle: function(title) {
+ this.title = title;
+ this.drawTitle();
+ },
+
+ drawLabel: function() {
+ var chart = this.chart,
+ surface = chart.surface,
+ bbox = chart.chartBBox,
+ centerX = bbox.x + (bbox.width / 2),
+ centerY = bbox.y + bbox.height,
+ margin = this.margin || 10,
+ rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
+ round = Math.round,
+ labelArray = [], label,
+ maxValue = this.maximum || 0,
+ steps = this.steps, i = 0,
+ adjY,
+ pi = Math.PI,
+ cos = Math.cos,
+ sin = Math.sin,
+ labelConf = this.label,
+ renderer = labelConf.renderer || function(v) { return v; };
+
+ if (!this.labelArray) {
+ //draw scale
+ for (i = 0; i <= steps; i++) {
+ // TODO Adjust for height of text / 2 instead
+ adjY = (i === 0 || i === steps) ? 7 : 0;
+ label = surface.add({
+ type: 'text',
+ text: renderer(round(i / steps * maxValue)),
+ x: centerX + rho * cos(i / steps * pi - pi),
+ y: centerY + rho * sin(i / steps * pi - pi) - adjY,
+ 'text-anchor': 'middle',
+ 'stroke-width': 0.2,
+ zIndex: 10,
+ stroke: '#333'
+ });
+ label.setAttributes({
+ hidden: false
+ }, true);
+ labelArray.push(label);
+ }
+ }
+ else {
+ labelArray = this.labelArray;
+ //draw values
+ for (i = 0; i <= steps; i++) {
+ // TODO Adjust for height of text / 2 instead
+ adjY = (i === 0 || i === steps) ? 7 : 0;
+ labelArray[i].setAttributes({
+ text: renderer(round(i / steps * maxValue)),
+ x: centerX + rho * cos(i / steps * pi - pi),
+ y: centerY + rho * sin(i / steps * pi - pi) - adjY
+ }, true);
+ }
+ }
+ this.labelArray = labelArray;
+ }
+});
+/**
+ * @class Ext.chart.axis.Numeric
+ * @extends Ext.chart.axis.Axis
+ *
+ * An axis to handle numeric values. This axis is used for quantitative data as
+ * opposed to the category axis. You can set mininum and maximum values to the
+ * axis so that the values are bound to that. If no values are set, then the
+ * scale will auto-adjust to the values.
+ *
+ * @example
+ * var store = Ext.create('Ext.data.JsonStore', {
+ * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ * data: [
+ * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
+ * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
+ * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
+ * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
+ * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * ]
+ * });
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 500,
+ * height: 300,
+ * store: store,
+ * axes: [{
+ * type: 'Numeric',
+ * grid: true,
+ * position: 'left',
+ * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * title: 'Sample Values',
+ * grid: {
+ * odd: {
+ * opacity: 1,
+ * fill: '#ddd',
+ * stroke: '#bbb',
+ * 'stroke-width': 1
+ * }
+ * },
+ * minimum: 0,
+ * adjustMinimumByMajorUnit: 0
+ * }, {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics',
+ * grid: true,
+ * label: {
+ * rotate: {
+ * degrees: 315
+ * }
+ * }
+ * }],
+ * series: [{
+ * type: 'area',
+ * highlight: false,
+ * axis: 'left',
+ * xField: 'name',
+ * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * style: {
+ * opacity: 0.93
+ * }
+ * }]
+ * });
+ *
+ * In this example we create an axis of Numeric type. We set a minimum value so that
+ * even if all series have values greater than zero, the grid starts at zero. We bind
+ * the axis onto the left part of the surface by setting `position` to `left`.
+ * We bind three different store fields to this axis by setting `fields` to an array.
+ * We set the title of the axis to _Number of Hits_ by using the `title` property.
+ * We use a `grid` configuration to set odd background rows to a certain style and even rows
+ * to be transparent/ignored.
+ */
+Ext.define('Ext.chart.axis.Numeric', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.chart.axis.Axis',
+
+ alternateClassName: 'Ext.chart.NumericAxis',
+
+ /* End Definitions */
+
+ type: 'numeric',
+
+ alias: 'axis.numeric',
+
+ constructor: function(config) {
+ var me = this,
+ hasLabel = !!(config.label && config.label.renderer),
+ label;
+
+ me.callParent([config]);
+ label = me.label;
+ if (me.roundToDecimal === false) {
+ return;
+ }
+ if (!hasLabel) {
+ label.renderer = function(v) {
+ return me.roundToDecimal(v, me.decimals);
+ };
+ }
+ },
+
+ roundToDecimal: function(v, dec) {
+ var val = Math.pow(10, dec || 0);
+ return Math.floor(v * val) / val;
+ },
+
+ /**
+ * The minimum value drawn by the axis. If not set explicitly, the axis
+ * minimum will be calculated automatically.
+ *
+ * @property {Number} minimum
+ */
+ minimum: NaN,
+
+ /**
+ * The maximum value drawn by the axis. If not set explicitly, the axis
+ * maximum will be calculated automatically.
+ *
+ * @property {Number} maximum
+ */
+ maximum: NaN,
+
+ /**
+ * The number of decimals to round the value to.
+ *
+ * @property {Number} decimals
+ */
+ decimals: 2,
+
+ /**
+ * The scaling algorithm to use on this axis. May be "linear" or
+ * "logarithmic". Currently only linear scale is implemented.
+ *
+ * @property {String} scale
+ * @private
+ */
+ scale: "linear",
+
+ /**
+ * Indicates the position of the axis relative to the chart
+ *
+ * @property {String} position
+ */
+ position: 'left',
+
+ /**
+ * Indicates whether to extend maximum beyond data's maximum to the nearest
+ * majorUnit.
+ *
+ * @property {Boolean} adjustMaximumByMajorUnit
+ */
+ adjustMaximumByMajorUnit: false,
+
+ /**
+ * Indicates whether to extend the minimum beyond data's minimum to the
+ * nearest majorUnit.
+ *
+ * @property {Boolean} adjustMinimumByMajorUnit
+ */
+ adjustMinimumByMajorUnit: false,
+
+ // @private apply data.
+ applyData: function() {
+ this.callParent();
+ return this.calcEnds();
+ }
+});
+
+/**
+ * @class Ext.chart.axis.Radial
+ * @extends Ext.chart.axis.Abstract
+ * @ignore
+ */
+Ext.define('Ext.chart.axis.Radial', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.chart.axis.Abstract',
+
+ /* End Definitions */
+
+ position: 'radial',
+
+ alias: 'axis.radial',
+
+ drawAxis: function(init) {
+ var chart = this.chart,
+ surface = chart.surface,
+ bbox = chart.chartBBox,
+ store = chart.store,
+ l = store.getCount(),
+ centerX = bbox.x + (bbox.width / 2),
+ centerY = bbox.y + (bbox.height / 2),
+ rho = Math.min(bbox.width, bbox.height) /2,
+ sprites = [], sprite,
+ steps = this.steps,
+ i, j, pi2 = Math.PI * 2,
+ cos = Math.cos, sin = Math.sin;
+
+ if (this.sprites && !chart.resizing) {
+ this.drawLabel();
+ return;
+ }
+
+ if (!this.sprites) {
+ //draw circles
+ for (i = 1; i <= steps; i++) {
+ sprite = surface.add({
+ type: 'circle',
+ x: centerX,
+ y: centerY,
+ radius: Math.max(rho * i / steps, 0),
+ stroke: '#ccc'
+ });
+ sprite.setAttributes({
+ hidden: false
+ }, true);
+ sprites.push(sprite);
+ }
+ //draw lines
+ store.each(function(rec, i) {
+ sprite = surface.add({
+ type: 'path',
+ path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
+ stroke: '#ccc'
+ });
+ sprite.setAttributes({
+ hidden: false
+ }, true);
+ sprites.push(sprite);
+ });
+ } else {
+ sprites = this.sprites;
+ //draw circles
+ for (i = 0; i < steps; i++) {
+ sprites[i].setAttributes({
+ x: centerX,
+ y: centerY,
+ radius: Math.max(rho * (i + 1) / steps, 0),
+ stroke: '#ccc'
+ }, true);
+ }
+ //draw lines
+ store.each(function(rec, j) {
+ sprites[i + j].setAttributes({
+ path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
+ stroke: '#ccc'
+ }, true);
+ });
+ }
+ this.sprites = sprites;
+
+ this.drawLabel();
+ },
+
+ drawLabel: function() {
+ var chart = this.chart,
+ surface = chart.surface,
+ bbox = chart.chartBBox,
+ store = chart.store,
+ centerX = bbox.x + (bbox.width / 2),
+ centerY = bbox.y + (bbox.height / 2),
+ rho = Math.min(bbox.width, bbox.height) /2,
+ max = Math.max, round = Math.round,
+ labelArray = [], label,
+ fields = [], nfields,
+ categories = [], xField,
+ aggregate = !this.maximum,
+ maxValue = this.maximum || 0,
+ steps = this.steps, i = 0, j, dx, dy,
+ pi2 = Math.PI * 2,
+ cos = Math.cos, sin = Math.sin,
+ display = this.label.display,
+ draw = display !== 'none',
+ margin = 10;
+
+ if (!draw) {
+ return;
+ }
+
+ //get all rendered fields
+ chart.series.each(function(series) {
+ fields.push(series.yField);
+ xField = series.xField;
+ });
+
+ //get maxValue to interpolate
+ store.each(function(record, i) {
+ if (aggregate) {
+ for (i = 0, nfields = fields.length; i < nfields; i++) {
+ maxValue = max(+record.get(fields[i]), maxValue);
+ }
+ }
+ categories.push(record.get(xField));
+ });
+ if (!this.labelArray) {
+ if (display != 'categories') {
+ //draw scale
+ for (i = 1; i <= steps; i++) {
+ label = surface.add({
+ type: 'text',
+ text: round(i / steps * maxValue),
+ x: centerX,
+ y: centerY - rho * i / steps,
+ 'text-anchor': 'middle',
+ 'stroke-width': 0.1,
+ stroke: '#333'
+ });
+ label.setAttributes({
+ hidden: false
+ }, true);
+ labelArray.push(label);
+ }
+ }
+ if (display != 'scale') {
+ //draw text
+ for (j = 0, steps = categories.length; j < steps; j++) {
+ dx = cos(j / steps * pi2) * (rho + margin);
+ dy = sin(j / steps * pi2) * (rho + margin);
+ label = surface.add({
+ type: 'text',
+ text: categories[j],
+ x: centerX + dx,
+ y: centerY + dy,
+ 'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
+ });
+ label.setAttributes({
+ hidden: false
+ }, true);
+ labelArray.push(label);
+ }
+ }
+ }
+ else {
+ labelArray = this.labelArray;
+ if (display != 'categories') {
+ //draw values
+ for (i = 0; i < steps; i++) {
+ labelArray[i].setAttributes({
+ text: round((i + 1) / steps * maxValue),
+ x: centerX,
+ y: centerY - rho * (i + 1) / steps,
+ 'text-anchor': 'middle',
+ 'stroke-width': 0.1,
+ stroke: '#333'
+ }, true);
+ }
+ }
+ if (display != 'scale') {
+ //draw text
+ for (j = 0, steps = categories.length; j < steps; j++) {
+ dx = cos(j / steps * pi2) * (rho + margin);
+ dy = sin(j / steps * pi2) * (rho + margin);
+ if (labelArray[i + j]) {
+ labelArray[i + j].setAttributes({
+ type: 'text',
+ text: categories[j],
+ x: centerX + dx,
+ y: centerY + dy,
+ 'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
+ }, true);
+ }
+ }
+ }
+ }
+ this.labelArray = labelArray;
+ }
+});
+/**
+ * @author Ed Spencer
+ *
+ * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
+ * but offers a set of methods used by both of those subclasses.
+ *
+ * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
+ * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what
+ * AbstractStore is and is not.
+ *
+ * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be
+ * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a
+ * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
+ *
+ * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
+ * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
+ * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data -
+ * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
+ * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
+ *
+ * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
+ * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
+ * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
+ * {@link Ext.data.Store#filter filter} methods.
+ */
+Ext.define('Ext.data.AbstractStore', {
+ requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
+
+ mixins: {
+ observable: 'Ext.util.Observable',
+ sortable: 'Ext.util.Sortable'
+ },
+
+ statics: {
+ create: function(store){
+ if (!store.isStore) {
+ if (!store.type) {
+ store.type = 'store';
+ }
+ store = Ext.createByAlias('store.' + store.type, store);
+ }
+ return store;
+ }
+ },
+
+ remoteSort : false,
+ remoteFilter: false,
+
+ /**
+ * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
+ * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
+ * see {@link #setProxy} for details.
+ */
+
+ /**
+ * @cfg {Boolean/Object} autoLoad
+ * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
+ * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
+ * Defaults to false.
+ */
+ autoLoad: false,
+
+ /**
+ * @cfg {Boolean} autoSync
+ * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
+ */
+ autoSync: false,
+
+ /**
+ * @property {String} batchUpdateMode
+ * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
+ * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
+ * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
+ * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
+ */
+ batchUpdateMode: 'operation',
+
+ /**
+ * @property {Boolean} filterOnLoad
+ * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
+ * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
+ */
+ filterOnLoad: true,
+
+ /**
+ * @property {Boolean} sortOnLoad
+ * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
+ * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
+ */
+ sortOnLoad: true,
+
+ /**
+ * @property {Boolean} implicitModel
+ * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
+ * constructor instead of a model constructor or name.
+ * @private
+ */
+ implicitModel: false,
+
+ /**
+ * @property {String} defaultProxyType
+ * The string type of the Proxy to create if none is specified. This defaults to creating a
+ * {@link Ext.data.proxy.Memory memory proxy}.
+ */
+ defaultProxyType: 'memory',
+
+ /**
+ * @property {Boolean} isDestroyed
+ * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
+ * as it will not function correctly any more.
+ */
+ isDestroyed: false,
+
+ isStore: true,
+
+ /**
+ * @cfg {String} storeId
+ * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
+ * making it easy to reuse elsewhere. Defaults to undefined.
+ */
+
+ /**
+ * @cfg {Object[]} fields
+ * This may be used in place of specifying a {@link #model} configuration. The fields should be a
+ * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
+ * with these fields. In general this configuration option should be avoided, it exists for the purposes of
+ * backwards compatibility. For anything more complicated, such as specifying a particular id property or
+ * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
+ * config.
+ */
+
+ /**
+ * @cfg {String} model
+ * Name of the {@link Ext.data.Model Model} associated with this store.
+ * The string is used as an argument for {@link Ext.ModelManager#getModel}.
+ */
+
+ sortRoot: 'data',
+
+ //documented above
+ constructor: function(config) {
+ var me = this,
+ filters;
+
+ me.addEvents(
+ /**
+ * @event add
+ * Fired when a Model instance has been added to this Store
+ * @param {Ext.data.Store} store The store
+ * @param {Ext.data.Model[]} records The Model instances that were added
+ * @param {Number} index The index at which the instances were inserted
+ */
+ 'add',
+
+ /**
+ * @event remove
+ * Fired when a Model instance has been removed from this Store
+ * @param {Ext.data.Store} store The Store object
+ * @param {Ext.data.Model} record The record that was removed
+ * @param {Number} index The index of the record that was removed
+ */
+ 'remove',
+
+ /**
+ * @event update
+ * Fires when a Model instance has been updated
+ * @param {Ext.data.Store} this
+ * @param {Ext.data.Model} record The Model instance that was updated
+ * @param {String} operation The update operation being performed. Value may be one of:
+ *
+ * Ext.data.Model.EDIT
+ * Ext.data.Model.REJECT
+ * Ext.data.Model.COMMIT
+ */
+ 'update',
+
+ /**
+ * @event datachanged
+ * Fires whenever the records in the Store have changed in some way - this could include adding or removing
+ * records, or updating the data in existing records
+ * @param {Ext.data.Store} this The data store
+ */
+ 'datachanged',
+
+ /**
+ * @event beforeload
+ * Fires before a request is made for a new data object. If the beforeload handler returns false the load
+ * action will be canceled.
+ * @param {Ext.data.Store} store This Store
+ * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
+ * load the Store
+ */
+ 'beforeload',
+
+ /**
+ * @event load
+ * Fires whenever the store reads data from a remote data source.
+ * @param {Ext.data.Store} this
+ * @param {Ext.data.Model[]} records An array of records
+ * @param {Boolean} successful True if the operation was successful.
+ */
+ 'load',
+
+ /**
+ * @event write
+ * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
+ * @param {Ext.data.Store} store This Store
+ * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
+ * the write
+ */
+ 'write',
+
+ /**
+ * @event beforesync
+ * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
+ * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
+ */
+ 'beforesync',
+ /**
+ * @event clear
+ * Fired after the {@link #removeAll} method is called.
+ * @param {Ext.data.Store} this
+ */
+ 'clear'
+ );
+
+ Ext.apply(me, config);
+ // don't use *config* anymore from here on... use *me* instead...
+
+ /**
+ * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
+ * at which point this is cleared.
+ * @private
+ * @property {Ext.data.Model[]} removed
+ */
+ me.removed = [];
+
+ me.mixins.observable.constructor.apply(me, arguments);
+ me.model = Ext.ModelManager.getModel(me.model);
+
+ /**
+ * @property {Object} modelDefaults
+ * @private
+ * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
+ * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
+ * for examples. This should not need to be used by application developers.
+ */
+ Ext.applyIf(me, {
+ modelDefaults: {}
+ });
+
+ //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
+ if (!me.model && me.fields) {
+ me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
+ extend: 'Ext.data.Model',
+ fields: me.fields,
+ proxy: me.proxy || me.defaultProxyType
+ });
+
+ delete me.fields;
+
+ me.implicitModel = true;
+ }
+
+ //
+ if (!me.model) {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Store defined with no model. You may have mistyped the model name.');
+ }
+ }
+ //
+
+ //ensures that the Proxy is instantiated correctly
+ me.setProxy(me.proxy || me.model.getProxy());
+
+ if (me.id && !me.storeId) {
+ me.storeId = me.id;
+ delete me.id;
+ }
+
+ if (me.storeId) {
+ Ext.data.StoreManager.register(me);
+ }
+
+ me.mixins.sortable.initSortable.call(me);
+
+ /**
+ * @property {Ext.util.MixedCollection} filters
+ * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
+ */
+ filters = me.decodeFilters(me.filters);
+ me.filters = Ext.create('Ext.util.MixedCollection');
+ me.filters.addAll(filters);
+ },
+
+ /**
+ * Sets the Store's Proxy by string, config object or Proxy instance
+ * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
+ * or an Ext.data.proxy.Proxy instance
+ * @return {Ext.data.proxy.Proxy} The attached Proxy object
+ */
+ setProxy: function(proxy) {
+ var me = this;
+
+ if (proxy instanceof Ext.data.proxy.Proxy) {
+ proxy.setModel(me.model);
+ } else {
+ if (Ext.isString(proxy)) {
+ proxy = {
+ type: proxy
+ };
+ }
+ Ext.applyIf(proxy, {
+ model: me.model
+ });
+
+ proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
+ }
+
+ me.proxy = proxy;
+
+ return me.proxy;
+ },
-Ext.define('Ext.chart.axis.Category', {
+ /**
+ * Returns the proxy currently attached to this proxy instance
+ * @return {Ext.data.proxy.Proxy} The Proxy instance
+ */
+ getProxy: function() {
+ return this.proxy;
+ },
- /* Begin Definitions */
+ //saves any phantom records
+ create: function(data, options) {
+ var me = this,
+ instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
+ operation;
+
+ options = options || {};
- extend: 'Ext.chart.axis.Axis',
+ Ext.applyIf(options, {
+ action : 'create',
+ records: [instance]
+ });
- alternateClassName: 'Ext.chart.CategoryAxis',
+ operation = Ext.create('Ext.data.Operation', options);
- alias: 'axis.category',
+ me.proxy.create(operation, me.onProxyWrite, me);
+
+ return instance;
+ },
- /* End Definitions */
+ read: function() {
+ return this.load.apply(this, arguments);
+ },
+
+ onProxyRead: Ext.emptyFn,
+
+ update: function(options) {
+ var me = this,
+ operation;
+ options = options || {};
+
+ Ext.applyIf(options, {
+ action : 'update',
+ records: me.getUpdatedRecords()
+ });
+
+ operation = Ext.create('Ext.data.Operation', options);
+
+ return me.proxy.update(operation, me.onProxyWrite, me);
+ },
/**
- * A list of category names to display along this axis.
- *
- * @property categoryNames
- * @type Array
+ * @private
+ * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
+ * the updates provided by the Proxy
*/
- categoryNames: null,
+ onProxyWrite: function(operation) {
+ var me = this,
+ success = operation.wasSuccessful(),
+ records = operation.getRecords();
+
+ switch (operation.action) {
+ case 'create':
+ me.onCreateRecords(records, operation, success);
+ break;
+ case 'update':
+ me.onUpdateRecords(records, operation, success);
+ break;
+ case 'destroy':
+ me.onDestroyRecords(records, operation, success);
+ break;
+ }
+
+ if (success) {
+ me.fireEvent('write', me, operation);
+ me.fireEvent('datachanged', me);
+ }
+ //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
+ Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
+ },
+
+
+ //tells the attached proxy to destroy the given records
+ destroy: function(options) {
+ var me = this,
+ operation;
+
+ options = options || {};
+
+ Ext.applyIf(options, {
+ action : 'destroy',
+ records: me.getRemovedRecords()
+ });
+
+ operation = Ext.create('Ext.data.Operation', options);
+
+ return me.proxy.destroy(operation, me.onProxyWrite, me);
+ },
/**
- * Indicates whether or not to calculate the number of categories (ticks and
- * labels) when there is not enough room to display all labels on the axis.
- * If set to true, the axis will determine the number of categories to plot.
- * If not, all categories will be plotted.
- *
- * @property calculateCategoryCount
- * @type Boolean
+ * @private
+ * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
+ * to onProxyWrite.
*/
- calculateCategoryCount: false,
+ onBatchOperationComplete: function(batch, operation) {
+ return this.onProxyWrite(operation);
+ },
- // @private creates an array of labels to be used when rendering.
- setLabels: function() {
- var store = this.chart.store,
- fields = this.fields,
- ln = fields.length,
+ /**
+ * @private
+ * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
+ * and updates the Store's internal data MixedCollection.
+ */
+ onBatchComplete: function(batch, operation) {
+ var me = this,
+ operations = batch.operations,
+ length = operations.length,
i;
- this.labels = [];
- store.each(function(record) {
- for (i = 0; i < ln; i++) {
- this.labels.push(record.get(fields[i]));
- }
- }, this);
- },
+ me.suspendEvents();
- // @private calculates labels positions and marker positions for rendering.
- applyData: function() {
- this.callParent();
- this.setLabels();
- var count = this.chart.store.getCount();
- return {
- from: 0,
- to: count,
- power: 1,
- step: 1,
- steps: count - 1
- };
- }
-});
+ for (i = 0; i < length; i++) {
+ me.onProxyWrite(operations[i]);
+ }
-/**
- * @class Ext.chart.axis.Gauge
- * @extends Ext.chart.axis.Abstract
- *
- * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
- * displays numeric data from an interval defined by the `minimum`, `maximum` and
- * `step` configuration properties. The placement of the numeric data can be changed
- * by altering the `margin` option that is set to `10` by default.
- *
- * A possible configuration for this axis would look like:
- *
- * axes: [{
- * type: 'gauge',
- * position: 'gauge',
- * minimum: 0,
- * maximum: 100,
- * steps: 10,
- * margin: 7
- * }],
- */
-Ext.define('Ext.chart.axis.Gauge', {
+ me.resumeEvents();
- /* Begin Definitions */
+ me.fireEvent('datachanged', me);
+ },
- extend: 'Ext.chart.axis.Abstract',
+ onBatchException: function(batch, operation) {
+ // //decide what to do... could continue with the next operation
+ // batch.start();
+ //
+ // //or retry the last operation
+ // batch.retry();
+ },
- /* End Definitions */
-
/**
- * @cfg {Number} minimum (required) the minimum value of the interval to be displayed in the axis.
+ * @private
+ * Filter function for new records.
*/
+ filterNew: function(item) {
+ // only want phantom records that are valid
+ return item.phantom === true && item.isValid();
+ },
/**
- * @cfg {Number} maximum (required) the maximum value of the interval to be displayed in the axis.
+ * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
+ * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
+ * @return {Ext.data.Model[]} The Model instances
*/
+ getNewRecords: function() {
+ return [];
+ },
/**
- * @cfg {Number} steps (required) the number of steps and tick marks to add to the interval.
+ * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
+ * @return {Ext.data.Model[]} The updated Model instances
*/
+ getUpdatedRecords: function() {
+ return [];
+ },
/**
- * @cfg {Number} margin (optional) the offset positioning of the tick marks and labels in pixels. Default's 10.
+ * @private
+ * Filter function for updated records.
*/
+ filterUpdated: function(item) {
+ // only want dirty records, not phantoms that are valid
+ return item.dirty === true && item.phantom !== true && item.isValid();
+ },
- position: 'gauge',
+ /**
+ * Returns any records that have been removed from the store but not yet destroyed on the proxy.
+ * @return {Ext.data.Model[]} The removed Model instances
+ */
+ getRemovedRecords: function() {
+ return this.removed;
+ },
- alias: 'axis.gauge',
+ filter: function(filters, value) {
- drawAxis: function(init) {
- var chart = this.chart,
- surface = chart.surface,
- bbox = chart.chartBBox,
- centerX = bbox.x + (bbox.width / 2),
- centerY = bbox.y + bbox.height,
- margin = this.margin || 10,
- rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
- sprites = [], sprite,
- steps = this.steps,
- i, pi = Math.PI,
- cos = Math.cos,
- sin = Math.sin;
+ },
- if (this.sprites && !chart.resizing) {
- this.drawLabel();
- return;
+ /**
+ * @private
+ * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
+ * @param {Object[]} filters The filters array
+ * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
+ */
+ decodeFilters: function(filters) {
+ if (!Ext.isArray(filters)) {
+ if (filters === undefined) {
+ filters = [];
+ } else {
+ filters = [filters];
+ }
}
- if (this.margin >= 0) {
- if (!this.sprites) {
- //draw circles
- for (i = 0; i <= steps; i++) {
- sprite = surface.add({
- type: 'path',
- path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
- centerY + (rho - margin) * sin(i / steps * pi - pi),
- 'L', centerX + rho * cos(i / steps * pi - pi),
- centerY + rho * sin(i / steps * pi - pi), 'Z'],
- stroke: '#ccc'
- });
- sprite.setAttributes({
- hidden: false
- }, true);
- sprites.push(sprite);
+ var length = filters.length,
+ Filter = Ext.util.Filter,
+ config, i;
+
+ for (i = 0; i < length; i++) {
+ config = filters[i];
+
+ if (!(config instanceof Filter)) {
+ Ext.apply(config, {
+ root: 'data'
+ });
+
+ //support for 3.x style filters where a function can be defined as 'fn'
+ if (config.fn) {
+ config.filterFn = config.fn;
}
- } else {
- sprites = this.sprites;
- //draw circles
- for (i = 0; i <= steps; i++) {
- sprites[i].setAttributes({
- path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
- centerY + (rho - margin) * sin(i / steps * pi - pi),
- 'L', centerX + rho * cos(i / steps * pi - pi),
- centerY + rho * sin(i / steps * pi - pi), 'Z'],
- stroke: '#ccc'
- }, true);
+
+ //support a function to be passed as a filter definition
+ if (typeof config == 'function') {
+ config = {
+ filterFn: config
+ };
}
+
+ filters[i] = new Filter(config);
}
}
- this.sprites = sprites;
- this.drawLabel();
- if (this.title) {
- this.drawTitle();
- }
+
+ return filters;
},
-
- drawTitle: function() {
- var me = this,
- chart = me.chart,
- surface = chart.surface,
- bbox = chart.chartBBox,
- labelSprite = me.titleSprite,
- labelBBox;
-
- if (!labelSprite) {
- me.titleSprite = labelSprite = surface.add({
- type: 'text',
- zIndex: 2
- });
- }
- labelSprite.setAttributes(Ext.apply({
- text: me.title
- }, me.label || {}), true);
- labelBBox = labelSprite.getBBox();
- labelSprite.setAttributes({
- x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
- y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
- }, true);
+
+ clearFilter: function(supressEvent) {
+
},
- /**
- * Updates the {@link #title} of this axis.
- * @param {String} title
- */
- setTitle: function(title) {
- this.title = title;
- this.drawTitle();
+ isFiltered: function() {
+
},
- drawLabel: function() {
- var chart = this.chart,
- surface = chart.surface,
- bbox = chart.chartBBox,
- centerX = bbox.x + (bbox.width / 2),
- centerY = bbox.y + bbox.height,
- margin = this.margin || 10,
- rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
- round = Math.round,
- labelArray = [], label,
- maxValue = this.maximum || 0,
- steps = this.steps, i = 0,
- adjY,
- pi = Math.PI,
- cos = Math.cos,
- sin = Math.sin,
- labelConf = this.label,
- renderer = labelConf.renderer || function(v) { return v; };
+ filterBy: function(fn, scope) {
- if (!this.labelArray) {
- //draw scale
- for (i = 0; i <= steps; i++) {
- // TODO Adjust for height of text / 2 instead
- adjY = (i === 0 || i === steps) ? 7 : 0;
- label = surface.add({
- type: 'text',
- text: renderer(round(i / steps * maxValue)),
- x: centerX + rho * cos(i / steps * pi - pi),
- y: centerY + rho * sin(i / steps * pi - pi) - adjY,
- 'text-anchor': 'middle',
- 'stroke-width': 0.2,
- zIndex: 10,
- stroke: '#333'
- });
- label.setAttributes({
- hidden: false
- }, true);
- labelArray.push(label);
- }
+ },
+
+ /**
+ * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
+ * and deleted records in the store, updating the Store's internal representation of the records
+ * as each operation completes.
+ */
+ sync: function() {
+ var me = this,
+ options = {},
+ toCreate = me.getNewRecords(),
+ toUpdate = me.getUpdatedRecords(),
+ toDestroy = me.getRemovedRecords(),
+ needsSync = false;
+
+ if (toCreate.length > 0) {
+ options.create = toCreate;
+ needsSync = true;
}
- else {
- labelArray = this.labelArray;
- //draw values
- for (i = 0; i <= steps; i++) {
- // TODO Adjust for height of text / 2 instead
- adjY = (i === 0 || i === steps) ? 7 : 0;
- labelArray[i].setAttributes({
- text: renderer(round(i / steps * maxValue)),
- x: centerX + rho * cos(i / steps * pi - pi),
- y: centerY + rho * sin(i / steps * pi - pi) - adjY
- }, true);
- }
+
+ if (toUpdate.length > 0) {
+ options.update = toUpdate;
+ needsSync = true;
+ }
+
+ if (toDestroy.length > 0) {
+ options.destroy = toDestroy;
+ needsSync = true;
}
- this.labelArray = labelArray;
- }
-});
-/**
- * @class Ext.chart.axis.Numeric
- * @extends Ext.chart.axis.Axis
- *
- * An axis to handle numeric values. This axis is used for quantitative data as
- * opposed to the category axis. You can set mininum and maximum values to the
- * axis so that the values are bound to that. If no values are set, then the
- * scale will auto-adjust to the values.
- *
- * {@img Ext.chart.axis.Numeric/Ext.chart.axis.Numeric.png Ext.chart.axis.Numeric chart axis}
- *
- * For example:
- *
- * var store = Ext.create('Ext.data.JsonStore', {
- * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- * data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
- * ]
- * });
- *
- * Ext.create('Ext.chart.Chart', {
- * renderTo: Ext.getBody(),
- * width: 500,
- * height: 300,
- * store: store,
- * axes: [{
- * type: 'Numeric',
- * grid: true,
- * position: 'left',
- * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
- * title: 'Sample Values',
- * grid: {
- * odd: {
- * opacity: 1,
- * fill: '#ddd',
- * stroke: '#bbb',
- * 'stroke-width': 1
- * }
- * },
- * minimum: 0,
- * adjustMinimumByMajorUnit: 0
- * }, {
- * type: 'Category',
- * position: 'bottom',
- * fields: ['name'],
- * title: 'Sample Metrics',
- * grid: true,
- * label: {
- * rotate: {
- * degrees: 315
- * }
- * }
- * }],
- * series: [{
- * type: 'area',
- * highlight: false,
- * axis: 'left',
- * xField: 'name',
- * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
- * style: {
- * opacity: 0.93
- * }
- * }]
- * });
- *
- * In this example we create an axis of Numeric type. We set a minimum value so that
- * even if all series have values greater than zero, the grid starts at zero. We bind
- * the axis onto the left part of the surface by setting position to left .
- * We bind three different store fields to this axis by setting fields to an array.
- * We set the title of the axis to Number of Hits by using the title property.
- * We use a grid configuration to set odd background rows to a certain style and even rows
- * to be transparent/ignored.
- *
- */
-Ext.define('Ext.chart.axis.Numeric', {
- /* Begin Definitions */
+ if (needsSync && me.fireEvent('beforesync', options) !== false) {
+ me.proxy.batch(options, me.getBatchListeners());
+ }
+ },
- extend: 'Ext.chart.axis.Axis',
- alternateClassName: 'Ext.chart.NumericAxis',
+ /**
+ * @private
+ * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
+ * This is broken out into a separate function to allow for customisation of the listeners
+ * @return {Object} The listeners object
+ */
+ getBatchListeners: function() {
+ var me = this,
+ listeners = {
+ scope: me,
+ exception: me.onBatchException
+ };
- /* End Definitions */
+ if (me.batchUpdateMode == 'operation') {
+ listeners.operationcomplete = me.onBatchOperationComplete;
+ } else {
+ listeners.complete = me.onBatchComplete;
+ }
- type: 'numeric',
+ return listeners;
+ },
- alias: 'axis.numeric',
+ //deprecated, will be removed in 5.0
+ save: function() {
+ return this.sync.apply(this, arguments);
+ },
- constructor: function(config) {
+ /**
+ * Loads the Store using its configured {@link #proxy}.
+ * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
+ * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
+ */
+ load: function(options) {
var me = this,
- hasLabel = !!(config.label && config.label.renderer),
- label;
+ operation;
+
+ options = options || {};
+
+ Ext.applyIf(options, {
+ action : 'read',
+ filters: me.filters.items,
+ sorters: me.getSorters()
+ });
- me.callParent([config]);
- label = me.label;
- if (me.roundToDecimal === false) {
- return;
+ operation = Ext.create('Ext.data.Operation', options);
+
+ if (me.fireEvent('beforeload', me, operation) !== false) {
+ me.loading = true;
+ me.proxy.read(operation, me.onProxyLoad, me);
}
- if (!hasLabel) {
- label.renderer = function(v) {
- return me.roundToDecimal(v, me.decimals);
- };
- }
- },
-
- roundToDecimal: function(v, dec) {
- var val = Math.pow(10, dec || 0);
- return ((v * val) >> 0) / val;
+
+ return me;
},
-
- /**
- * The minimum value drawn by the axis. If not set explicitly, the axis
- * minimum will be calculated automatically.
- *
- * @property minimum
- * @type Number
- */
- minimum: NaN,
/**
- * The maximum value drawn by the axis. If not set explicitly, the axis
- * maximum will be calculated automatically.
- *
- * @property maximum
- * @type Number
+ * @private
+ * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
+ * @param {Ext.data.Model} record The model instance that was edited
*/
- maximum: NaN,
+ afterEdit : function(record) {
+ var me = this;
+
+ if (me.autoSync) {
+ me.sync();
+ }
+
+ me.fireEvent('update', me, record, Ext.data.Model.EDIT);
+ },
/**
- * The number of decimals to round the value to.
- * Default's 2.
- *
- * @property decimals
- * @type Number
+ * @private
+ * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
+ * @param {Ext.data.Model} record The model instance that was edited
*/
- decimals: 2,
+ afterReject : function(record) {
+ this.fireEvent('update', this, record, Ext.data.Model.REJECT);
+ },
/**
- * The scaling algorithm to use on this axis. May be "linear" or
- * "logarithmic".
- *
- * @property scale
- * @type String
+ * @private
+ * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
+ * @param {Ext.data.Model} record The model instance that was edited
*/
- scale: "linear",
+ afterCommit : function(record) {
+ this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
+ },
- /**
- * Indicates the position of the axis relative to the chart
- *
- * @property position
- * @type String
- */
- position: 'left',
+ clearData: Ext.emptyFn,
+
+ destroyStore: function() {
+ var me = this;
+
+ if (!me.isDestroyed) {
+ if (me.storeId) {
+ Ext.data.StoreManager.unregister(me);
+ }
+ me.clearData();
+ me.data = null;
+ me.tree = null;
+ // Ext.destroy(this.proxy);
+ me.reader = me.writer = null;
+ me.clearListeners();
+ me.isDestroyed = true;
+
+ if (me.implicitModel) {
+ Ext.destroy(me.model);
+ }
+ }
+ },
+
+ doSort: function(sorterFn) {
+ var me = this;
+ if (me.remoteSort) {
+ //the load function will pick up the new sorters and request the sorted data from the proxy
+ me.load();
+ } else {
+ me.data.sortBy(sorterFn);
+ me.fireEvent('datachanged', me);
+ }
+ },
+
+ getCount: Ext.emptyFn,
+ getById: Ext.emptyFn,
+
/**
- * Indicates whether to extend maximum beyond data's maximum to the nearest
- * majorUnit.
- *
- * @property adjustMaximumByMajorUnit
- * @type Boolean
+ * Removes all records from the store. This method does a "fast remove",
+ * individual remove events are not called. The {@link #clear} event is
+ * fired upon completion.
+ * @method
*/
- adjustMaximumByMajorUnit: false,
+ removeAll: Ext.emptyFn,
+ // individual substores should implement a "fast" remove
+ // and fire a clear event afterwards
/**
- * Indicates whether to extend the minimum beyond data's minimum to the
- * nearest majorUnit.
- *
- * @property adjustMinimumByMajorUnit
- * @type Boolean
+ * Returns true if the Store is currently performing a load operation
+ * @return {Boolean} True if the Store is currently loading
*/
- adjustMinimumByMajorUnit: false,
-
- // @private apply data.
- applyData: function() {
- this.callParent();
- return this.calcEnds();
- }
+ isLoading: function() {
+ return !!this.loading;
+ }
});
/**
- * @class Ext.chart.axis.Radial
- * @extends Ext.chart.axis.Abstract
- * @ignore
+ * @class Ext.util.Grouper
+ * @extends Ext.util.Sorter
+
+Represents a single grouper that can be applied to a Store. The grouper works
+in the same fashion as the {@link Ext.util.Sorter}.
+
+ * @markdown
*/
-Ext.define('Ext.chart.axis.Radial', {
+
+Ext.define('Ext.util.Grouper', {
/* Begin Definitions */
- extend: 'Ext.chart.axis.Abstract',
+ extend: 'Ext.util.Sorter',
/* End Definitions */
- position: 'radial',
-
- alias: 'axis.radial',
-
- drawAxis: function(init) {
- var chart = this.chart,
- surface = chart.surface,
- bbox = chart.chartBBox,
- store = chart.store,
- l = store.getCount(),
- centerX = bbox.x + (bbox.width / 2),
- centerY = bbox.y + (bbox.height / 2),
- rho = Math.min(bbox.width, bbox.height) /2,
- sprites = [], sprite,
- steps = this.steps,
- i, j, pi2 = Math.PI * 2,
- cos = Math.cos, sin = Math.sin;
-
- if (this.sprites && !chart.resizing) {
- this.drawLabel();
- return;
- }
+ /**
+ * Returns the value for grouping to be used.
+ * @param {Ext.data.Model} instance The Model instance
+ * @return {String} The group string for this model
+ */
+ getGroupString: function(instance) {
+ return instance.get(this.property);
+ }
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.Store
+ * @extends Ext.data.AbstractStore
+ *
+ * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
+ * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
+ * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.
+ *
+ * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
+ *
+
+// Set up a {@link Ext.data.Model model} to use in our Store
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ {name: 'firstName', type: 'string'},
+ {name: 'lastName', type: 'string'},
+ {name: 'age', type: 'int'},
+ {name: 'eyeColor', type: 'string'}
+ ]
+});
- if (!this.sprites) {
- //draw circles
- for (i = 1; i <= steps; i++) {
- sprite = surface.add({
- type: 'circle',
- x: centerX,
- y: centerY,
- radius: Math.max(rho * i / steps, 0),
- stroke: '#ccc'
- });
- sprite.setAttributes({
- hidden: false
- }, true);
- sprites.push(sprite);
- }
- //draw lines
- store.each(function(rec, i) {
- sprite = surface.add({
- type: 'path',
- path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
- stroke: '#ccc'
- });
- sprite.setAttributes({
- hidden: false
- }, true);
- sprites.push(sprite);
- });
- } else {
- sprites = this.sprites;
- //draw circles
- for (i = 0; i < steps; i++) {
- sprites[i].setAttributes({
- x: centerX,
- y: centerY,
- radius: Math.max(rho * (i + 1) / steps, 0),
- stroke: '#ccc'
- }, true);
- }
- //draw lines
- store.each(function(rec, j) {
- sprites[i + j].setAttributes({
- path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
- stroke: '#ccc'
- }, true);
- });
+var myStore = Ext.create('Ext.data.Store', {
+ model: 'User',
+ proxy: {
+ type: 'ajax',
+ url : '/users.json',
+ reader: {
+ type: 'json',
+ root: 'users'
}
- this.sprites = sprites;
-
- this.drawLabel();
},
+ autoLoad: true
+});
+
- drawLabel: function() {
- var chart = this.chart,
- surface = chart.surface,
- bbox = chart.chartBBox,
- store = chart.store,
- centerX = bbox.x + (bbox.width / 2),
- centerY = bbox.y + (bbox.height / 2),
- rho = Math.min(bbox.width, bbox.height) /2,
- max = Math.max, round = Math.round,
- labelArray = [], label,
- fields = [], nfields,
- categories = [], xField,
- aggregate = !this.maximum,
- maxValue = this.maximum || 0,
- steps = this.steps, i = 0, j, dx, dy,
- pi2 = Math.PI * 2,
- cos = Math.cos, sin = Math.sin,
- display = this.label.display,
- draw = display !== 'none',
- margin = 10;
-
- if (!draw) {
- return;
+ * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
+ * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
+ * {@link Ext.data.reader.Json see the docs on JsonReader} for details.
+ *
+ * Inline data
+ *
+ * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
+ * into Model instances:
+ *
+
+Ext.create('Ext.data.Store', {
+ model: 'User',
+ data : [
+ {firstName: 'Ed', lastName: 'Spencer'},
+ {firstName: 'Tommy', lastName: 'Maintz'},
+ {firstName: 'Aaron', lastName: 'Conran'},
+ {firstName: 'Jamie', lastName: 'Avins'}
+ ]
+});
+
+ *
+ * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't need
+ * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
+ * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).
+ *
+ * Additional data can also be loaded locally using {@link #add}.
+ *
+ * Loading Nested Data
+ *
+ * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
+ * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load a nested dataset
+ * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
+ * docs for a full explanation:
+ *
+
+var store = Ext.create('Ext.data.Store', {
+ autoLoad: true,
+ model: "User",
+ proxy: {
+ type: 'ajax',
+ url : 'users.json',
+ reader: {
+ type: 'json',
+ root: 'users'
}
-
- //get all rendered fields
- chart.series.each(function(series) {
- fields.push(series.yField);
- xField = series.xField;
- });
-
- //get maxValue to interpolate
- store.each(function(record, i) {
- if (aggregate) {
- for (i = 0, nfields = fields.length; i < nfields; i++) {
- maxValue = max(+record.get(fields[i]), maxValue);
- }
- }
- categories.push(record.get(xField));
- });
- if (!this.labelArray) {
- if (display != 'categories') {
- //draw scale
- for (i = 1; i <= steps; i++) {
- label = surface.add({
- type: 'text',
- text: round(i / steps * maxValue),
- x: centerX,
- y: centerY - rho * i / steps,
- 'text-anchor': 'middle',
- 'stroke-width': 0.1,
- stroke: '#333'
- });
- label.setAttributes({
- hidden: false
- }, true);
- labelArray.push(label);
- }
- }
- if (display != 'scale') {
- //draw text
- for (j = 0, steps = categories.length; j < steps; j++) {
- dx = cos(j / steps * pi2) * (rho + margin);
- dy = sin(j / steps * pi2) * (rho + margin);
- label = surface.add({
- type: 'text',
- text: categories[j],
- x: centerX + dx,
- y: centerY + dy,
- 'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
- });
- label.setAttributes({
- hidden: false
- }, true);
- labelArray.push(label);
+ }
+});
+
+ *
+ * Which would consume a response like this:
+ *
+
+{
+ "users": [
+ {
+ "id": 1,
+ "name": "Ed",
+ "orders": [
+ {
+ "id": 10,
+ "total": 10.76,
+ "status": "invoiced"
+ },
+ {
+ "id": 11,
+ "total": 13.45,
+ "status": "shipped"
}
- }
+ ]
}
- else {
- labelArray = this.labelArray;
- if (display != 'categories') {
- //draw values
- for (i = 0; i < steps; i++) {
- labelArray[i].setAttributes({
- text: round((i + 1) / steps * maxValue),
- x: centerX,
- y: centerY - rho * (i + 1) / steps,
- 'text-anchor': 'middle',
- 'stroke-width': 0.1,
- stroke: '#333'
- }, true);
- }
- }
- if (display != 'scale') {
- //draw text
- for (j = 0, steps = categories.length; j < steps; j++) {
- dx = cos(j / steps * pi2) * (rho + margin);
- dy = sin(j / steps * pi2) * (rho + margin);
- if (labelArray[i + j]) {
- labelArray[i + j].setAttributes({
- type: 'text',
- text: categories[j],
- x: centerX + dx,
- y: centerY + dy,
- 'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
- }, true);
- }
- }
- }
+ ]
+}
+
+ *
+ * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
+ *
+ * Filtering and Sorting
+ *
+ * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
+ * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
+ * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
+ *
+
+var store = Ext.create('Ext.data.Store', {
+ model: 'User',
+ sorters: [
+ {
+ property : 'age',
+ direction: 'DESC'
+ },
+ {
+ property : 'firstName',
+ direction: 'ASC'
+ }
+ ],
+
+ filters: [
+ {
+ property: 'firstName',
+ value : /Ed/
}
- this.labelArray = labelArray;
- }
+ ]
});
-/**
- * @author Ed Spencer
- * @class Ext.data.AbstractStore
+
+ *
+ * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
+ * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
+ * perform these operations instead.
+ *
+ * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
+ * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
+ * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.
+ *
+
+store.filter('eyeColor', 'Brown');
+
+ *
+ * Change the sorting at any time by calling {@link #sort}:
+ *
+
+store.sort('height', 'ASC');
+
+ *
+ * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
+ * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
+ * to the MixedCollection:
+ *
+
+store.sorters.add(new Ext.util.Sorter({
+ property : 'shoeSize',
+ direction: 'ASC'
+}));
+
+store.sort();
+
+ *
+ * Registering with StoreManager
+ *
+ * Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
+ * This makes it easy to reuse the same store in multiple views:
+ *
+
+//this store can be used several times
+Ext.create('Ext.data.Store', {
+ model: 'User',
+ storeId: 'usersStore'
+});
+
+new Ext.List({
+ store: 'usersStore',
+
+ //other config goes here
+});
+
+new Ext.view.View({
+ store: 'usersStore',
+
+ //other config goes here
+});
+
+ *
+ * Further Reading
+ *
+ * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
+ * pieces and how they fit together, see:
+ *
+ *
+ * {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
+ * {@link Ext.data.Model Model} - the core class in the data package
+ * {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
+ *
*
- * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
- * but offers a set of methods used by both of those subclasses.
- *
- * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
- * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what
- * AbstractStore is and is not.
- *
- * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be
- * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a
- * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
- *
- * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
- * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
- * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data -
- * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
- * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
- *
- * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
- * or can be completed on the server. This is controlled by the {@link #remoteSort} and (@link #remoteFilter{ config
- * options. For more information see the {@link #sort} and {@link #filter} methods.
*/
-Ext.define('Ext.data.AbstractStore', {
- requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
-
- mixins: {
- observable: 'Ext.util.Observable',
- sortable: 'Ext.util.Sortable'
- },
-
- statics: {
- create: function(store){
- if (!store.isStore) {
- if (!store.type) {
- store.type = 'store';
- }
- store = Ext.createByAlias('store.' + store.type, store);
- }
- return store;
- }
- },
-
- remoteSort : false,
+Ext.define('Ext.data.Store', {
+ extend: 'Ext.data.AbstractStore',
+
+ alias: 'store.store',
+
+ requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
+ uses: ['Ext.data.proxy.Memory'],
+
+ /**
+ * @cfg {Boolean} remoteSort
+ * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to false .
+ */
+ remoteSort: false,
+
+ /**
+ * @cfg {Boolean} remoteFilter
+ * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to false .
+ */
remoteFilter: false,
+ /**
+ * @cfg {Boolean} remoteGroup
+ * True if the grouping should apply on the server side, false if it is local only. If the
+ * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
+ * helper, automatically sending the grouping information to the server.
+ */
+ remoteGroup : false,
+
/**
* @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
* object or a Proxy instance - see {@link #setProxy} for details.
*/
/**
- * @cfg {Boolean/Object} autoLoad If data is not specified, and if autoLoad is true or an Object, this store's load method
- * is automatically called after creation. If the value of autoLoad is an Object, this Object will be passed to the store's
- * load method. Defaults to false.
+ * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
*/
- autoLoad: false,
/**
- * @cfg {Boolean} autoSync True to automatically sync the Store with its Proxy after every edit to one of its Records.
- * Defaults to false.
+ * @property {String} groupField
+ * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
+ * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
+ * level of grouping, and groups can be fetched via the {@link #getGroups} method.
*/
- autoSync: false,
+ groupField: undefined,
/**
- * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
- * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
- * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
- * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
- * @property batchUpdateMode
+ * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
+ * @property groupDir
* @type String
*/
- batchUpdateMode: 'operation',
+ groupDir: "ASC",
/**
- * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
- * Defaults to true, ignored if {@link #remoteFilter} is true
- * @property filterOnLoad
- * @type Boolean
+ * @cfg {Number} pageSize
+ * The number of records considered to form a 'page'. This is used to power the built-in
+ * paging using the nextPage and previousPage functions. Defaults to 25.
*/
- filterOnLoad: true,
+ pageSize: 25,
/**
- * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
- * Defaults to true, igored if {@link #remoteSort} is true
- * @property sortOnLoad
- * @type Boolean
+ * The page that the Store has most recently loaded (see {@link #loadPage})
+ * @property currentPage
+ * @type Number
*/
- sortOnLoad: true,
+ currentPage: 1,
/**
- * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's constructor
- * instead of a model constructor or name.
- * @property implicitModel
- * @type Boolean
- * @private
+ * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
+ * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
+ * large data sets to be loaded one page at a time but rendered all together.
*/
- implicitModel: false,
+ clearOnPageLoad: true,
/**
- * The string type of the Proxy to create if none is specified. This defaults to creating a {@link Ext.data.proxy.Memory memory proxy}.
- * @property defaultProxyType
- * @type String
+ * @property {Boolean} loading
+ * True if the Store is currently loading via its Proxy
+ * @private
*/
- defaultProxyType: 'memory',
+ loading: false,
/**
- * True if the Store has already been destroyed via {@link #destroyStore}. If this is true, the reference to Store should be deleted
- * as it will not function correctly any more.
- * @property isDestroyed
- * @type Boolean
+ * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
+ * causing the sorters to be reapplied after filtering. Defaults to true
*/
- isDestroyed: false,
-
- isStore: true,
+ sortOnFilter: true,
/**
- * @cfg {String} storeId Optional unique identifier for this store. If present, this Store will be registered with
- * the {@link Ext.data.StoreManager}, making it easy to reuse elsewhere. Defaults to undefined.
+ * @cfg {Boolean} buffered
+ * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
+ * tell the store to pre-fetch records ahead of a time.
*/
-
+ buffered: false,
+
/**
- * @cfg {Array} fields
- * This may be used in place of specifying a {@link #model} configuration. The fields should be a
- * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
- * with these fields. In general this configuration option should be avoided, it exists for the purposes of
- * backwards compatibility. For anything more complicated, such as specifying a particular id property or
- * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model} config.
+ * @cfg {Number} purgePageCount
+ * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
+ * This option is only relevant when the {@link #buffered} option is set to true.
*/
+ purgePageCount: 5,
- sortRoot: 'data',
-
- //documented above
+ isStore: true,
+
+ onClassExtended: function(cls, data) {
+ var model = data.model;
+
+ if (typeof model == 'string') {
+ var onBeforeClassCreated = data.onBeforeClassCreated;
+
+ data.onBeforeClassCreated = function(cls, data) {
+ var me = this;
+
+ Ext.require(model, function() {
+ onBeforeClassCreated.call(me, cls, data);
+ });
+ };
+ }
+ },
+
+ /**
+ * Creates the store.
+ * @param {Object} config (optional) Config object
+ */
constructor: function(config) {
- var me = this,
- filters;
-
- me.addEvents(
- /**
- * @event add
- * Fired when a Model instance has been added to this Store
- * @param {Ext.data.Store} store The store
- * @param {Array} records The Model instances that were added
- * @param {Number} index The index at which the instances were inserted
- */
- 'add',
+ // Clone the config so we don't modify the original config object
+ config = Ext.Object.merge({}, config);
- /**
- * @event remove
- * Fired when a Model instance has been removed from this Store
- * @param {Ext.data.Store} store The Store object
- * @param {Ext.data.Model} record The record that was removed
- * @param {Number} index The index of the record that was removed
- */
- 'remove',
-
- /**
- * @event update
- * Fires when a Record has been updated
- * @param {Store} this
- * @param {Ext.data.Model} record The Model instance that was updated
- * @param {String} operation The update operation being performed. Value may be one of:
- *
- Ext.data.Model.EDIT
- Ext.data.Model.REJECT
- Ext.data.Model.COMMIT
- *
- */
- 'update',
+ var me = this,
+ groupers = config.groupers || me.groupers,
+ groupField = config.groupField || me.groupField,
+ proxy,
+ data;
- /**
- * @event datachanged
- * Fires whenever the records in the Store have changed in some way - this could include adding or removing records,
- * or updating the data in existing records
- * @param {Ext.data.Store} this The data store
- */
- 'datachanged',
+ if (config.buffered || me.buffered) {
+ me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
+ return record.index;
+ });
+ me.pendingRequests = [];
+ me.pagesRequested = [];
- /**
- * @event beforeload
- * Event description
- * @param {Ext.data.Store} store This Store
- * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
- */
- 'beforeload',
+ me.sortOnLoad = false;
+ me.filterOnLoad = false;
+ }
+ me.addEvents(
/**
- * @event load
- * Fires whenever the store reads data from a remote data source.
+ * @event beforeprefetch
+ * Fires before a prefetch occurs. Return false to cancel.
* @param {Ext.data.Store} this
- * @param {Array} records An array of records
- * @param {Boolean} successful True if the operation was successful.
+ * @param {Ext.data.Operation} operation The associated operation
*/
- 'load',
-
+ 'beforeprefetch',
/**
- * @event beforesync
- * Called before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
- * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
+ * @event groupchange
+ * Fired whenever the grouping in the grid changes
+ * @param {Ext.data.Store} store The store
+ * @param {Ext.util.Grouper[]} groupers The array of grouper objects
*/
- 'beforesync',
+ 'groupchange',
/**
- * @event clear
- * Fired after the {@link #removeAll} method is called.
+ * @event load
+ * Fires whenever records have been prefetched
* @param {Ext.data.Store} this
+ * @param {Ext.util.Grouper[]} records An array of records
+ * @param {Boolean} successful True if the operation was successful.
+ * @param {Ext.data.Operation} operation The associated operation
*/
- 'clear'
+ 'prefetch'
);
-
- Ext.apply(me, config);
- // don't use *config* anymore from here on... use *me* instead...
+ data = config.data || me.data;
/**
- * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
- * at which point this is cleared.
- * @private
- * @property removed
- * @type Array
+ * The MixedCollection that holds this store's local cache of records
+ * @property data
+ * @type Ext.util.MixedCollection
*/
- me.removed = [];
+ me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
+ return record.internalId;
+ });
- me.mixins.observable.constructor.apply(me, arguments);
- me.model = Ext.ModelManager.getModel(me.model);
+ if (data) {
+ me.inlineData = data;
+ delete config.data;
+ }
+
+ if (!groupers && groupField) {
+ groupers = [{
+ property : groupField,
+ direction: config.groupDir || me.groupDir
+ }];
+ }
+ delete config.groupers;
/**
- * @property modelDefaults
- * @type Object
- * @private
- * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
- * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
- * for examples. This should not need to be used by application developers.
+ * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
+ * @property groupers
+ * @type Ext.util.MixedCollection
*/
- Ext.applyIf(me, {
- modelDefaults: {}
- });
-
- //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
- if (!me.model && me.fields) {
- me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
- extend: 'Ext.data.Model',
- fields: me.fields,
- proxy: me.proxy || me.defaultProxyType
- });
+ me.groupers = Ext.create('Ext.util.MixedCollection');
+ me.groupers.addAll(me.decodeGroupers(groupers));
- delete me.fields;
+ this.callParent([config]);
+ // don't use *config* anymore from here on... use *me* instead...
- me.implicitModel = true;
+ if (me.groupers.items.length) {
+ me.sort(me.groupers.items, 'prepend', false);
}
- //ensures that the Proxy is instantiated correctly
- me.setProxy(me.proxy || me.model.getProxy());
+ proxy = me.proxy;
+ data = me.inlineData;
- if (me.id && !me.storeId) {
- me.storeId = me.id;
- delete me.id;
+ if (data) {
+ if (proxy instanceof Ext.data.proxy.Memory) {
+ proxy.data = data;
+ me.read();
+ } else {
+ me.add.apply(me, data);
+ }
+
+ me.sort();
+ delete me.inlineData;
+ } else if (me.autoLoad) {
+ Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
+ // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
+ // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
}
+ },
- if (me.storeId) {
- Ext.data.StoreManager.register(me);
+ onBeforeSort: function() {
+ var groupers = this.groupers;
+ if (groupers.getCount() > 0) {
+ this.sort(groupers.items, 'prepend', false);
}
-
- me.mixins.sortable.initSortable.call(me);
-
- /**
- * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
- * @property filters
- * @type Ext.util.MixedCollection
- */
- filters = me.decodeFilters(me.filters);
- me.filters = Ext.create('Ext.util.MixedCollection');
- me.filters.addAll(filters);
},
/**
- * Sets the Store's Proxy by string, config object or Proxy instance
- * @param {String|Object|Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
- * or an Ext.data.proxy.Proxy instance
- * @return {Ext.data.proxy.Proxy} The attached Proxy object
+ * @private
+ * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
+ * @param {Object[]} groupers The groupers array
+ * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
*/
- setProxy: function(proxy) {
- var me = this;
-
- if (proxy instanceof Ext.data.proxy.Proxy) {
- proxy.setModel(me.model);
- } else {
- if (Ext.isString(proxy)) {
- proxy = {
- type: proxy
- };
+ decodeGroupers: function(groupers) {
+ if (!Ext.isArray(groupers)) {
+ if (groupers === undefined) {
+ groupers = [];
+ } else {
+ groupers = [groupers];
}
- Ext.applyIf(proxy, {
- model: me.model
- });
-
- proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
}
-
- me.proxy = proxy;
-
- return me.proxy;
- },
- /**
- * Returns the proxy currently attached to this proxy instance
- * @return {Ext.data.proxy.Proxy} The Proxy instance
- */
- getProxy: function() {
- return this.proxy;
- },
+ var length = groupers.length,
+ Grouper = Ext.util.Grouper,
+ config, i;
- //saves any phantom records
- create: function(data, options) {
- var me = this,
- instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
- operation;
-
- options = options || {};
+ for (i = 0; i < length; i++) {
+ config = groupers[i];
- Ext.applyIf(options, {
- action : 'create',
- records: [instance]
- });
+ if (!(config instanceof Grouper)) {
+ if (Ext.isString(config)) {
+ config = {
+ property: config
+ };
+ }
- operation = Ext.create('Ext.data.Operation', options);
+ Ext.applyIf(config, {
+ root : 'data',
+ direction: "ASC"
+ });
- me.proxy.create(operation, me.onProxyWrite, me);
-
- return instance;
- },
+ //support for 3.x style sorters where a function can be defined as 'fn'
+ if (config.fn) {
+ config.sorterFn = config.fn;
+ }
- read: function() {
- return this.load.apply(this, arguments);
- },
+ //support a function to be passed as a sorter definition
+ if (typeof config == 'function') {
+ config = {
+ sorterFn: config
+ };
+ }
- onProxyRead: Ext.emptyFn,
+ groupers[i] = new Grouper(config);
+ }
+ }
- update: function(options) {
+ return groupers;
+ },
+
+ /**
+ * Group data in the store
+ * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
+ * or an Array of grouper configurations.
+ * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
+ */
+ group: function(groupers, direction) {
var me = this,
- operation;
- options = options || {};
+ hasNew = false,
+ grouper,
+ newGroupers;
+
+ if (Ext.isArray(groupers)) {
+ newGroupers = groupers;
+ } else if (Ext.isObject(groupers)) {
+ newGroupers = [groupers];
+ } else if (Ext.isString(groupers)) {
+ grouper = me.groupers.get(groupers);
+
+ if (!grouper) {
+ grouper = {
+ property : groupers,
+ direction: direction
+ };
+ newGroupers = [grouper];
+ } else if (direction === undefined) {
+ grouper.toggle();
+ } else {
+ grouper.setDirection(direction);
+ }
+ }
+
+ if (newGroupers && newGroupers.length) {
+ hasNew = true;
+ newGroupers = me.decodeGroupers(newGroupers);
+ me.groupers.clear();
+ me.groupers.addAll(newGroupers);
+ }
+
+ if (me.remoteGroup) {
+ me.load({
+ scope: me,
+ callback: me.fireGroupChange
+ });
+ } else {
+ // need to explicitly force a sort if we have groupers
+ me.sort(null, null, null, hasNew);
+ me.fireGroupChange();
+ }
+ },
- Ext.applyIf(options, {
- action : 'update',
- records: me.getUpdatedRecords()
+ /**
+ * Clear any groupers in the store
+ */
+ clearGrouping: function(){
+ var me = this;
+ // Clear any groupers we pushed on to the sorters
+ me.groupers.each(function(grouper){
+ me.sorters.remove(grouper);
});
+ me.groupers.clear();
+ if (me.remoteGroup) {
+ me.load({
+ scope: me,
+ callback: me.fireGroupChange
+ });
+ } else {
+ me.sort();
+ me.fireEvent('groupchange', me, me.groupers);
+ }
+ },
- operation = Ext.create('Ext.data.Operation', options);
-
- return me.proxy.update(operation, me.onProxyWrite, me);
+ /**
+ * Checks if the store is currently grouped
+ * @return {Boolean} True if the store is grouped.
+ */
+ isGrouped: function() {
+ return this.groupers.getCount() > 0;
},
/**
+ * Fires the groupchange event. Abstracted out so we can use it
+ * as a callback
* @private
- * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
- * the updates provided by the Proxy
*/
- onProxyWrite: function(operation) {
- var me = this,
- success = operation.wasSuccessful(),
- records = operation.getRecords();
+ fireGroupChange: function(){
+ this.fireEvent('groupchange', this, this.groupers);
+ },
- switch (operation.action) {
- case 'create':
- me.onCreateRecords(records, operation, success);
- break;
- case 'update':
- me.onUpdateRecords(records, operation, success);
- break;
- case 'destroy':
- me.onDestroyRecords(records, operation, success);
- break;
- }
+ /**
+ * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
+ * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
+
+var myStore = Ext.create('Ext.data.Store', {
+ groupField: 'color',
+ groupDir : 'DESC'
+});
- if (success) {
- me.fireEvent('write', me, operation);
- me.fireEvent('datachanged', me);
- }
- //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
- Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
+myStore.getGroups(); //returns:
+[
+ {
+ name: 'yellow',
+ children: [
+ //all records where the color field is 'yellow'
+ ]
},
+ {
+ name: 'red',
+ children: [
+ //all records where the color field is 'red'
+ ]
+ }
+]
+
+ * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
+ * @return {Object/Object[]} The grouped data
+ */
+ getGroups: function(requestGroupString) {
+ var records = this.data.items,
+ length = records.length,
+ groups = [],
+ pointers = {},
+ record,
+ groupStr,
+ group,
+ i;
+ for (i = 0; i < length; i++) {
+ record = records[i];
+ groupStr = this.getGroupString(record);
+ group = pointers[groupStr];
- //tells the attached proxy to destroy the given records
- destroy: function(options) {
- var me = this,
- operation;
-
- options = options || {};
+ if (group === undefined) {
+ group = {
+ name: groupStr,
+ children: []
+ };
- Ext.applyIf(options, {
- action : 'destroy',
- records: me.getRemovedRecords()
- });
+ groups.push(group);
+ pointers[groupStr] = group;
+ }
- operation = Ext.create('Ext.data.Operation', options);
+ group.children.push(record);
+ }
- return me.proxy.destroy(operation, me.onProxyWrite, me);
+ return requestGroupString ? pointers[requestGroupString] : groups;
},
/**
* @private
- * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
- * to onProxyWrite.
+ * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
+ * matching a certain group.
*/
- onBatchOperationComplete: function(batch, operation) {
- return this.onProxyWrite(operation);
+ getGroupsForGrouper: function(records, grouper) {
+ var length = records.length,
+ groups = [],
+ oldValue,
+ newValue,
+ record,
+ group,
+ i;
+
+ for (i = 0; i < length; i++) {
+ record = records[i];
+ newValue = grouper.getGroupString(record);
+
+ if (newValue !== oldValue) {
+ group = {
+ name: newValue,
+ grouper: grouper,
+ records: []
+ };
+ groups.push(group);
+ }
+
+ group.records.push(record);
+
+ oldValue = newValue;
+ }
+
+ return groups;
},
/**
* @private
- * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
- * and updates the Store's internal data MixedCollection.
+ * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
+ * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
+ * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
+ * @param {Ext.data.Model[]} records The set or subset of records to group
+ * @param {Number} grouperIndex The grouper index to retrieve
+ * @return {Object[]} The grouped records
*/
- onBatchComplete: function(batch, operation) {
+ getGroupsForGrouperIndex: function(records, grouperIndex) {
var me = this,
- operations = batch.operations,
- length = operations.length,
+ groupers = me.groupers,
+ grouper = groupers.getAt(grouperIndex),
+ groups = me.getGroupsForGrouper(records, grouper),
+ length = groups.length,
i;
- me.suspendEvents();
+ if (grouperIndex + 1 < groupers.length) {
+ for (i = 0; i < length; i++) {
+ groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
+ }
+ }
for (i = 0; i < length; i++) {
- me.onProxyWrite(operations[i]);
+ groups[i].depth = grouperIndex;
}
- me.resumeEvents();
-
- me.fireEvent('datachanged', me);
- },
-
- onBatchException: function(batch, operation) {
- // //decide what to do... could continue with the next operation
- // batch.start();
- //
- // //or retry the last operation
- // batch.retry();
+ return groups;
},
/**
* @private
- * Filter function for new records.
+ * Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
+ * this case grouping by genre and then author in a fictional books dataset):
+
+[
+ {
+ name: 'Fantasy',
+ depth: 0,
+ records: [
+ //book1, book2, book3, book4
+ ],
+ children: [
+ {
+ name: 'Rowling',
+ depth: 1,
+ records: [
+ //book1, book2
+ ]
+ },
+ {
+ name: 'Tolkein',
+ depth: 1,
+ records: [
+ //book3, book4
+ ]
+ }
+ ]
+ }
+]
+
+ * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
+ * function correctly so this should only be set to false if the Store is known to already be sorted correctly
+ * (defaults to true)
+ * @return {Object[]} The group data
*/
- filterNew: function(item) {
- // only want phantom records that are valid
- return item.phantom === true && item.isValid();
- },
+ getGroupData: function(sort) {
+ var me = this;
+ if (sort !== false) {
+ me.sort();
+ }
- /**
- * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
- * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
- * @return {Array} The Model instances
- */
- getNewRecords: function() {
- return [];
+ return me.getGroupsForGrouperIndex(me.data.items, 0);
},
/**
- * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
- * @return {Array} The updated Model instances
+ * Returns the string to group on for a given model instance. The default implementation of this method returns
+ * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
+ * group by the first letter of a model's 'name' field, use the following code:
+
+Ext.create('Ext.data.Store', {
+ groupDir: 'ASC',
+ getGroupString: function(instance) {
+ return instance.get('name')[0];
+ }
+});
+
+ * @param {Ext.data.Model} instance The model instance
+ * @return {String} The string to compare when forming groups
*/
- getUpdatedRecords: function() {
- return [];
+ getGroupString: function(instance) {
+ var group = this.groupers.first();
+ if (group) {
+ return instance.get(group.property);
+ }
+ return '';
},
-
/**
- * @private
- * Filter function for updated records.
+ * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
+ * See also {@link #add}
.
+ * @param {Number} index The start index at which to insert the passed Records.
+ * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
*/
- filterUpdated: function(item) {
- // only want dirty records, not phantoms that are valid
- return item.dirty === true && item.phantom !== true && item.isValid();
- },
+ insert: function(index, records) {
+ var me = this,
+ sync = false,
+ i,
+ record,
+ len;
- /**
- * Returns any records that have been removed from the store but not yet destroyed on the proxy.
- * @return {Array} The removed Model instances
- */
- getRemovedRecords: function() {
- return this.removed;
- },
+ records = [].concat(records);
+ for (i = 0, len = records.length; i < len; i++) {
+ record = me.createModel(records[i]);
+ record.set(me.modelDefaults);
+ // reassign the model in the array in case it wasn't created yet
+ records[i] = record;
- filter: function(filters, value) {
+ me.data.insert(index + i, record);
+ record.join(me);
+ sync = sync || record.phantom === true;
+ }
+
+ if (me.snapshot) {
+ me.snapshot.addAll(records);
+ }
+
+ me.fireEvent('add', me, records, index);
+ me.fireEvent('datachanged', me);
+ if (me.autoSync && sync) {
+ me.sync();
+ }
},
/**
- * @private
- * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
- * @param {Array} filters The filters array
- * @return {Array} Array of Ext.util.Filter objects
+ * Adds Model instance to the Store. This method accepts either:
+ *
+ * - An array of Model instances or Model configuration objects.
+ * - Any number of Model instance or Model configuration object arguments.
+ *
+ * The new Model instances will be added at the end of the existing collection.
+ *
+ * Sample usage:
+ *
+ * myStore.add({some: 'data'}, {some: 'other data'});
+ *
+ * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
+ * or Model configuration objects, or variable number of Model instance or config arguments.
+ * @return {Ext.data.Model[]} The model instances that were added
*/
- decodeFilters: function(filters) {
- if (!Ext.isArray(filters)) {
- if (filters === undefined) {
- filters = [];
- } else {
- filters = [filters];
- }
+ add: function(records) {
+ //accept both a single-argument array of records, or any number of record arguments
+ if (!Ext.isArray(records)) {
+ records = Array.prototype.slice.apply(arguments);
}
- var length = filters.length,
- Filter = Ext.util.Filter,
- config, i;
-
- for (i = 0; i < length; i++) {
- config = filters[i];
+ var me = this,
+ i = 0,
+ length = records.length,
+ record;
- if (!(config instanceof Filter)) {
- Ext.apply(config, {
- root: 'data'
- });
+ for (; i < length; i++) {
+ record = me.createModel(records[i]);
+ // reassign the model in the array in case it wasn't created yet
+ records[i] = record;
+ }
- //support for 3.x style filters where a function can be defined as 'fn'
- if (config.fn) {
- config.filterFn = config.fn;
- }
+ me.insert(me.data.length, records);
- //support a function to be passed as a filter definition
- if (typeof config == 'function') {
- config = {
- filterFn: config
- };
- }
+ return records;
+ },
- filters[i] = new Filter(config);
- }
+ /**
+ * Converts a literal to a model, if it's not a model already
+ * @private
+ * @param record {Ext.data.Model/Object} The record to create
+ * @return {Ext.data.Model}
+ */
+ createModel: function(record) {
+ if (!record.isModel) {
+ record = Ext.ModelManager.create(record, this.model);
}
- return filters;
+ return record;
},
- clearFilter: function(supressEvent) {
-
+ /**
+ * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
+ * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
+ * Returning false aborts and exits the iteration.
+ * @param {Object} scope (optional) The scope (this
reference) in which the function is executed.
+ * Defaults to the current {@link Ext.data.Model Record} in the iteration.
+ */
+ each: function(fn, scope) {
+ this.data.each(fn, scope);
},
- isFiltered: function() {
+ /**
+ * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
+ * 'datachanged' event after removal.
+ * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
+ */
+ remove: function(records, /* private */ isMove) {
+ if (!Ext.isArray(records)) {
+ records = [records];
+ }
- },
+ /*
+ * Pass the isMove parameter if we know we're going to be re-inserting this record
+ */
+ isMove = isMove === true;
+ var me = this,
+ sync = false,
+ i = 0,
+ length = records.length,
+ isPhantom,
+ index,
+ record;
- filterBy: function(fn, scope) {
+ for (; i < length; i++) {
+ record = records[i];
+ index = me.data.indexOf(record);
- },
-
- /**
- * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
- * and deleted records in the store, updating the Store's internal representation of the records
- * as each operation completes.
- */
- sync: function() {
- var me = this,
- options = {},
- toCreate = me.getNewRecords(),
- toUpdate = me.getUpdatedRecords(),
- toDestroy = me.getRemovedRecords(),
- needsSync = false;
+ if (me.snapshot) {
+ me.snapshot.remove(record);
+ }
- if (toCreate.length > 0) {
- options.create = toCreate;
- needsSync = true;
- }
+ if (index > -1) {
+ isPhantom = record.phantom === true;
+ if (!isMove && !isPhantom) {
+ // don't push phantom records onto removed
+ me.removed.push(record);
+ }
- if (toUpdate.length > 0) {
- options.update = toUpdate;
- needsSync = true;
- }
+ record.unjoin(me);
+ me.data.remove(record);
+ sync = sync || !isPhantom;
- if (toDestroy.length > 0) {
- options.destroy = toDestroy;
- needsSync = true;
+ me.fireEvent('remove', me, record, index);
+ }
}
- if (needsSync && me.fireEvent('beforesync', options) !== false) {
- me.proxy.batch(options, me.getBatchListeners());
+ me.fireEvent('datachanged', me);
+ if (!isMove && me.autoSync && sync) {
+ me.sync();
}
},
-
/**
- * @private
- * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
- * This is broken out into a separate function to allow for customisation of the listeners
- * @return {Object} The listeners object
+ * Removes the model instance at the given index
+ * @param {Number} index The record index
*/
- getBatchListeners: function() {
- var me = this,
- listeners = {
- scope: me,
- exception: me.onBatchException
- };
+ removeAt: function(index) {
+ var record = this.getAt(index);
- if (me.batchUpdateMode == 'operation') {
- listeners.operationcomplete = me.onBatchOperationComplete;
- } else {
- listeners.complete = me.onBatchComplete;
+ if (record) {
+ this.remove(record);
}
-
- return listeners;
- },
-
- //deprecated, will be removed in 5.0
- save: function() {
- return this.sync.apply(this, arguments);
},
/**
- * Loads the Store using its configured {@link #proxy}.
- * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
- * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
+ * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
+ * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
+ * instances into the Store and calling an optional callback if required. Example usage:
+ *
+
+store.load({
+ scope : this,
+ callback: function(records, operation, success) {
+ //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
+ console.log(records);
+ }
+});
+
+ *
+ * If the callback scope does not need to be set, a function can simply be passed:
+ *
+
+store.load(function(records, operation, success) {
+ console.log('loaded records');
+});
+
+ *
+ * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
*/
load: function(options) {
- var me = this,
- operation;
+ var me = this;
options = options || {};
+ if (Ext.isFunction(options)) {
+ options = {
+ callback: options
+ };
+ }
+
Ext.applyIf(options, {
- action : 'read',
- filters: me.filters.items,
- sorters: me.getSorters()
+ groupers: me.groupers.items,
+ page: me.currentPage,
+ start: (me.currentPage - 1) * me.pageSize,
+ limit: me.pageSize,
+ addRecords: false
});
-
- operation = Ext.create('Ext.data.Operation', options);
- if (me.fireEvent('beforeload', me, operation) !== false) {
- me.loading = true;
- me.proxy.read(operation, me.onProxyLoad, me);
- }
-
- return me;
+ return me.callParent([options]);
},
/**
* @private
- * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
- * @param {Ext.data.Model} record The model instance that was edited
+ * Called internally when a Proxy has completed a load request
*/
- afterEdit : function(record) {
- var me = this;
-
- if (me.autoSync) {
- me.sync();
+ onProxyLoad: function(operation) {
+ var me = this,
+ resultSet = operation.getResultSet(),
+ records = operation.getRecords(),
+ successful = operation.wasSuccessful();
+
+ if (resultSet) {
+ me.totalCount = resultSet.total;
}
-
- me.fireEvent('update', me, record, Ext.data.Model.EDIT);
+
+ if (successful) {
+ me.loadRecords(records, operation);
+ }
+
+ me.loading = false;
+ me.fireEvent('load', me, records, successful);
+
+ //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
+ //People are definitely using this so can't deprecate safely until 2.x
+ me.fireEvent('read', me, records, operation.wasSuccessful());
+
+ //this is a callback that would have been passed to the 'read' function and is optional
+ Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
},
/**
+ * Create any new records when a write is returned from the server.
* @private
- * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
- * @param {Ext.data.Model} record The model instance that was edited
+ * @param {Ext.data.Model[]} records The array of new records
+ * @param {Ext.data.Operation} operation The operation that just completed
+ * @param {Boolean} success True if the operation was successful
*/
- afterReject : function(record) {
- this.fireEvent('update', this, record, Ext.data.Model.REJECT);
+ onCreateRecords: function(records, operation, success) {
+ if (success) {
+ var i = 0,
+ data = this.data,
+ snapshot = this.snapshot,
+ length = records.length,
+ originalRecords = operation.records,
+ record,
+ original,
+ index;
+
+ /*
+ * Loop over each record returned from the server. Assume they are
+ * returned in order of how they were sent. If we find a matching
+ * record, replace it with the newly created one.
+ */
+ for (; i < length; ++i) {
+ record = records[i];
+ original = originalRecords[i];
+ if (original) {
+ index = data.indexOf(original);
+ if (index > -1) {
+ data.removeAt(index);
+ data.insert(index, record);
+ }
+ if (snapshot) {
+ index = snapshot.indexOf(original);
+ if (index > -1) {
+ snapshot.removeAt(index);
+ snapshot.insert(index, record);
+ }
+ }
+ record.phantom = false;
+ record.join(this);
+ }
+ }
+ }
},
/**
+ * Update any records when a write is returned from the server.
* @private
- * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
- * @param {Ext.data.Model} record The model instance that was edited
+ * @param {Ext.data.Model[]} records The array of updated records
+ * @param {Ext.data.Operation} operation The operation that just completed
+ * @param {Boolean} success True if the operation was successful
*/
- afterCommit : function(record) {
- this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
- },
-
- clearData: Ext.emptyFn,
-
- destroyStore: function() {
- var me = this;
-
- if (!me.isDestroyed) {
- if (me.storeId) {
- Ext.data.StoreManager.unregister(me);
- }
- me.clearData();
- me.data = null;
- me.tree = null;
- // Ext.destroy(this.proxy);
- me.reader = me.writer = null;
- me.clearListeners();
- me.isDestroyed = true;
+ onUpdateRecords: function(records, operation, success){
+ if (success) {
+ var i = 0,
+ length = records.length,
+ data = this.data,
+ snapshot = this.snapshot,
+ record;
- if (me.implicitModel) {
- Ext.destroy(me.model);
+ for (; i < length; ++i) {
+ record = records[i];
+ data.replace(record);
+ if (snapshot) {
+ snapshot.replace(record);
+ }
+ record.join(this);
}
}
},
-
- doSort: function(sorterFn) {
- var me = this;
- if (me.remoteSort) {
- //the load function will pick up the new sorters and request the sorted data from the proxy
- me.load();
- } else {
- me.data.sortBy(sorterFn);
- me.fireEvent('datachanged', me);
- }
- },
-
- getCount: Ext.emptyFn,
-
- getById: Ext.emptyFn,
-
- /**
- * Removes all records from the store. This method does a "fast remove",
- * individual remove events are not called. The {@link #clear} event is
- * fired upon completion.
- * @method
- */
- removeAll: Ext.emptyFn,
- // individual substores should implement a "fast" remove
- // and fire a clear event afterwards
/**
- * Returns true if the Store is currently performing a load operation
- * @return {Boolean} True if the Store is currently loading
+ * Remove any records when a write is returned from the server.
+ * @private
+ * @param {Ext.data.Model[]} records The array of removed records
+ * @param {Ext.data.Operation} operation The operation that just completed
+ * @param {Boolean} success True if the operation was successful
*/
- isLoading: function() {
- return this.loading;
- }
-});
-
-/**
- * @class Ext.util.Grouper
- * @extends Ext.util.Sorter
-
-Represents a single grouper that can be applied to a Store. The grouper works
-in the same fashion as the {@link Ext.util.Sorter}.
-
- * @markdown
- */
-
-Ext.define('Ext.util.Grouper', {
+ onDestroyRecords: function(records, operation, success){
+ if (success) {
+ var me = this,
+ i = 0,
+ length = records.length,
+ data = me.data,
+ snapshot = me.snapshot,
+ record;
- /* Begin Definitions */
+ for (; i < length; ++i) {
+ record = records[i];
+ record.unjoin(me);
+ data.remove(record);
+ if (snapshot) {
+ snapshot.remove(record);
+ }
+ }
+ me.removed = [];
+ }
+ },
- extend: 'Ext.util.Sorter',
+ //inherit docs
+ getNewRecords: function() {
+ return this.data.filterBy(this.filterNew).items;
+ },
- /* End Definitions */
+ //inherit docs
+ getUpdatedRecords: function() {
+ return this.data.filterBy(this.filterUpdated).items;
+ },
/**
- * Returns the value for grouping to be used.
- * @param {Ext.data.Model} instance The Model instance
- * @return {String} The group string for this model
+ * Filters the loaded set of records by a given set of filters.
+ *
+ * Filtering by single field:
+ *
+ * store.filter("email", /\.com$/);
+ *
+ * Using multiple filters:
+ *
+ * store.filter([
+ * {property: "email", value: /\.com$/},
+ * {filterFn: function(item) { return item.get("age") > 10; }}
+ * ]);
+ *
+ * Using Ext.util.Filter instances instead of config objects
+ * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
+ *
+ * store.filter([
+ * Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
+ * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
+ * ]);
+ *
+ * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
+ * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
+ * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
+ * pass in a property string
+ * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
*/
- getGroupString: function(instance) {
- return instance.get(this.property);
- }
-});
-/**
- * @author Ed Spencer
- * @class Ext.data.Store
- * @extends Ext.data.AbstractStore
- *
- * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
- * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
- * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.
- *
- * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
- *
-
-// Set up a {@link Ext.data.Model model} to use in our Store
-Ext.define('User', {
- extend: 'Ext.data.Model',
- fields: [
- {name: 'firstName', type: 'string'},
- {name: 'lastName', type: 'string'},
- {name: 'age', type: 'int'},
- {name: 'eyeColor', type: 'string'}
- ]
-});
-
-var myStore = new Ext.data.Store({
- model: 'User',
- proxy: {
- type: 'ajax',
- url : '/users.json',
- reader: {
- type: 'json',
- root: 'users'
+ filter: function(filters, value) {
+ if (Ext.isString(filters)) {
+ filters = {
+ property: filters,
+ value: value
+ };
}
- },
- autoLoad: true
-});
-
- * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
- * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
- * {@link Ext.data.reader.Json see the docs on JsonReader} for details.
- *
- * Inline data
- *
- * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
- * into Model instances:
- *
-
-new Ext.data.Store({
- model: 'User',
- data : [
- {firstName: 'Ed', lastName: 'Spencer'},
- {firstName: 'Tommy', lastName: 'Maintz'},
- {firstName: 'Aaron', lastName: 'Conran'},
- {firstName: 'Jamie', lastName: 'Avins'}
- ]
-});
-
- *
- * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't need
- * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
- * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).
- *
- * Additional data can also be loaded locally using {@link #add}.
- *
- * Loading Nested Data
- *
- * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
- * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load a nested dataset
- * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
- * docs for a full explanation:
- *
-
-var store = new Ext.data.Store({
- autoLoad: true,
- model: "User",
- proxy: {
- type: 'ajax',
- url : 'users.json',
- reader: {
- type: 'json',
- root: 'users'
- }
- }
-});
-
- *
- * Which would consume a response like this:
- *
-
-{
- "users": [
- {
- "id": 1,
- "name": "Ed",
- "orders": [
- {
- "id": 10,
- "total": 10.76,
- "status": "invoiced"
- },
- {
- "id": 11,
- "total": 13.45,
- "status": "shipped"
- }
- ]
- }
- ]
-}
-
- *
- * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
- *
- * Filtering and Sorting
- *
- * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
- * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
- * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
- *
-
-var store = new Ext.data.Store({
- model: 'User',
- sorters: [
- {
- property : 'age',
- direction: 'DESC'
- },
- {
- property : 'firstName',
- direction: 'ASC'
- }
- ],
+ var me = this,
+ decoded = me.decodeFilters(filters),
+ i = 0,
+ doLocalSort = me.sortOnFilter && !me.remoteSort,
+ length = decoded.length;
- filters: [
- {
- property: 'firstName',
- value : /Ed/
+ for (; i < length; i++) {
+ me.filters.replace(decoded[i]);
}
- ]
-});
-
- *
- * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
- * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
- * perform these operations instead.
- *
- * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
- * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
- * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.
- *
-
-store.filter('eyeColor', 'Brown');
-
- *
- * Change the sorting at any time by calling {@link #sort}:
- *
-
-store.sort('height', 'ASC');
-
- *
- * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
- * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
- * to the MixedCollection:
- *
-
-store.sorters.add(new Ext.util.Sorter({
- property : 'shoeSize',
- direction: 'ASC'
-}));
-
-store.sort();
-
- *
- * Registering with StoreManager
- *
- * Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
- * This makes it easy to reuse the same store in multiple views:
- *
-
-//this store can be used several times
-new Ext.data.Store({
- model: 'User',
- storeId: 'usersStore'
-});
-new Ext.List({
- store: 'usersStore',
+ if (me.remoteFilter) {
+ //the load function will pick up the new filters and request the filtered data from the proxy
+ me.load();
+ } else {
+ /**
+ * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
+ * records when a filter is removed or changed
+ * @property snapshot
+ * @type Ext.util.MixedCollection
+ */
+ if (me.filters.getCount()) {
+ me.snapshot = me.snapshot || me.data.clone();
+ me.data = me.data.filter(me.filters.items);
- //other config goes here
-});
+ if (doLocalSort) {
+ me.sort();
+ }
+ // fire datachanged event if it hasn't already been fired by doSort
+ if (!doLocalSort || me.sorters.length < 1) {
+ me.fireEvent('datachanged', me);
+ }
+ }
+ }
+ },
-new Ext.view.View({
- store: 'usersStore',
+ /**
+ * Revert to a view of the Record cache with no filtering applied.
+ * @param {Boolean} suppressEvent If true the filter is cleared silently without firing the
+ * {@link #datachanged} event.
+ */
+ clearFilter: function(suppressEvent) {
+ var me = this;
- //other config goes here
-});
-
- *
- * Further Reading
- *
- * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
- * pieces and how they fit together, see:
- *
- *
- * {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
- * {@link Ext.data.Model Model} - the core class in the data package
- * {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
- *
- *
- */
-Ext.define('Ext.data.Store', {
- extend: 'Ext.data.AbstractStore',
+ me.filters.clear();
- alias: 'store.store',
+ if (me.remoteFilter) {
+ me.load();
+ } else if (me.isFiltered()) {
+ me.data = me.snapshot.clone();
+ delete me.snapshot;
- requires: ['Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
- uses: ['Ext.data.proxy.Memory'],
+ if (suppressEvent !== true) {
+ me.fireEvent('datachanged', me);
+ }
+ }
+ },
/**
- * @cfg {Boolean} remoteSort
- * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to false .
+ * Returns true if this store is currently filtered
+ * @return {Boolean}
*/
- remoteSort: false,
+ isFiltered: function() {
+ var snapshot = this.snapshot;
+ return !! snapshot && snapshot !== this.data;
+ },
/**
- * @cfg {Boolean} remoteFilter
- * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to false .
- */
- remoteFilter: false,
-
- /**
- * @cfg {Boolean} remoteGroup
- * True if the grouping should apply on the server side, false if it is local only (defaults to false). If the
- * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
- * helper, automatically sending the grouping information to the server.
+ * Filter by a function. The specified function will be called for each
+ * Record in this Store. If the function returns true the Record is included,
+ * otherwise it is filtered out.
+ * @param {Function} fn The function to be called. It will be passed the following parameters:
+ * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to this Store.
*/
- remoteGroup : false,
+ filterBy: function(fn, scope) {
+ var me = this;
- /**
- * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
- * object or a Proxy instance - see {@link #setProxy} for details.
- */
+ me.snapshot = me.snapshot || me.data.clone();
+ me.data = me.queryBy(fn, scope || me);
+ me.fireEvent('datachanged', me);
+ },
/**
- * @cfg {Array} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
- */
+ * Query the cached records in this Store using a filtering function. The specified function
+ * will be called with each record in this Store. If the function returns true the record is
+ * included in the results.
+ * @param {Function} fn The function to be called. It will be passed the following parameters:
+ * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to this Store.
+ * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
+ **/
+ queryBy: function(fn, scope) {
+ var me = this,
+ data = me.snapshot || me.data;
+ return data.filterBy(fn, scope || me);
+ },
/**
- * @cfg {String} model The {@link Ext.data.Model} associated with this store
+ * Loads an array of data straight into the Store.
+ *
+ * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
+ * processed by a reader). If your data requires processing to decode the data structure, use a
+ * {@link Ext.data.proxy.Memory MemoryProxy} instead.
+ *
+ * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
+ * into model instances.
+ * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
+ * to remove the old ones first.
*/
+ loadData: function(data, append) {
+ var model = this.model,
+ length = data.length,
+ newData = [],
+ i,
+ record;
- /**
- * The (optional) field by which to group data in the store. Internally, grouping is very similar to sorting - the
- * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
- * level of grouping, and groups can be fetched via the {@link #getGroups} method.
- * @property groupField
- * @type String
- */
- groupField: undefined,
+ //make sure each data element is an Ext.data.Model instance
+ for (i = 0; i < length; i++) {
+ record = data[i];
- /**
- * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
- * @property groupDir
- * @type String
- */
- groupDir: "ASC",
+ if (!(record instanceof Ext.data.Model)) {
+ record = Ext.ModelManager.create(record, model);
+ }
+ newData.push(record);
+ }
- /**
- * @cfg {Number} pageSize
- * The number of records considered to form a 'page'. This is used to power the built-in
- * paging using the nextPage and previousPage functions. Defaults to 25.
- */
- pageSize: 25,
+ this.loadRecords(newData, {addRecords: append});
+ },
- /**
- * The page that the Store has most recently loaded (see {@link #loadPage})
- * @property currentPage
- * @type Number
- */
- currentPage: 1,
/**
- * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
- * {@link #nextPage} or {@link #previousPage} (defaults to true). Setting to false keeps existing records, allowing
- * large data sets to be loaded one page at a time but rendered all together.
- */
- clearOnPageLoad: true,
+ * Loads data via the bound Proxy's reader
+ *
+ * Use this method if you are attempting to load data and want to utilize the configured data reader.
+ *
+ * @param {Object[]} data The full JSON object you'd like to load into the Data store.
+ * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
+ * to remove the old ones first.
+ */
+ loadRawData : function(data, append) {
+ var me = this,
+ result = me.proxy.reader.read(data),
+ records = result.records;
+
+ if (result.success) {
+ me.loadRecords(records, { addRecords: append });
+ me.fireEvent('load', me, records, true);
+ }
+ },
- /**
- * True if the Store is currently loading via its Proxy
- * @property loading
- * @type Boolean
- * @private
- */
- loading: false,
/**
- * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
- * causing the sorters to be reapplied after filtering. Defaults to true
- */
- sortOnFilter: true,
-
- /**
- * @cfg {Boolean} buffered
- * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
- * tell the store to pre-fetch records ahead of a time.
- */
- buffered: false,
-
- /**
- * @cfg {Number} purgePageCount
- * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
- * This option is only relevant when the {@link #buffered} option is set to true.
+ * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
+ * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
+ * @param {Ext.data.Model[]} records The array of records to load
+ * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
*/
- purgePageCount: 5,
+ loadRecords: function(records, options) {
+ var me = this,
+ i = 0,
+ length = records.length;
- isStore: true,
+ options = options || {};
- /**
- * Creates the store.
- * @param {Object} config (optional) Config object
- */
- constructor: function(config) {
- config = config || {};
- var me = this,
- groupers = config.groupers || me.groupers,
- groupField = config.groupField || me.groupField,
- proxy,
- data;
-
- if (config.buffered || me.buffered) {
- me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
- return record.index;
- });
- me.pendingRequests = [];
- me.pagesRequested = [];
-
- me.sortOnLoad = false;
- me.filterOnLoad = false;
+ if (!options.addRecords) {
+ delete me.snapshot;
+ me.clearData();
}
-
- me.addEvents(
- /**
- * @event beforeprefetch
- * Fires before a prefetch occurs. Return false to cancel.
- * @param {Ext.data.store} this
- * @param {Ext.data.Operation} operation The associated operation
- */
- 'beforeprefetch',
- /**
- * @event groupchange
- * Fired whenever the grouping in the grid changes
- * @param {Ext.data.Store} store The store
- * @param {Array} groupers The array of grouper objects
- */
- 'groupchange',
- /**
- * @event load
- * Fires whenever records have been prefetched
- * @param {Ext.data.store} this
- * @param {Array} records An array of records
- * @param {Boolean} successful True if the operation was successful.
- * @param {Ext.data.Operation} operation The associated operation
- */
- 'prefetch'
- );
- data = config.data || me.data;
- /**
- * The MixedCollection that holds this store's local cache of records
- * @property data
- * @type Ext.util.MixedCollection
- */
- me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
- return record.internalId;
- });
+ me.data.addAll(records);
- if (data) {
- me.inlineData = data;
- delete config.data;
- }
-
- if (!groupers && groupField) {
- groupers = [{
- property : groupField,
- direction: config.groupDir || me.groupDir
- }];
- }
- delete config.groupers;
-
- /**
- * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
- * @property groupers
- * @type Ext.util.MixedCollection
- */
- me.groupers = Ext.create('Ext.util.MixedCollection');
- me.groupers.addAll(me.decodeGroupers(groupers));
+ //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
+ for (; i < length; i++) {
+ if (options.start !== undefined) {
+ records[i].index = options.start + i;
- this.callParent([config]);
- // don't use *config* anymore from here on... use *me* instead...
-
- if (me.groupers.items.length) {
- me.sort(me.groupers.items, 'prepend', false);
+ }
+ records[i].join(me);
}
- proxy = me.proxy;
- data = me.inlineData;
+ /*
+ * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
+ * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
+ * datachanged event is fired by the call to this.add, above.
+ */
+ me.suspendEvents();
- if (data) {
- if (proxy instanceof Ext.data.proxy.Memory) {
- proxy.data = data;
- me.read();
- } else {
- me.add.apply(me, data);
- }
+ if (me.filterOnLoad && !me.remoteFilter) {
+ me.filter();
+ }
+ if (me.sortOnLoad && !me.remoteSort) {
me.sort();
- delete me.inlineData;
- } else if (me.autoLoad) {
- Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
- // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
- // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
}
+
+ me.resumeEvents();
+ me.fireEvent('datachanged', me, records);
},
-
- onBeforeSort: function() {
- this.sort(this.groupers.items, 'prepend', false);
- },
-
+
+ // PAGING METHODS
/**
- * @private
- * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
- * @param {Array} groupers The groupers array
- * @return {Array} Array of Ext.util.Grouper objects
+ * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
+ * load operation, passing in calculated 'start' and 'limit' params
+ * @param {Number} page The number of the page to load
+ * @param {Object} options See options for {@link #load}
*/
- decodeGroupers: function(groupers) {
- if (!Ext.isArray(groupers)) {
- if (groupers === undefined) {
- groupers = [];
- } else {
- groupers = [groupers];
- }
- }
-
- var length = groupers.length,
- Grouper = Ext.util.Grouper,
- config, i;
+ loadPage: function(page, options) {
+ var me = this;
+ options = Ext.apply({}, options);
- for (i = 0; i < length; i++) {
- config = groupers[i];
+ me.currentPage = page;
- if (!(config instanceof Grouper)) {
- if (Ext.isString(config)) {
- config = {
- property: config
- };
- }
-
- Ext.applyIf(config, {
- root : 'data',
- direction: "ASC"
- });
+ me.read(Ext.applyIf(options, {
+ page: page,
+ start: (page - 1) * me.pageSize,
+ limit: me.pageSize,
+ addRecords: !me.clearOnPageLoad
+ }));
+ },
- //support for 3.x style sorters where a function can be defined as 'fn'
- if (config.fn) {
- config.sorterFn = config.fn;
- }
+ /**
+ * Loads the next 'page' in the current data set
+ * @param {Object} options See options for {@link #load}
+ */
+ nextPage: function(options) {
+ this.loadPage(this.currentPage + 1, options);
+ },
- //support a function to be passed as a sorter definition
- if (typeof config == 'function') {
- config = {
- sorterFn: config
- };
- }
+ /**
+ * Loads the previous 'page' in the current data set
+ * @param {Object} options See options for {@link #load}
+ */
+ previousPage: function(options) {
+ this.loadPage(this.currentPage - 1, options);
+ },
- groupers[i] = new Grouper(config);
- }
- }
+ // private
+ clearData: function() {
+ var me = this;
+ me.data.each(function(record) {
+ record.unjoin(me);
+ });
- return groupers;
+ me.data.clear();
},
-
+
+ // Buffering
/**
- * Group data in the store
- * @param {String|Array} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
- * or an Array of grouper configurations.
- * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
+ * Prefetches data into the store using its configured {@link #proxy}.
+ * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
+ * See {@link #load}
*/
- group: function(groupers, direction) {
+ prefetch: function(options) {
var me = this,
- grouper,
- newGroupers;
-
- if (Ext.isArray(groupers)) {
- newGroupers = groupers;
- } else if (Ext.isObject(groupers)) {
- newGroupers = [groupers];
- } else if (Ext.isString(groupers)) {
- grouper = me.groupers.get(groupers);
+ operation,
+ requestId = me.getRequestId();
- if (!grouper) {
- grouper = {
- property : groupers,
- direction: direction
- };
- newGroupers = [grouper];
- } else if (direction === undefined) {
- grouper.toggle();
- } else {
- grouper.setDirection(direction);
- }
- }
-
- if (newGroupers && newGroupers.length) {
- newGroupers = me.decodeGroupers(newGroupers);
- me.groupers.clear();
- me.groupers.addAll(newGroupers);
- }
-
- if (me.remoteGroup) {
- me.load({
- scope: me,
- callback: me.fireGroupChange
- });
- } else {
- me.sort();
- me.fireEvent('groupchange', me, me.groupers);
+ options = options || {};
+
+ Ext.applyIf(options, {
+ action : 'read',
+ filters: me.filters.items,
+ sorters: me.sorters.items,
+ requestId: requestId
+ });
+ me.pendingRequests.push(requestId);
+
+ operation = Ext.create('Ext.data.Operation', options);
+
+ // HACK to implement loadMask support.
+ //if (operation.blocking) {
+ // me.fireEvent('beforeload', me, operation);
+ //}
+ if (me.fireEvent('beforeprefetch', me, operation) !== false) {
+ me.loading = true;
+ me.proxy.read(operation, me.onProxyPrefetch, me);
}
+
+ return me;
},
-
+
/**
- * Clear any groupers in the store
+ * Prefetches a page of data.
+ * @param {Number} page The page to prefetch
+ * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
+ * See {@link #load}
*/
- clearGrouping: function(){
- var me = this;
- // Clear any groupers we pushed on to the sorters
- me.groupers.each(function(grouper){
- me.sorters.remove(grouper);
- });
- me.groupers.clear();
- if (me.remoteGroup) {
- me.load({
- scope: me,
- callback: me.fireGroupChange
+ prefetchPage: function(page, options) {
+ var me = this,
+ pageSize = me.pageSize,
+ start = (page - 1) * me.pageSize,
+ end = start + pageSize;
+
+ // Currently not requesting this page and range isn't already satisified
+ if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
+ options = options || {};
+ me.pagesRequested.push(page);
+ Ext.applyIf(options, {
+ page : page,
+ start: start,
+ limit: pageSize,
+ callback: me.onWaitForGuarantee,
+ scope: me
});
- } else {
- me.sort();
- me.fireEvent('groupchange', me, me.groupers);
+
+ me.prefetch(options);
}
+
},
-
+
/**
- * Checks if the store is currently grouped
- * @return {Boolean} True if the store is grouped.
+ * Returns a unique requestId to track requests.
+ * @private
*/
- isGrouped: function() {
- return this.groupers.getCount() > 0;
+ getRequestId: function() {
+ this.requestSeed = this.requestSeed || 1;
+ return this.requestSeed++;
},
-
+
/**
- * Fires the groupchange event. Abstracted out so we can use it
- * as a callback
+ * Called after the configured proxy completes a prefetch operation.
* @private
+ * @param {Ext.data.Operation} operation The operation that completed
*/
- fireGroupChange: function(){
- this.fireEvent('groupchange', this, this.groupers);
- },
+ onProxyPrefetch: function(operation) {
+ var me = this,
+ resultSet = operation.getResultSet(),
+ records = operation.getRecords(),
- /**
- * Returns an object containing the result of applying grouping to the records in this store. See {@link #groupField},
- * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
-
-var myStore = new Ext.data.Store({
- groupField: 'color',
- groupDir : 'DESC'
-});
+ successful = operation.wasSuccessful();
-myStore.getGroups(); //returns:
-[
- {
- name: 'yellow',
- children: [
- //all records where the color field is 'yellow'
- ]
+ if (resultSet) {
+ me.totalCount = resultSet.total;
+ me.fireEvent('totalcountchange', me.totalCount);
+ }
+
+ if (successful) {
+ me.cacheRecords(records, operation);
+ }
+ Ext.Array.remove(me.pendingRequests, operation.requestId);
+ if (operation.page) {
+ Ext.Array.remove(me.pagesRequested, operation.page);
+ }
+
+ me.loading = false;
+ me.fireEvent('prefetch', me, records, successful, operation);
+
+ // HACK to support loadMask
+ if (operation.blocking) {
+ me.fireEvent('load', me, records, successful);
+ }
+
+ //this is a callback that would have been passed to the 'read' function and is optional
+ Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
},
- {
- name: 'red',
- children: [
- //all records where the color field is 'red'
- ]
- }
-]
-
- * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
- * @return {Array} The grouped data
+
+ /**
+ * Caches the records in the prefetch and stripes them with their server-side
+ * index.
+ * @private
+ * @param {Ext.data.Model[]} records The records to cache
+ * @param {Ext.data.Operation} The associated operation
*/
- getGroups: function(requestGroupString) {
- var records = this.data.items,
+ cacheRecords: function(records, operation) {
+ var me = this,
+ i = 0,
length = records.length,
- groups = [],
- pointers = {},
- record,
- groupStr,
- group,
- i;
-
- for (i = 0; i < length; i++) {
- record = records[i];
- groupStr = this.getGroupString(record);
- group = pointers[groupStr];
+ start = operation ? operation.start : 0;
- if (group === undefined) {
- group = {
- name: groupStr,
- children: []
- };
+ if (!Ext.isDefined(me.totalCount)) {
+ me.totalCount = records.length;
+ me.fireEvent('totalcountchange', me.totalCount);
+ }
- groups.push(group);
- pointers[groupStr] = group;
- }
+ for (; i < length; i++) {
+ // this is the true index, not the viewIndex
+ records[i].index = start + i;
+ }
- group.children.push(record);
+ me.prefetchData.addAll(records);
+ if (me.purgePageCount) {
+ me.purgeRecords();
}
- return requestGroupString ? pointers[requestGroupString] : groups;
+ },
+
+
+ /**
+ * Purge the least recently used records in the prefetch if the purgeCount
+ * has been exceeded.
+ */
+ purgeRecords: function() {
+ var me = this,
+ prefetchCount = me.prefetchData.getCount(),
+ purgeCount = me.purgePageCount * me.pageSize,
+ numRecordsToPurge = prefetchCount - purgeCount - 1,
+ i = 0;
+
+ for (; i <= numRecordsToPurge; i++) {
+ me.prefetchData.removeAt(0);
+ }
},
/**
+ * Determines if the range has already been satisfied in the prefetchData.
* @private
- * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
- * matching a certain group.
+ * @param {Number} start The start index
+ * @param {Number} end The end index in the range
*/
- getGroupsForGrouper: function(records, grouper) {
- var length = records.length,
- groups = [],
- oldValue,
- newValue,
- record,
- group,
- i;
-
- for (i = 0; i < length; i++) {
- record = records[i];
- newValue = grouper.getGroupString(record);
+ rangeSatisfied: function(start, end) {
+ var me = this,
+ i = start,
+ satisfied = true;
- if (newValue !== oldValue) {
- group = {
- name: newValue,
- grouper: grouper,
- records: []
- };
- groups.push(group);
+ for (; i < end; i++) {
+ if (!me.prefetchData.getByKey(i)) {
+ satisfied = false;
+ //
+ if (end - i > me.pageSize) {
+ Ext.Error.raise("A single page prefetch could never satisfy this request.");
+ }
+ //
+ break;
}
-
- group.records.push(record);
-
- oldValue = newValue;
}
+ return satisfied;
+ },
- return groups;
+ /**
+ * Determines the page from a record index
+ * @param {Number} index The record index
+ * @return {Number} The page the record belongs to
+ */
+ getPageFromRecordIndex: function(index) {
+ return Math.floor(index / this.pageSize) + 1;
},
/**
+ * Handles a guaranteed range being loaded
* @private
- * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
- * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
- * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
- * @param {Array} records The set or subset of records to group
- * @param {Number} grouperIndex The grouper index to retrieve
- * @return {Array} The grouped records
*/
- getGroupsForGrouperIndex: function(records, grouperIndex) {
+ onGuaranteedRange: function() {
var me = this,
- groupers = me.groupers,
- grouper = groupers.getAt(grouperIndex),
- groups = me.getGroupsForGrouper(records, grouper),
- length = groups.length,
- i;
+ totalCount = me.getTotalCount(),
+ start = me.requestStart,
+ end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
+ range = [],
+ record,
+ i = start;
- if (grouperIndex + 1 < groupers.length) {
- for (i = 0; i < length; i++) {
- groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
- }
- }
+ end = Math.max(0, end);
- for (i = 0; i < length; i++) {
- groups[i].depth = grouperIndex;
+ //
+ if (start > end) {
+ Ext.log({
+ level: 'warn',
+ msg: 'Start (' + start + ') was greater than end (' + end +
+ ') for the range of records requested (' + me.requestStart + '-' +
+ me.requestEnd + ')' + (this.storeId ? ' from store "' + this.storeId + '"' : '')
+ });
}
+ //
- return groups;
- },
+ if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
+ me.guaranteedStart = start;
+ me.guaranteedEnd = end;
- /**
- * @private
- * Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
- * this case grouping by genre and then author in a fictional books dataset):
-
-[
- {
- name: 'Fantasy',
- depth: 0,
- records: [
- //book1, book2, book3, book4
- ],
- children: [
- {
- name: 'Rowling',
- depth: 1,
- records: [
- //book1, book2
- ]
- },
- {
- name: 'Tolkein',
- depth: 1,
- records: [
- //book3, book4
- ]
+ for (; i <= end; i++) {
+ record = me.prefetchData.getByKey(i);
+ //
+// if (!record) {
+// Ext.log('Record with key "' + i + '" was not found and store said it was guaranteed');
+// }
+ //
+ if (record) {
+ range.push(record);
+ }
+ }
+ me.fireEvent('guaranteedrange', range, start, end);
+ if (me.cb) {
+ me.cb.call(me.scope || me, range);
}
- ]
- }
-]
-
- * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
- * function correctly so this should only be set to false if the Store is known to already be sorted correctly
- * (defaults to true)
- * @return {Array} The group data
- */
- getGroupData: function(sort) {
- var me = this;
- if (sort !== false) {
- me.sort();
}
- return me.getGroupsForGrouperIndex(me.data.items, 0);
+ me.unmask();
+ },
+
+ // hack to support loadmask
+ mask: function() {
+ this.masked = true;
+ this.fireEvent('beforeload');
+ },
+
+ // hack to support loadmask
+ unmask: function() {
+ if (this.masked) {
+ this.fireEvent('load');
+ }
},
/**
- * Returns the string to group on for a given model instance. The default implementation of this method returns
- * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
- * group by the first letter of a model's 'name' field, use the following code:
-
-new Ext.data.Store({
- groupDir: 'ASC',
- getGroupString: function(instance) {
- return instance.get('name')[0];
- }
-});
-
- * @param {Ext.data.Model} instance The model instance
- * @return {String} The string to compare when forming groups
+ * Returns the number of pending requests out.
*/
- getGroupString: function(instance) {
- var group = this.groupers.first();
- if (group) {
- return instance.get(group.property);
+ hasPendingRequests: function() {
+ return this.pendingRequests.length;
+ },
+
+
+ // wait until all requests finish, until guaranteeing the range.
+ onWaitForGuarantee: function() {
+ if (!this.hasPendingRequests()) {
+ this.onGuaranteedRange();
}
- return '';
},
+
/**
- * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
- * See also {@link #add}
.
- * @param {Number} index The start index at which to insert the passed Records.
- * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
+ * Guarantee a specific range, this will load the store with a range (that
+ * must be the pageSize or smaller) and take care of any loading that may
+ * be necessary.
*/
- insert: function(index, records) {
+ guaranteeRange: function(start, end, cb, scope) {
+ //
+ if (start && end) {
+ if (end - start > this.pageSize) {
+ Ext.Error.raise({
+ start: start,
+ end: end,
+ pageSize: this.pageSize,
+ msg: "Requested a bigger range than the specified pageSize"
+ });
+ }
+ }
+ //
+
+ end = (end > this.totalCount) ? this.totalCount - 1 : end;
+
var me = this,
- sync = false,
- i,
- record,
- len;
+ i = start,
+ prefetchData = me.prefetchData,
+ range = [],
+ startLoaded = !!prefetchData.getByKey(start),
+ endLoaded = !!prefetchData.getByKey(end),
+ startPage = me.getPageFromRecordIndex(start),
+ endPage = me.getPageFromRecordIndex(end);
- records = [].concat(records);
- for (i = 0, len = records.length; i < len; i++) {
- record = me.createModel(records[i]);
- record.set(me.modelDefaults);
- // reassign the model in the array in case it wasn't created yet
- records[i] = record;
-
- me.data.insert(index + i, record);
- record.join(me);
+ me.cb = cb;
+ me.scope = scope;
- sync = sync || record.phantom === true;
+ me.requestStart = start;
+ me.requestEnd = end;
+ // neither beginning or end are loaded
+ if (!startLoaded || !endLoaded) {
+ // same page, lets load it
+ if (startPage === endPage) {
+ me.mask();
+ me.prefetchPage(startPage, {
+ //blocking: true,
+ callback: me.onWaitForGuarantee,
+ scope: me
+ });
+ // need to load two pages
+ } else {
+ me.mask();
+ me.prefetchPage(startPage, {
+ //blocking: true,
+ callback: me.onWaitForGuarantee,
+ scope: me
+ });
+ me.prefetchPage(endPage, {
+ //blocking: true,
+ callback: me.onWaitForGuarantee,
+ scope: me
+ });
+ }
+ // Request was already satisfied via the prefetch
+ } else {
+ me.onGuaranteedRange();
}
+ },
- if (me.snapshot) {
- me.snapshot.addAll(records);
+ // because prefetchData is stored by index
+ // this invalidates all of the prefetchedData
+ sort: function() {
+ var me = this,
+ prefetchData = me.prefetchData,
+ sorters,
+ start,
+ end,
+ range;
+
+ if (me.buffered) {
+ if (me.remoteSort) {
+ prefetchData.clear();
+ me.callParent(arguments);
+ } else {
+ sorters = me.getSorters();
+ start = me.guaranteedStart;
+ end = me.guaranteedEnd;
+
+ if (sorters.length) {
+ prefetchData.sort(sorters);
+ range = prefetchData.getRange();
+ prefetchData.clear();
+ me.cacheRecords(range);
+ delete me.guaranteedStart;
+ delete me.guaranteedEnd;
+ me.guaranteeRange(start, end);
+ }
+ me.callParent(arguments);
+ }
+ } else {
+ me.callParent(arguments);
}
+ },
- me.fireEvent('add', me, records, index);
- me.fireEvent('datachanged', me);
- if (me.autoSync && sync) {
- me.sync();
+ // overriden to provide striping of the indexes as sorting occurs.
+ // this cannot be done inside of sort because datachanged has already
+ // fired and will trigger a repaint of the bound view.
+ doSort: function(sorterFn) {
+ var me = this;
+ if (me.remoteSort) {
+ //the load function will pick up the new sorters and request the sorted data from the proxy
+ me.load();
+ } else {
+ me.data.sortBy(sorterFn);
+ if (!me.buffered) {
+ var range = me.getRange(),
+ ln = range.length,
+ i = 0;
+ for (; i < ln; i++) {
+ range[i].index = i;
+ }
+ }
+ me.fireEvent('datachanged', me);
}
},
/**
- * Adds Model instances to the Store by instantiating them based on a JavaScript object. When adding already-
- * instantiated Models, use {@link #insert} instead. The instances will be added at the end of the existing collection.
- * This method accepts either a single argument array of Model instances or any number of model instance arguments.
- * Sample usage:
- *
-
-myStore.add({some: 'data'}, {some: 'other data'});
-
- *
- * @param {Object} data The data for each model
- * @return {Array} The array of newly created model instances
+ * Finds the index of the first matching Record in this store by a specific field value.
+ * @param {String} fieldName The name of the Record field to test.
+ * @param {String/RegExp} value Either a string that the field value
+ * should begin with, or a RegExp to test against the field.
+ * @param {Number} startIndex (optional) The index to start searching at
+ * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
+ * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
+ * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
+ * @return {Number} The matched index or -1
*/
- add: function(records) {
- //accept both a single-argument array of records, or any number of record arguments
- if (!Ext.isArray(records)) {
- records = Array.prototype.slice.apply(arguments);
- }
+ find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
+ var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
+ return fn ? this.data.findIndexBy(fn, null, start) : -1;
+ },
+ /**
+ * Finds the first matching Record in this store by a specific field value.
+ * @param {String} fieldName The name of the Record field to test.
+ * @param {String/RegExp} value Either a string that the field value
+ * should begin with, or a RegExp to test against the field.
+ * @param {Number} startIndex (optional) The index to start searching at
+ * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
+ * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
+ * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
+ * @return {Ext.data.Model} The matched record or null
+ */
+ findRecord: function() {
var me = this,
- i = 0,
- length = records.length,
- record;
-
- for (; i < length; i++) {
- record = me.createModel(records[i]);
- // reassign the model in the array in case it wasn't created yet
- records[i] = record;
- }
-
- me.insert(me.data.length, records);
-
- return records;
+ index = me.find.apply(me, arguments);
+ return index !== -1 ? me.getAt(index) : null;
},
/**
- * Converts a literal to a model, if it's not a model already
* @private
- * @param record {Ext.data.Model/Object} The record to create
- * @return {Ext.data.Model}
+ * Returns a filter function used to test a the given property's value. Defers most of the work to
+ * Ext.util.MixedCollection's createValueMatcher function
+ * @param {String} property The property to create the filter function for
+ * @param {String/RegExp} value The string/regex to compare the property value to
+ * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
+ * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
+ * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
+ * Ignored if anyMatch is true.
*/
- createModel: function(record) {
- if (!record.isModel) {
- record = Ext.ModelManager.create(record, this.model);
+ createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
+ if (Ext.isEmpty(value)) {
+ return false;
}
-
- return record;
+ value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
+ return function(r) {
+ return value.test(r.data[property]);
+ };
},
/**
- * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
- * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
- * Returning false aborts and exits the iteration.
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed.
- * Defaults to the current {@link Ext.data.Model Record} in the iteration.
+ * Finds the index of the first matching Record in this store by a specific field value.
+ * @param {String} fieldName The name of the Record field to test.
+ * @param {Object} value The value to match the field against.
+ * @param {Number} startIndex (optional) The index to start searching at
+ * @return {Number} The matched index or -1
*/
- each: function(fn, scope) {
- this.data.each(fn, scope);
+ findExact: function(property, value, start) {
+ return this.data.findIndexBy(function(rec) {
+ return rec.get(property) == value;
+ },
+ this, start);
},
/**
- * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
- * 'datachanged' event after removal.
- * @param {Ext.data.Model/Array} records The Ext.data.Model instance or array of instances to remove
+ * Find the index of the first matching Record in this Store by a function.
+ * If the function returns true it is considered a match.
+ * @param {Function} fn The function to be called. It will be passed the following parameters:
+ * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to this Store.
+ * @param {Number} startIndex (optional) The index to start searching at
+ * @return {Number} The matched index or -1
*/
- remove: function(records, /* private */ isMove) {
- if (!Ext.isArray(records)) {
- records = [records];
- }
+ findBy: function(fn, scope, start) {
+ return this.data.findIndexBy(fn, scope, start);
+ },
- /*
- * Pass the isMove parameter if we know we're going to be re-inserting this record
- */
- isMove = isMove === true;
+ /**
+ * Collects unique values for a particular dataIndex from this store.
+ * @param {String} dataIndex The property to collect
+ * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
+ * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
+ * @return {Object[]} An array of the unique values
+ **/
+ collect: function(dataIndex, allowNull, bypassFilter) {
var me = this,
- sync = false,
- i = 0,
- length = records.length,
- isPhantom,
- index,
- record;
+ data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
- for (; i < length; i++) {
- record = records[i];
- index = me.data.indexOf(record);
-
- if (me.snapshot) {
- me.snapshot.remove(record);
- }
-
- if (index > -1) {
- isPhantom = record.phantom === true;
- if (!isMove && !isPhantom) {
- // don't push phantom records onto removed
- me.removed.push(record);
- }
+ return data.collect(dataIndex, 'data', allowNull);
+ },
- record.unjoin(me);
- me.data.remove(record);
- sync = sync || !isPhantom;
+ /**
+ * Gets the number of cached records.
+ * If using paging, this may not be the total size of the dataset. If the data object
+ * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
+ * the dataset size. Note : see the Important note in {@link #load}.
+ * @return {Number} The number of Records in the Store's cache.
+ */
+ getCount: function() {
+ return this.data.length || 0;
+ },
- me.fireEvent('remove', me, record, index);
- }
- }
+ /**
+ * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
+ * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
+ * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
+ * could be loaded into the Store if the Store contained all data
+ * @return {Number} The total number of Model instances available via the Proxy
+ */
+ getTotalCount: function() {
+ return this.totalCount;
+ },
- me.fireEvent('datachanged', me);
- if (!isMove && me.autoSync && sync) {
- me.sync();
- }
+ /**
+ * Get the Record at the specified index.
+ * @param {Number} index The index of the Record to find.
+ * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
+ */
+ getAt: function(index) {
+ return this.data.getAt(index);
},
/**
- * Removes the model instance at the given index
- * @param {Number} index The record index
+ * Returns a range of Records between specified indices.
+ * @param {Number} [startIndex=0] The starting index
+ * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
+ * @return {Ext.data.Model[]} An array of Records
*/
- removeAt: function(index) {
- var record = this.getAt(index);
+ getRange: function(start, end) {
+ return this.data.getRange(start, end);
+ },
- if (record) {
- this.remove(record);
- }
+ /**
+ * Get the Record with the specified id.
+ * @param {String} id The id of the Record to find.
+ * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
+ */
+ getById: function(id) {
+ return (this.snapshot || this.data).findBy(function(record) {
+ return record.getId() === id;
+ });
+ },
+
+ /**
+ * Get the index within the cache of the passed Record.
+ * @param {Ext.data.Model} record The Ext.data.Model object to find.
+ * @return {Number} The index of the passed Record. Returns -1 if not found.
+ */
+ indexOf: function(record) {
+ return this.data.indexOf(record);
},
+
/**
- * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
- * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
- * instances into the Store and calling an optional callback if required. Example usage:
- *
-
-store.load({
- scope : this,
- callback: function(records, operation, success) {
- //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
- console.log(records);
- }
-});
-
- *
- * If the callback scope does not need to be set, a function can simply be passed:
- *
-
-store.load(function(records, operation, success) {
- console.log('loaded records');
-});
-
- *
- * @param {Object/Function} options Optional config object, passed into the Ext.data.Operation object before loading.
+ * Get the index within the entire dataset. From 0 to the totalCount.
+ * @param {Ext.data.Model} record The Ext.data.Model object to find.
+ * @return {Number} The index of the passed Record. Returns -1 if not found.
*/
- load: function(options) {
- var me = this;
-
- options = options || {};
-
- if (Ext.isFunction(options)) {
- options = {
- callback: options
- };
+ indexOfTotal: function(record) {
+ var index = record.index;
+ if (index || index === 0) {
+ return index;
}
+ return this.indexOf(record);
+ },
- Ext.applyIf(options, {
- groupers: me.groupers.items,
- page: me.currentPage,
- start: (me.currentPage - 1) * me.pageSize,
- limit: me.pageSize,
- addRecords: false
- });
-
- return me.callParent([options]);
+ /**
+ * Get the index within the cache of the Record with the passed id.
+ * @param {String} id The id of the Record to find.
+ * @return {Number} The index of the Record. Returns -1 if not found.
+ */
+ indexOfId: function(id) {
+ return this.indexOf(this.getById(id));
},
/**
- * @private
- * Called internally when a Proxy has completed a load request
+ * Remove all items from the store.
+ * @param {Boolean} silent Prevent the `clear` event from being fired.
*/
- onProxyLoad: function(operation) {
- var me = this,
- resultSet = operation.getResultSet(),
- records = operation.getRecords(),
- successful = operation.wasSuccessful();
+ removeAll: function(silent) {
+ var me = this;
- if (resultSet) {
- me.totalCount = resultSet.total;
+ me.clearData();
+ if (me.snapshot) {
+ me.snapshot.clear();
}
-
- if (successful) {
- me.loadRecords(records, operation);
+ if (silent !== true) {
+ me.fireEvent('clear', me);
}
+ },
- me.loading = false;
- me.fireEvent('load', me, records, successful);
-
- //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
- //People are definitely using this so can't deprecate safely until 2.x
- me.fireEvent('read', me, records, operation.wasSuccessful());
+ /*
+ * Aggregation methods
+ */
- //this is a callback that would have been passed to the 'read' function and is optional
- Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
- },
-
/**
- * Create any new records when a write is returned from the server.
- * @private
- * @param {Array} records The array of new records
- * @param {Ext.data.Operation} operation The operation that just completed
- * @param {Boolean} success True if the operation was successful
+ * Convenience function for getting the first model instance in the store
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the first record being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
*/
- onCreateRecords: function(records, operation, success) {
- if (success) {
- var i = 0,
- data = this.data,
- snapshot = this.snapshot,
- length = records.length,
- originalRecords = operation.records,
- record,
- original,
- index;
+ first: function(grouped) {
+ var me = this;
- /*
- * Loop over each record returned from the server. Assume they are
- * returned in order of how they were sent. If we find a matching
- * record, replace it with the newly created one.
- */
- for (; i < length; ++i) {
- record = records[i];
- original = originalRecords[i];
- if (original) {
- index = data.indexOf(original);
- if (index > -1) {
- data.removeAt(index);
- data.insert(index, record);
- }
- if (snapshot) {
- index = snapshot.indexOf(original);
- if (index > -1) {
- snapshot.removeAt(index);
- snapshot.insert(index, record);
- }
- }
- record.phantom = false;
- record.join(this);
- }
- }
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(function(records) {
+ return records.length ? records[0] : undefined;
+ }, me, true);
+ } else {
+ return me.data.first();
}
},
/**
- * Update any records when a write is returned from the server.
- * @private
- * @param {Array} records The array of updated records
- * @param {Ext.data.Operation} operation The operation that just completed
- * @param {Boolean} success True if the operation was successful
+ * Convenience function for getting the last model instance in the store
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the last record being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
*/
- onUpdateRecords: function(records, operation, success){
- if (success) {
- var i = 0,
- length = records.length,
- data = this.data,
- snapshot = this.snapshot,
- record;
+ last: function(grouped) {
+ var me = this;
- for (; i < length; ++i) {
- record = records[i];
- data.replace(record);
- if (snapshot) {
- snapshot.replace(record);
- }
- record.join(this);
- }
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(function(records) {
+ var len = records.length;
+ return len ? records[len - 1] : undefined;
+ }, me, true);
+ } else {
+ return me.data.last();
}
},
/**
- * Remove any records when a write is returned from the server.
- * @private
- * @param {Array} records The array of removed records
- * @param {Ext.data.Operation} operation The operation that just completed
- * @param {Boolean} success True if the operation was successful
+ * Sums the value of property for each {@link Ext.data.Model record} between start
+ * and end and returns the result.
+ * @param {String} field A field in each record
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the sum for that group being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Number} The sum
*/
- onDestroyRecords: function(records, operation, success){
- if (success) {
- var me = this,
- i = 0,
- length = records.length,
- data = me.data,
- snapshot = me.snapshot,
- record;
+ sum: function(field, grouped) {
+ var me = this;
- for (; i < length; ++i) {
- record = records[i];
- record.unjoin(me);
- data.remove(record);
- if (snapshot) {
- snapshot.remove(record);
- }
- }
- me.removed = [];
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(me.getSum, me, true, [field]);
+ } else {
+ return me.getSum(me.data.items, field);
}
},
- //inherit docs
- getNewRecords: function() {
- return this.data.filterBy(this.filterNew).items;
- },
+ // @private, see sum
+ getSum: function(records, field) {
+ var total = 0,
+ i = 0,
+ len = records.length;
- //inherit docs
- getUpdatedRecords: function() {
- return this.data.filterBy(this.filterUpdated).items;
+ for (; i < len; ++i) {
+ total += records[i].get(field);
+ }
+
+ return total;
},
/**
- * Filters the loaded set of records by a given set of filters.
- * @param {Mixed} filters The set of filters to apply to the data. These are stored internally on the store,
- * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
- * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
- * pass in a property string
- * @param {String} value Optional value to filter by (only if using a property string as the first argument)
+ * Gets the count of items in the store.
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the count for each group being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Number} the count
*/
- filter: function(filters, value) {
- if (Ext.isString(filters)) {
- filters = {
- property: filters,
- value: value
- };
- }
-
- var me = this,
- decoded = me.decodeFilters(filters),
- i = 0,
- doLocalSort = me.sortOnFilter && !me.remoteSort,
- length = decoded.length;
-
- for (; i < length; i++) {
- me.filters.replace(decoded[i]);
- }
+ count: function(grouped) {
+ var me = this;
- if (me.remoteFilter) {
- //the load function will pick up the new filters and request the filtered data from the proxy
- me.load();
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(function(records) {
+ return records.length;
+ }, me, true);
} else {
- /**
- * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
- * records when a filter is removed or changed
- * @property snapshot
- * @type Ext.util.MixedCollection
- */
- if (me.filters.getCount()) {
- me.snapshot = me.snapshot || me.data.clone();
- me.data = me.data.filter(me.filters.items);
-
- if (doLocalSort) {
- me.sort();
- }
- // fire datachanged event if it hasn't already been fired by doSort
- if (!doLocalSort || me.sorters.length < 1) {
- me.fireEvent('datachanged', me);
- }
- }
+ return me.getCount();
}
},
/**
- * Revert to a view of the Record cache with no filtering applied.
- * @param {Boolean} suppressEvent If true the filter is cleared silently without firing the
- * {@link #datachanged} event.
+ * Gets the minimum value in the store.
+ * @param {String} field The field in each record
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the minimum in the group being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Object} The minimum value, if no items exist, undefined.
*/
- clearFilter: function(suppressEvent) {
+ min: function(field, grouped) {
var me = this;
- me.filters.clear();
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(me.getMin, me, true, [field]);
+ } else {
+ return me.getMin(me.data.items, field);
+ }
+ },
- if (me.remoteFilter) {
- me.load();
- } else if (me.isFiltered()) {
- me.data = me.snapshot.clone();
- delete me.snapshot;
+ // @private, see min
+ getMin: function(records, field){
+ var i = 1,
+ len = records.length,
+ value, min;
- if (suppressEvent !== true) {
- me.fireEvent('datachanged', me);
- }
+ if (len > 0) {
+ min = records[0].get(field);
}
- },
- /**
- * Returns true if this store is currently filtered
- * @return {Boolean}
- */
- isFiltered: function() {
- var snapshot = this.snapshot;
- return !! snapshot && snapshot !== this.data;
+ for (; i < len; ++i) {
+ value = records[i].get(field);
+ if (value < min) {
+ min = value;
+ }
+ }
+ return min;
},
/**
- * Filter by a function. The specified function will be called for each
- * Record in this Store. If the function returns true the Record is included,
- * otherwise it is filtered out.
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to this Store.
+ * Gets the maximum value in the store.
+ * @param {String} field The field in each record
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the maximum in the group being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Object} The maximum value, if no items exist, undefined.
*/
- filterBy: function(fn, scope) {
+ max: function(field, grouped) {
var me = this;
- me.snapshot = me.snapshot || me.data.clone();
- me.data = me.queryBy(fn, scope || me);
- me.fireEvent('datachanged', me);
- },
-
- /**
- * Query the cached records in this Store using a filtering function. The specified function
- * will be called with each record in this Store. If the function returns true the record is
- * included in the results.
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to this Store.
- * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
- **/
- queryBy: function(fn, scope) {
- var me = this,
- data = me.snapshot || me.data;
- return data.filterBy(fn, scope || me);
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(me.getMax, me, true, [field]);
+ } else {
+ return me.getMax(me.data.items, field);
+ }
},
- /**
- * Loads an array of data straight into the Store
- * @param {Array} data Array of data to load. Any non-model instances will be cast into model instances first
- * @param {Boolean} append True to add the records to the existing records in the store, false to remove the old ones first
- */
- loadData: function(data, append) {
- var model = this.model,
- length = data.length,
- i,
- record;
+ // @private, see max
+ getMax: function(records, field) {
+ var i = 1,
+ len = records.length,
+ value,
+ max;
- //make sure each data element is an Ext.data.Model instance
- for (i = 0; i < length; i++) {
- record = data[i];
+ if (len > 0) {
+ max = records[0].get(field);
+ }
- if (! (record instanceof Ext.data.Model)) {
- data[i] = Ext.ModelManager.create(record, model);
+ for (; i < len; ++i) {
+ value = records[i].get(field);
+ if (value > max) {
+ max = value;
}
}
-
- this.loadRecords(data, {addRecords: append});
+ return max;
},
/**
- * Loads an array of {@Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
- * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
- * @param {Array} records The array of records to load
- * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
+ * Gets the average value in the store.
+ * @param {String} field The field in each record
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the group average being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @return {Object} The average value, if no items exist, 0.
*/
- loadRecords: function(records, options) {
- var me = this,
- i = 0,
- length = records.length;
-
- options = options || {};
-
-
- if (!options.addRecords) {
- delete me.snapshot;
- me.clearData();
+ average: function(field, grouped) {
+ var me = this;
+ if (grouped && me.isGrouped()) {
+ return me.aggregate(me.getAverage, me, true, [field]);
+ } else {
+ return me.getAverage(me.data.items, field);
}
+ },
- me.data.addAll(records);
-
- //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
- for (; i < length; i++) {
- if (options.start !== undefined) {
- records[i].index = options.start + i;
+ // @private, see average
+ getAverage: function(records, field) {
+ var i = 0,
+ len = records.length,
+ sum = 0;
+ if (records.length > 0) {
+ for (; i < len; ++i) {
+ sum += records[i].get(field);
}
- records[i].join(me);
+ return sum / len;
}
+ return 0;
+ },
- /*
- * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
- * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
- * datachanged event is fired by the call to this.add, above.
- */
- me.suspendEvents();
+ /**
+ * Runs the aggregate function for all the records in the store.
+ * @param {Function} fn The function to execute. The function is called with a single parameter,
+ * an array of records for that group.
+ * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
+ * @param {Boolean} grouped (Optional) True to perform the operation for each group
+ * in the store. The value returned will be an object literal with the key being the group
+ * name and the group average being the value. The grouped parameter is only honored if
+ * the store has a groupField.
+ * @param {Array} args (optional) Any arguments to append to the function call
+ * @return {Object} An object literal with the group names and their appropriate values.
+ */
+ aggregate: function(fn, scope, grouped, args) {
+ args = args || [];
+ if (grouped && this.isGrouped()) {
+ var groups = this.getGroups(),
+ i = 0,
+ len = groups.length,
+ out = {},
+ group;
- if (me.filterOnLoad && !me.remoteFilter) {
- me.filter();
+ for (; i < len; ++i) {
+ group = groups[i];
+ out[group.name] = fn.apply(scope || this, [group.children].concat(args));
+ }
+ return out;
+ } else {
+ return fn.apply(scope || this, [this.data.items].concat(args));
}
+ }
+}, function() {
+ // A dummy empty store with a fieldless Model defined in it.
+ // Just for binding to Views which are instantiated with no Store defined.
+ // They will be able to run and render fine, and be bound to a generated Store later.
+ Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
+});
- if (me.sortOnLoad && !me.remoteSort) {
- me.sort();
- }
+/**
+ * @author Ed Spencer
+ * @class Ext.data.JsonStore
+ * @extends Ext.data.Store
+ * @ignore
+ *
+ * Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
+ * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.
+ *
+ * A store configuration would be something like:
+ *
+
+var store = new Ext.data.JsonStore({
+ // store configs
+ autoDestroy: true,
+ storeId: 'myStore',
- me.resumeEvents();
- me.fireEvent('datachanged', me, records);
+ proxy: {
+ type: 'ajax',
+ url: 'get-images.php',
+ reader: {
+ type: 'json',
+ root: 'images',
+ idProperty: 'name'
+ }
},
- // PAGING METHODS
+ //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
+ fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
+});
+
+ *
+ * This store is configured to consume a returned object of the form:
+{
+ images: [
+ {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
+ {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
+ ]
+}
+
+ *
+ * An object literal of this form could also be used as the {@link #data} config option.
+ *
+ * @xtype jsonstore
+ */
+Ext.define('Ext.data.JsonStore', {
+ extend: 'Ext.data.Store',
+ alias: 'store.json',
+
/**
- * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
- * load operation, passing in calculated 'start' and 'limit' params
- * @param {Number} page The number of the page to load
+ * @cfg {Ext.data.DataReader} reader @hide
*/
- loadPage: function(page) {
- var me = this;
-
- me.currentPage = page;
+ constructor: function(config) {
+ config = config || {};
- me.read({
- page: page,
- start: (page - 1) * me.pageSize,
- limit: me.pageSize,
- addRecords: !me.clearOnPageLoad
+ Ext.applyIf(config, {
+ proxy: {
+ type : 'ajax',
+ reader: 'json',
+ writer: 'json'
+ }
});
- },
- /**
- * Loads the next 'page' in the current data set
- */
- nextPage: function() {
- this.loadPage(this.currentPage + 1);
- },
+ this.callParent([config]);
+ }
+});
- /**
- * Loads the previous 'page' in the current data set
- */
- previousPage: function() {
- this.loadPage(this.currentPage - 1);
- },
+/**
+ * @class Ext.chart.axis.Time
+ * @extends Ext.chart.axis.Numeric
+ *
+ * A type of axis whose units are measured in time values. Use this axis
+ * for listing dates that you will want to group or dynamically change.
+ * If you just want to display dates as categories then use the
+ * Category class for axis instead.
+ *
+ * For example:
+ *
+ * axes: [{
+ * type: 'Time',
+ * position: 'bottom',
+ * fields: 'date',
+ * title: 'Day',
+ * dateFormat: 'M d',
+ *
+ * constrain: true,
+ * fromDate: new Date('1/1/11'),
+ * toDate: new Date('1/7/11')
+ * }]
+ *
+ * In this example we're creating a time axis that has as title *Day*.
+ * The field the axis is bound to is `date`.
+ * The date format to use to display the text for the axis labels is `M d`
+ * which is a three letter month abbreviation followed by the day number.
+ * The time axis will show values for dates between `fromDate` and `toDate`.
+ * Since `constrain` is set to true all other values for other dates not between
+ * the fromDate and toDate will not be displayed.
+ *
+ */
+Ext.define('Ext.chart.axis.Time', {
- // private
- clearData: function() {
- this.data.each(function(record) {
- record.unjoin();
- });
+ /* Begin Definitions */
- this.data.clear();
- },
-
- // Buffering
- /**
- * Prefetches data the Store using its configured {@link #proxy}.
- * @param {Object} options Optional config object, passed into the Ext.data.Operation object before loading.
- * See {@link #load}
- */
- prefetch: function(options) {
- var me = this,
- operation,
- requestId = me.getRequestId();
+ extend: 'Ext.chart.axis.Numeric',
- options = options || {};
+ alternateClassName: 'Ext.chart.TimeAxis',
- Ext.applyIf(options, {
- action : 'read',
- filters: me.filters.items,
- sorters: me.sorters.items,
- requestId: requestId
- });
- me.pendingRequests.push(requestId);
+ alias: 'axis.time',
- operation = Ext.create('Ext.data.Operation', options);
+ requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
+
+ /* End Definitions */
- // HACK to implement loadMask support.
- //if (operation.blocking) {
- // me.fireEvent('beforeload', me, operation);
- //}
- if (me.fireEvent('beforeprefetch', me, operation) !== false) {
- me.loading = true;
- me.proxy.read(operation, me.onProxyPrefetch, me);
- }
-
- return me;
- },
-
/**
- * Prefetches a page of data.
- * @param {Number} page The page to prefetch
- * @param {Object} options Optional config object, passed into the Ext.data.Operation object before loading.
- * See {@link #load}
- * @param
+ * @cfg {String/Boolean} dateFormat
+ * Indicates the format the date will be rendered on.
+ * For example: 'M d' will render the dates as 'Jan 30', etc.
+ * For a list of possible format strings see {@link Ext.Date Date}
*/
- prefetchPage: function(page, options) {
- var me = this,
- pageSize = me.pageSize,
- start = (page - 1) * me.pageSize,
- end = start + pageSize;
-
- // Currently not requesting this page and range isn't already satisified
- if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
- options = options || {};
- me.pagesRequested.push(page);
- Ext.applyIf(options, {
- page : page,
- start: start,
- limit: pageSize,
- callback: me.onWaitForGuarantee,
- scope: me
- });
-
- me.prefetch(options);
- }
-
- },
-
+ dateFormat: false,
+
/**
- * Returns a unique requestId to track requests.
- * @private
+ * @cfg {Date} fromDate The starting date for the time axis.
*/
- getRequestId: function() {
- this.requestSeed = this.requestSeed || 1;
- return this.requestSeed++;
- },
-
+ fromDate: false,
+
/**
- * Handles a success pre-fetch
- * @private
- * @param {Ext.data.Operation} operation The operation that completed
+ * @cfg {Date} toDate The ending date for the time axis.
*/
- onProxyPrefetch: function(operation) {
- var me = this,
- resultSet = operation.getResultSet(),
- records = operation.getRecords(),
-
- successful = operation.wasSuccessful();
-
- if (resultSet) {
- me.totalCount = resultSet.total;
- me.fireEvent('totalcountchange', me.totalCount);
- }
-
- if (successful) {
- me.cacheRecords(records, operation);
- }
- Ext.Array.remove(me.pendingRequests, operation.requestId);
- if (operation.page) {
- Ext.Array.remove(me.pagesRequested, operation.page);
- }
-
- me.loading = false;
- me.fireEvent('prefetch', me, records, successful, operation);
-
- // HACK to support loadMask
- if (operation.blocking) {
- me.fireEvent('load', me, records, successful);
- }
+ toDate: false,
- //this is a callback that would have been passed to the 'read' function and is optional
- Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
- },
-
/**
- * Caches the records in the prefetch and stripes them with their server-side
- * index.
- * @private
- * @param {Array} records The records to cache
- * @param {Ext.data.Operation} The associated operation
+ * @cfg {Array/Boolean} step
+ * An array with two components: The first is the unit of the step (day, month, year, etc).
+ * The second one is the number of units for the step (1, 2, etc.).
+ * Defaults to `[Ext.Date.DAY, 1]`.
*/
- cacheRecords: function(records, operation) {
- var me = this,
- i = 0,
- length = records.length,
- start = operation ? operation.start : 0;
-
- if (!Ext.isDefined(me.totalCount)) {
- me.totalCount = records.length;
- me.fireEvent('totalcountchange', me.totalCount);
- }
-
- for (; i < length; i++) {
- // this is the true index, not the viewIndex
- records[i].index = start + i;
- }
-
- me.prefetchData.addAll(records);
- if (me.purgePageCount) {
- me.purgeRecords();
- }
-
- },
-
+ step: [Ext.Date.DAY, 1],
/**
- * Purge the least recently used records in the prefetch if the purgeCount
- * has been exceeded.
+ * @cfg {Boolean} constrain
+ * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
+ * If false, the time axis will adapt to the new values by adding/removing steps.
*/
- purgeRecords: function() {
- var me = this,
- prefetchCount = me.prefetchData.getCount(),
- purgeCount = me.purgePageCount * me.pageSize,
- numRecordsToPurge = prefetchCount - purgeCount - 1,
- i = 0;
+ constrain: false,
- for (; i <= numRecordsToPurge; i++) {
- me.prefetchData.removeAt(0);
- }
- },
+ // Avoid roundtoDecimal call in Numeric Axis's constructor
+ roundToDecimal: false,
- /**
- * Determines if the range has already been satisfied in the prefetchData.
- * @private
- * @param {Number} start The start index
- * @param {Number} end The end index in the range
- */
- rangeSatisfied: function(start, end) {
- var me = this,
- i = start,
- satisfied = true;
-
- for (; i < end; i++) {
- if (!me.prefetchData.getByKey(i)) {
- satisfied = false;
- //
- if (end - i > me.pageSize) {
- Ext.Error.raise("A single page prefetch could never satisfy this request.");
- }
- //
- break;
+ constructor: function (config) {
+ var me = this, label, f, df;
+ me.callParent([config]);
+ label = me.label || {};
+ df = this.dateFormat;
+ if (df) {
+ if (label.renderer) {
+ f = label.renderer;
+ label.renderer = function(v) {
+ v = f(v);
+ return Ext.Date.format(new Date(f(v)), df);
+ };
+ } else {
+ label.renderer = function(v) {
+ return Ext.Date.format(new Date(v >> 0), df);
+ };
}
}
- return satisfied;
- },
-
- /**
- * Determines the page from a record index
- * @param {Number} index The record index
- * @return {Number} The page the record belongs to
- */
- getPageFromRecordIndex: function(index) {
- return Math.floor(index / this.pageSize) + 1;
},
-
- /**
- * Handles a guaranteed range being loaded
- * @private
- */
- onGuaranteedRange: function() {
+
+ doConstrain: function () {
var me = this,
- totalCount = me.getTotalCount(),
- start = me.requestStart,
- end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
- range = [],
- record,
- i = start;
-
- //
- if (start > end) {
- Ext.Error.raise("Start (" + start + ") was greater than end (" + end + ")");
+ store = me.chart.store,
+ data = [],
+ series = me.chart.series.items,
+ math = Math,
+ mmax = math.max,
+ mmin = math.min,
+ fields = me.fields,
+ ln = fields.length,
+ range = me.getRange(),
+ min = range.min, max = range.max, i, l, excludes = [],
+ value, values, rec, data = [];
+ for (i = 0, l = series.length; i < l; i++) {
+ excludes[i] = series[i].__excludes;
}
- //
-
- if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
- me.guaranteedStart = start;
- me.guaranteedEnd = end;
-
- for (; i <= end; i++) {
- record = me.prefetchData.getByKey(i);
- //
- if (!record) {
- Ext.Error.raise("Record was not found and store said it was guaranteed");
+ store.each(function(record) {
+ for (i = 0; i < ln; i++) {
+ if (excludes[i]) {
+ continue;
}
- //
- range.push(record);
- }
- me.fireEvent('guaranteedrange', range, start, end);
- if (me.cb) {
- me.cb.call(me.scope || me, range);
+ value = record.get(fields[i]);
+ if (+value < +min) return;
+ if (+value > +max) return;
}
- }
-
- me.unmask();
- },
-
- // hack to support loadmask
- mask: function() {
- this.masked = true;
- this.fireEvent('beforeload');
+ data.push(record);
+ })
+ me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
},
-
- // hack to support loadmask
- unmask: function() {
- if (this.masked) {
- this.fireEvent('load');
+
+ // Before rendering, set current default step count to be number of records.
+ processView: function () {
+ var me = this;
+ if (me.fromDate) {
+ me.minimum = +me.fromDate;
}
- },
-
- /**
- * Returns the number of pending requests out.
- */
- hasPendingRequests: function() {
- return this.pendingRequests.length;
- },
-
-
- // wait until all requests finish, until guaranteeing the range.
- onWaitForGuarantee: function() {
- if (!this.hasPendingRequests()) {
- this.onGuaranteedRange();
+ if (me.toDate) {
+ me.maximum = +me.toDate;
}
- },
-
- /**
- * Guarantee a specific range, this will load the store with a range (that
- * must be the pageSize or smaller) and take care of any loading that may
- * be necessary.
- */
- guaranteeRange: function(start, end, cb, scope) {
- //
- if (start && end) {
- if (end - start > this.pageSize) {
- Ext.Error.raise({
- start: start,
- end: end,
- pageSize: this.pageSize,
- msg: "Requested a bigger range than the specified pageSize"
- });
- }
+ if (me.constrain) {
+ me.doConstrain();
}
- //
-
- end = (end > this.totalCount) ? this.totalCount - 1 : end;
-
- var me = this,
- i = start,
- prefetchData = me.prefetchData,
- range = [],
- startLoaded = !!prefetchData.getByKey(start),
- endLoaded = !!prefetchData.getByKey(end),
- startPage = me.getPageFromRecordIndex(start),
- endPage = me.getPageFromRecordIndex(end);
-
- me.cb = cb;
- me.scope = scope;
+ },
- me.requestStart = start;
- me.requestEnd = end;
- // neither beginning or end are loaded
- if (!startLoaded || !endLoaded) {
- // same page, lets load it
- if (startPage === endPage) {
- me.mask();
- me.prefetchPage(startPage, {
- //blocking: true,
- callback: me.onWaitForGuarantee,
- scope: me
- });
- // need to load two pages
- } else {
- me.mask();
- me.prefetchPage(startPage, {
- //blocking: true,
- callback: me.onWaitForGuarantee,
- scope: me
- });
- me.prefetchPage(endPage, {
- //blocking: true,
- callback: me.onWaitForGuarantee,
- scope: me
- });
+ // @private modifies the store and creates the labels for the axes.
+ calcEnds: function() {
+ var me = this, range, step = me.step;
+ if (step) {
+ range = me.getRange();
+ range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
+ if (me.minimum) {
+ range.from = me.minimum;
}
- // Request was already satisfied via the prefetch
- } else {
- me.onGuaranteedRange();
- }
- },
-
- // because prefetchData is stored by index
- // this invalidates all of the prefetchedData
- sort: function() {
- var me = this,
- prefetchData = me.prefetchData,
- sorters,
- start,
- end,
- range;
-
- if (me.buffered) {
- if (me.remoteSort) {
- prefetchData.clear();
- me.callParent(arguments);
- } else {
- sorters = me.getSorters();
- start = me.guaranteedStart;
- end = me.guaranteedEnd;
-
- if (sorters.length) {
- prefetchData.sort(sorters);
- range = prefetchData.getRange();
- prefetchData.clear();
- me.cacheRecords(range);
- delete me.guaranteedStart;
- delete me.guaranteedEnd;
- me.guaranteeRange(start, end);
- }
- me.callParent(arguments);
+ if (me.maximum) {
+ range.to = me.maximum;
}
+ range.step = (range.to - range.from) / range.steps;
+ return range;
} else {
- me.callParent(arguments);
+ return me.callParent(arguments);
}
- },
+ }
+ });
- // overriden to provide striping of the indexes as sorting occurs.
- // this cannot be done inside of sort because datachanged has already
- // fired and will trigger a repaint of the bound view.
- doSort: function(sorterFn) {
- var me = this;
- if (me.remoteSort) {
- //the load function will pick up the new sorters and request the sorted data from the proxy
- me.load();
- } else {
- me.data.sortBy(sorterFn);
- if (!me.buffered) {
- var range = me.getRange(),
- ln = range.length,
- i = 0;
- for (; i < ln; i++) {
- range[i].index = i;
- }
- }
- me.fireEvent('datachanged', me);
- }
- },
-
- /**
- * Finds the index of the first matching Record in this store by a specific field value.
- * @param {String} fieldName The name of the Record field to test.
- * @param {String/RegExp} value Either a string that the field value
- * should begin with, or a RegExp to test against the field.
- * @param {Number} startIndex (optional) The index to start searching at
- * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
- * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
- * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
- * @return {Number} The matched index or -1
- */
- find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
- var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
- return fn ? this.data.findIndexBy(fn, null, start) : -1;
+
+/**
+ * @class Ext.chart.series.Series
+ *
+ * Series is the abstract class containing the common logic to all chart series. Series includes
+ * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
+ * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
+ *
+ * ## Listeners
+ *
+ * The series class supports listeners via the Observable syntax. Some of these listeners are:
+ *
+ * - `itemmouseup` When the user interacts with a marker.
+ * - `itemmousedown` When the user interacts with a marker.
+ * - `itemmousemove` When the user iteracts with a marker.
+ * - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
+ *
+ * For example:
+ *
+ * series: [{
+ * type: 'column',
+ * axis: 'left',
+ * listeners: {
+ * 'afterrender': function() {
+ * console('afterrender');
+ * }
+ * },
+ * xField: 'category',
+ * yField: 'data1'
+ * }]
+ */
+Ext.define('Ext.chart.series.Series', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable',
+ labels: 'Ext.chart.Label',
+ highlights: 'Ext.chart.Highlight',
+ tips: 'Ext.chart.Tip',
+ callouts: 'Ext.chart.Callout'
},
+ /* End Definitions */
+
/**
- * Finds the first matching Record in this store by a specific field value.
- * @param {String} fieldName The name of the Record field to test.
- * @param {String/RegExp} value Either a string that the field value
- * should begin with, or a RegExp to test against the field.
- * @param {Number} startIndex (optional) The index to start searching at
- * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
- * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
- * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
- * @return {Ext.data.Model} The matched record or null
+ * @cfg {Boolean/Object} highlight
+ * If set to `true` it will highlight the markers or the series when hovering
+ * with the mouse. This parameter can also be an object with the same style
+ * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
+ * styles to markers and series.
*/
- findRecord: function() {
- var me = this,
- index = me.find.apply(me, arguments);
- return index !== -1 ? me.getAt(index) : null;
- },
/**
- * @private
- * Returns a filter function used to test a the given property's value. Defers most of the work to
- * Ext.util.MixedCollection's createValueMatcher function
- * @param {String} property The property to create the filter function for
- * @param {String/RegExp} value The string/regex to compare the property value to
- * @param {Boolean} anyMatch True if we don't care if the filter value is not the full value (defaults to false)
- * @param {Boolean} caseSensitive True to create a case-sensitive regex (defaults to false)
- * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
- * Ignored if anyMatch is true.
+ * @cfg {Object} tips
+ * Add tooltips to the visualization's markers. The options for the tips are the
+ * same configuration used with {@link Ext.tip.ToolTip}. For example:
+ *
+ * tips: {
+ * trackMouse: true,
+ * width: 140,
+ * height: 28,
+ * renderer: function(storeItem, item) {
+ * this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
+ * }
+ * },
*/
- createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
- if (Ext.isEmpty(value)) {
- return false;
- }
- value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
- return function(r) {
- return value.test(r.data[property]);
- };
- },
/**
- * Finds the index of the first matching Record in this store by a specific field value.
- * @param {String} fieldName The name of the Record field to test.
- * @param {Mixed} value The value to match the field against.
- * @param {Number} startIndex (optional) The index to start searching at
- * @return {Number} The matched index or -1
+ * @cfg {String} type
+ * The type of series. Set in subclasses.
*/
- findExact: function(property, value, start) {
- return this.data.findIndexBy(function(rec) {
- return rec.get(property) === value;
- },
- this, start);
- },
+ type: null,
/**
- * Find the index of the first matching Record in this Store by a function.
- * If the function returns true it is considered a match.
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to this Store.
- * @param {Number} startIndex (optional) The index to start searching at
- * @return {Number} The matched index or -1
+ * @cfg {String} title
+ * The human-readable name of the series.
*/
- findBy: function(fn, scope, start) {
- return this.data.findIndexBy(fn, scope, start);
- },
+ title: null,
/**
- * Collects unique values for a particular dataIndex from this store.
- * @param {String} dataIndex The property to collect
- * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
- * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
- * @return {Array} An array of the unique values
- **/
- collect: function(dataIndex, allowNull, bypassFilter) {
- var me = this,
- data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
-
- return data.collect(dataIndex, 'data', allowNull);
- },
+ * @cfg {Boolean} showInLegend
+ * Whether to show this series in the legend.
+ */
+ showInLegend: true,
/**
- * Gets the number of cached records.
- * If using paging, this may not be the total size of the dataset. If the data object
- * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
- * the dataset size. Note : see the Important note in {@link #load}.
- * @return {Number} The number of Records in the Store's cache.
+ * @cfg {Function} renderer
+ * A function that can be overridden to set custom styling properties to each rendered element.
+ * Passes in (sprite, record, attributes, index, store) to the function.
*/
- getCount: function() {
- return this.data.length || 0;
+ renderer: function(sprite, record, attributes, index, store) {
+ return attributes;
},
/**
- * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
- * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
- * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
- * could be loaded into the Store if the Store contained all data
- * @return {Number} The total number of Model instances available via the Proxy
+ * @cfg {Array} shadowAttributes
+ * An array with shadow attributes
*/
- getTotalCount: function() {
- return this.totalCount;
- },
+ shadowAttributes: null,
+
+ //@private triggerdrawlistener flag
+ triggerAfterDraw: false,
/**
- * Get the Record at the specified index.
- * @param {Number} index The index of the Record to find.
- * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
+ * @cfg {Object} listeners
+ * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
+ *
+ * - itemmouseover
+ * - itemmouseout
+ * - itemmousedown
+ * - itemmouseup
*/
- getAt: function(index) {
- return this.data.getAt(index);
- },
+ constructor: function(config) {
+ var me = this;
+ if (config) {
+ Ext.apply(me, config);
+ }
+
+ me.shadowGroups = [];
+
+ me.mixins.labels.constructor.call(me, config);
+ me.mixins.highlights.constructor.call(me, config);
+ me.mixins.tips.constructor.call(me, config);
+ me.mixins.callouts.constructor.call(me, config);
+
+ me.addEvents({
+ scope: me,
+ itemmouseover: true,
+ itemmouseout: true,
+ itemmousedown: true,
+ itemmouseup: true,
+ mouseleave: true,
+ afterdraw: true,
+
+ /**
+ * @event titlechange
+ * Fires when the series title is changed via {@link #setTitle}.
+ * @param {String} title The new title value
+ * @param {Number} index The index in the collection of titles
+ */
+ titlechange: true
+ });
+
+ me.mixins.observable.constructor.call(me, config);
+
+ me.on({
+ scope: me,
+ itemmouseover: me.onItemMouseOver,
+ itemmouseout: me.onItemMouseOut,
+ mouseleave: me.onMouseLeave
+ });
+ },
+
/**
- * Returns a range of Records between specified indices.
- * @param {Number} startIndex (optional) The starting index (defaults to 0)
- * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
- * @return {Ext.data.Model[]} An array of Records
+ * Iterate over each of the records for this series. The default implementation simply iterates
+ * through the entire data store, but individual series implementations can override this to
+ * provide custom handling, e.g. adding/removing records.
+ * @param {Function} fn The function to execute for each record.
+ * @param {Object} scope Scope for the fn.
*/
- getRange: function(start, end) {
- return this.data.getRange(start, end);
+ eachRecord: function(fn, scope) {
+ var chart = this.chart;
+ (chart.substore || chart.store).each(fn, scope);
},
/**
- * Get the Record with the specified id.
- * @param {String} id The id of the Record to find.
- * @return {Ext.data.Model} The Record with the passed id. Returns undefined if not found.
+ * Return the number of records being displayed in this series. Defaults to the number of
+ * records in the store; individual series implementations can override to provide custom handling.
*/
- getById: function(id) {
- return (this.snapshot || this.data).findBy(function(record) {
- return record.getId() === id;
- });
+ getRecordCount: function() {
+ var chart = this.chart,
+ store = chart.substore || chart.store;
+ return store ? store.getCount() : 0;
},
/**
- * Get the index within the cache of the passed Record.
- * @param {Ext.data.Model} record The Ext.data.Model object to find.
- * @return {Number} The index of the passed Record. Returns -1 if not found.
+ * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
+ * @param index
*/
- indexOf: function(record) {
- return this.data.indexOf(record);
+ isExcluded: function(index) {
+ var excludes = this.__excludes;
+ return !!(excludes && excludes[index]);
},
+ // @private set the bbox and clipBox for the series
+ setBBox: function(noGutter) {
+ var me = this,
+ chart = me.chart,
+ chartBBox = chart.chartBBox,
+ gutterX = noGutter ? 0 : chart.maxGutter[0],
+ gutterY = noGutter ? 0 : chart.maxGutter[1],
+ clipBox, bbox;
+
+ clipBox = {
+ x: chartBBox.x,
+ y: chartBBox.y,
+ width: chartBBox.width,
+ height: chartBBox.height
+ };
+ me.clipBox = clipBox;
- /**
- * Get the index within the entire dataset. From 0 to the totalCount.
- * @param {Ext.data.Model} record The Ext.data.Model object to find.
- * @return {Number} The index of the passed Record. Returns -1 if not found.
- */
- indexOfTotal: function(record) {
- return record.index || this.indexOf(record);
+ bbox = {
+ x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
+ y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
+ width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
+ height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
+ };
+ me.bbox = bbox;
},
- /**
- * Get the index within the cache of the Record with the passed id.
- * @param {String} id The id of the Record to find.
- * @return {Number} The index of the Record. Returns -1 if not found.
- */
- indexOfId: function(id) {
- return this.data.indexOfKey(id);
- },
-
- /**
- * Remove all items from the store.
- * @param {Boolean} silent Prevent the `clear` event from being fired.
- */
- removeAll: function(silent) {
+ // @private set the animation for the sprite
+ onAnimate: function(sprite, attr) {
var me = this;
+ sprite.stopAnimation();
+ if (me.triggerAfterDraw) {
+ return sprite.animate(Ext.applyIf(attr, me.chart.animate));
+ } else {
+ me.triggerAfterDraw = true;
+ return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
+ listeners: {
+ 'afteranimate': function() {
+ me.triggerAfterDraw = false;
+ me.fireEvent('afterrender');
+ }
+ }
+ }));
+ }
+ },
- me.clearData();
- if (me.snapshot) {
- me.snapshot.clear();
+ // @private return the gutter.
+ getGutters: function() {
+ return [0, 0];
+ },
+
+ // @private wrapper for the itemmouseover event.
+ onItemMouseOver: function(item) {
+ var me = this;
+ if (item.series === me) {
+ if (me.highlight) {
+ me.highlightItem(item);
+ }
+ if (me.tooltip) {
+ me.showTip(item);
+ }
}
- if (silent !== true) {
- me.fireEvent('clear', me);
+ },
+
+ // @private wrapper for the itemmouseout event.
+ onItemMouseOut: function(item) {
+ var me = this;
+ if (item.series === me) {
+ me.unHighlightItem();
+ if (me.tooltip) {
+ me.hideTip(item);
+ }
}
},
- /*
- * Aggregation methods
- */
+ // @private wrapper for the mouseleave event.
+ onMouseLeave: function() {
+ var me = this;
+ me.unHighlightItem();
+ if (me.tooltip) {
+ me.hideTip();
+ }
+ },
/**
- * Convenience function for getting the first model instance in the store
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the first record being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
+ * For a given x/y point relative to the Surface, find a corresponding item from this
+ * series, if any.
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Object} An object describing the item, or null if there is no matching item.
+ * The exact contents of this object will vary by series type, but should always contain the following:
+ * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
+ * @return {Object} return.value the value(s) of the item's data point
+ * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
+ * for this data item, which can be used as e.g. a tooltip anchor point.
+ * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
*/
- first: function(grouped) {
- var me = this;
-
- if (grouped && me.isGrouped()) {
- return me.aggregate(function(records) {
- return records.length ? records[0] : undefined;
- }, me, true);
- } else {
- return me.data.first();
+ getItemForPoint: function(x, y) {
+ //if there are no items to query just return null.
+ if (!this.items || !this.items.length || this.seriesIsHidden) {
+ return null;
+ }
+ var me = this,
+ items = me.items,
+ bbox = me.bbox,
+ item, i, ln;
+ // Check bounds
+ if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
+ return null;
+ }
+ for (i = 0, ln = items.length; i < ln; i++) {
+ if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
+ return items[i];
+ }
}
+
+ return null;
+ },
+
+ isItemInPoint: function(x, y, item, i) {
+ return false;
},
/**
- * Convenience function for getting the last model instance in the store
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the last record being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
+ * Hides all the elements in the series.
*/
- last: function(grouped) {
- var me = this;
+ hideAll: function() {
+ var me = this,
+ items = me.items,
+ item, len, i, j, l, sprite, shadows;
- if (grouped && me.isGrouped()) {
- return me.aggregate(function(records) {
- var len = records.length;
- return len ? records[len - 1] : undefined;
- }, me, true);
- } else {
- return me.data.last();
+ me.seriesIsHidden = true;
+ me._prevShowMarkers = me.showMarkers;
+
+ me.showMarkers = false;
+ //hide all labels
+ me.hideLabels(0);
+ //hide all sprites
+ for (i = 0, len = items.length; i < len; i++) {
+ item = items[i];
+ sprite = item.sprite;
+ if (sprite) {
+ sprite.setAttributes({
+ hidden: true
+ }, true);
+ }
+
+ if (sprite && sprite.shadows) {
+ shadows = sprite.shadows;
+ for (j = 0, l = shadows.length; j < l; ++j) {
+ shadows[j].setAttributes({
+ hidden: true
+ }, true);
+ }
+ }
}
},
/**
- * Sums the value of property for each {@link Ext.data.Model record} between start
- * and end and returns the result.
- * @param {String} field A field in each record
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the sum for that group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Number} The sum
+ * Shows all the elements in the series.
*/
- sum: function(field, grouped) {
- var me = this;
+ showAll: function() {
+ var me = this,
+ prevAnimate = me.chart.animate;
+ me.chart.animate = false;
+ me.seriesIsHidden = false;
+ me.showMarkers = me._prevShowMarkers;
+ me.drawSeries();
+ me.chart.animate = prevAnimate;
+ },
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getSum, me, true, [field]);
- } else {
- return me.getSum(me.data.items, field);
+ /**
+ * Returns a string with the color to be used for the series legend item.
+ */
+ getLegendColor: function(index) {
+ var me = this, fill, stroke;
+ if (me.seriesStyle) {
+ fill = me.seriesStyle.fill;
+ stroke = me.seriesStyle.stroke;
+ if (fill && fill != 'none') {
+ return fill;
+ }
+ return stroke;
}
+ return '#000';
},
- // @private, see sum
- getSum: function(records, field) {
- var total = 0,
- i = 0,
- len = records.length;
-
- for (; i < len; ++i) {
- total += records[i].get(field);
+ /**
+ * Checks whether the data field should be visible in the legend
+ * @private
+ * @param {Number} index The index of the current item
+ */
+ visibleInLegend: function(index){
+ var excludes = this.__excludes;
+ if (excludes) {
+ return !excludes[index];
}
-
- return total;
+ return !this.seriesIsHidden;
},
/**
- * Gets the count of items in the store.
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the count for each group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Number} the count
+ * Changes the value of the {@link #title} for the series.
+ * Arguments can take two forms:
+ *
+ * A single String value: this will be used as the new single title for the series (applies
+ * to series with only one yField)
+ * A numeric index and a String value: this will set the title for a single indexed yField.
+ *
+ * @param {Number} index
+ * @param {String} title
*/
- count: function(grouped) {
- var me = this;
+ setTitle: function(index, title) {
+ var me = this,
+ oldTitle = me.title;
- if (grouped && me.isGrouped()) {
- return me.aggregate(function(records) {
- return records.length;
- }, me, true);
+ if (Ext.isString(index)) {
+ title = index;
+ index = 0;
+ }
+
+ if (Ext.isArray(oldTitle)) {
+ oldTitle[index] = title;
} else {
- return me.getCount();
+ me.title = title;
}
- },
+
+ me.fireEvent('titlechange', title, index);
+ }
+});
+
+/**
+ * @class Ext.chart.series.Cartesian
+ * @extends Ext.chart.series.Series
+ *
+ * Common base class for series implementations which plot values using x/y coordinates.
+ */
+Ext.define('Ext.chart.series.Cartesian', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.chart.series.Series',
+
+ alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
+
+ /* End Definitions */
/**
- * Gets the minimum value in the store.
- * @param {String} field The field in each record
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the minimum in the group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Mixed/undefined} The minimum value, if no items exist, undefined.
+ * The field used to access the x axis value from the items from the data
+ * source.
+ *
+ * @cfg xField
+ * @type String
*/
- min: function(field, grouped) {
- var me = this;
+ xField: null,
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getMin, me, true, [field]);
- } else {
- return me.getMin(me.data.items, field);
- }
- },
+ /**
+ * The field used to access the y-axis value from the items from the data
+ * source.
+ *
+ * @cfg yField
+ * @type String
+ */
+ yField: null,
+
+ /**
+ * @cfg {String} axis
+ * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
+ * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
+ * relative scale will be used.
+ */
+ axis: 'left',
- // @private, see min
- getMin: function(records, field){
- var i = 1,
- len = records.length,
- value, min;
+ getLegendLabels: function() {
+ var me = this,
+ labels = [],
+ combinations = me.combinations;
- if (len > 0) {
- min = records[0].get(field);
- }
+ Ext.each([].concat(me.yField), function(yField, i) {
+ var title = me.title;
+ // Use the 'title' config if present, otherwise use the raw yField name
+ labels.push((Ext.isArray(title) ? title[i] : title) || yField);
+ });
- for (; i < len; ++i) {
- value = records[i].get(field);
- if (value < min) {
- min = value;
- }
+ // Handle yFields combined via legend drag-drop
+ if (combinations) {
+ Ext.each(combinations, function(combo) {
+ var label0 = labels[combo[0]],
+ label1 = labels[combo[1]];
+ labels[combo[1]] = label0 + ' & ' + label1;
+ labels.splice(combo[0], 1);
+ });
}
- return min;
+
+ return labels;
},
/**
- * Gets the maximum value in the store.
- * @param {String} field The field in each record
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the maximum in the group being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Mixed/undefined} The maximum value, if no items exist, undefined.
+ * @protected Iterates over a given record's values for each of this series's yFields,
+ * executing a given function for each value. Any yFields that have been combined
+ * via legend drag-drop will be treated as a single value.
+ * @param {Ext.data.Model} record
+ * @param {Function} fn
+ * @param {Object} scope
*/
- max: function(field, grouped) {
- var me = this;
+ eachYValue: function(record, fn, scope) {
+ Ext.each(this.getYValueAccessors(), function(accessor, i) {
+ fn.call(scope, accessor(record), i);
+ });
+ },
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getMax, me, true, [field]);
- } else {
- return me.getMax(me.data.items, field);
- }
+ /**
+ * @protected Returns the number of yField values, taking into account fields combined
+ * via legend drag-drop.
+ * @return {Number}
+ */
+ getYValueCount: function() {
+ return this.getYValueAccessors().length;
},
- // @private, see max
- getMax: function(records, field) {
- var i = 1,
- len = records.length,
- value,
- max;
+ combine: function(index1, index2) {
+ var me = this,
+ accessors = me.getYValueAccessors(),
+ accessor1 = accessors[index1],
+ accessor2 = accessors[index2];
- if (len > 0) {
- max = records[0].get(field);
- }
+ // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
+ accessors[index2] = function(record) {
+ return accessor1(record) + accessor2(record);
+ };
+ accessors.splice(index1, 1);
- for (; i < len; ++i) {
- value = records[i].get(field);
- if (value > max) {
- max = value;
- }
- }
- return max;
+ me.callParent([index1, index2]);
+ },
+
+ clearCombinations: function() {
+ // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
+ delete this.yValueAccessors;
+ this.callParent();
},
/**
- * Gets the average value in the store.
- * @param {String} field The field in each record
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the group average being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @return {Mixed/undefined} The average value, if no items exist, 0.
+ * @protected Returns an array of functions, each of which returns the value of the yField
+ * corresponding to function's index in the array, for a given record (each function takes the
+ * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
+ * this list of accessors will be kept in sync with those combinations.
+ * @return {Array} array of accessor functions
*/
- average: function(field, grouped) {
- var me = this;
- if (grouped && me.isGrouped()) {
- return me.aggregate(me.getAverage, me, true, [field]);
- } else {
- return me.getAverage(me.data.items, field);
+ getYValueAccessors: function() {
+ var me = this,
+ accessors = me.yValueAccessors;
+ if (!accessors) {
+ accessors = me.yValueAccessors = [];
+ Ext.each([].concat(me.yField), function(yField) {
+ accessors.push(function(record) {
+ return record.get(yField);
+ });
+ });
}
+ return accessors;
},
- // @private, see average
- getAverage: function(records, field) {
- var i = 0,
- len = records.length,
- sum = 0;
+ /**
+ * Calculate the min and max values for this series's xField.
+ * @return {Array} [min, max]
+ */
+ getMinMaxXValues: function() {
+ var me = this,
+ min, max,
+ xField = me.xField;
- if (records.length > 0) {
- for (; i < len; ++i) {
- sum += records[i].get(field);
- }
- return sum / len;
+ if (me.getRecordCount() > 0) {
+ min = Infinity;
+ max = -min;
+ me.eachRecord(function(record) {
+ var xValue = record.get(xField);
+ if (xValue > max) {
+ max = xValue;
+ }
+ if (xValue < min) {
+ min = xValue;
+ }
+ });
+ } else {
+ min = max = 0;
}
- return 0;
+ return [min, max];
},
/**
- * Runs the aggregate function for all the records in the store.
- * @param {Function} fn The function to execute. The function is called with a single parameter,
- * an array of records for that group.
- * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
- * @param {Boolean} grouped (Optional) True to perform the operation for each group
- * in the store. The value returned will be an object literal with the key being the group
- * name and the group average being the value. The grouped parameter is only honored if
- * the store has a groupField.
- * @param {Array} args (optional) Any arguments to append to the function call
- * @return {Object} An object literal with the group names and their appropriate values.
+ * Calculate the min and max values for this series's yField(s). Takes into account yField
+ * combinations, exclusions, and stacking.
+ * @return {Array} [min, max]
*/
- aggregate: function(fn, scope, grouped, args) {
- args = args || [];
- if (grouped && this.isGrouped()) {
- var groups = this.getGroups(),
- i = 0,
- len = groups.length,
- out = {},
- group;
+ getMinMaxYValues: function() {
+ var me = this,
+ stacked = me.stacked,
+ min, max,
+ positiveTotal, negativeTotal;
- for (; i < len; ++i) {
- group = groups[i];
- out[group.name] = fn.apply(scope || this, [group.children].concat(args));
+ function eachYValueStacked(yValue, i) {
+ if (!me.isExcluded(i)) {
+ if (yValue < 0) {
+ negativeTotal += yValue;
+ } else {
+ positiveTotal += yValue;
+ }
}
- return out;
- } else {
- return fn.apply(scope || this, [this.data.items].concat(args));
}
- }
-});
-/**
- * @author Ed Spencer
- * @class Ext.data.JsonStore
- * @extends Ext.data.Store
- * @ignore
- *
- * Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
- * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.
- *
- * A store configuration would be something like:
- *
-
-var store = new Ext.data.JsonStore({
- // store configs
- autoDestroy: true,
- storeId: 'myStore'
+ function eachYValue(yValue, i) {
+ if (!me.isExcluded(i)) {
+ if (yValue > max) {
+ max = yValue;
+ }
+ if (yValue < min) {
+ min = yValue;
+ }
+ }
+ }
- proxy: {
- type: 'ajax',
- url: 'get-images.php',
- reader: {
- type: 'json',
- root: 'images',
- idProperty: 'name'
+ if (me.getRecordCount() > 0) {
+ min = Infinity;
+ max = -min;
+ me.eachRecord(function(record) {
+ if (stacked) {
+ positiveTotal = 0;
+ negativeTotal = 0;
+ me.eachYValue(record, eachYValueStacked);
+ if (positiveTotal > max) {
+ max = positiveTotal;
+ }
+ if (negativeTotal < min) {
+ min = negativeTotal;
+ }
+ } else {
+ me.eachYValue(record, eachYValue);
+ }
+ });
+ } else {
+ min = max = 0;
}
+ return [min, max];
},
- //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
- fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
-});
-
- *
- * This store is configured to consume a returned object of the form:
-{
- images: [
- {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
- {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
- ]
-}
-
- *
- * An object literal of this form could also be used as the {@link #data} config option.
- *
- * @xtype jsonstore
- */
-Ext.define('Ext.data.JsonStore', {
- extend: 'Ext.data.Store',
- alias: 'store.json',
+ getAxesForXAndYFields: function() {
+ var me = this,
+ axes = me.chart.axes,
+ axis = [].concat(me.axis),
+ xAxis, yAxis;
- /**
- * @cfg {Ext.data.DataReader} reader @hide
- */
- constructor: function(config) {
- config = config || {};
+ if (Ext.Array.indexOf(axis, 'top') > -1) {
+ xAxis = 'top';
+ } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
+ xAxis = 'bottom';
+ } else {
+ if (axes.get('top')) {
+ xAxis = 'top';
+ } else if (axes.get('bottom')) {
+ xAxis = 'bottom';
+ }
+ }
- Ext.applyIf(config, {
- proxy: {
- type : 'ajax',
- reader: 'json',
- writer: 'json'
+ if (Ext.Array.indexOf(axis, 'left') > -1) {
+ yAxis = 'left';
+ } else if (Ext.Array.indexOf(axis, 'right') > -1) {
+ yAxis = 'right';
+ } else {
+ if (axes.get('left')) {
+ yAxis = 'left';
+ } else if (axes.get('right')) {
+ yAxis = 'right';
}
- });
+ }
- this.callParent([config]);
+ return {
+ xAxis: xAxis,
+ yAxis: yAxis
+ };
}
+
+
});
/**
- * @class Ext.chart.axis.Time
- * @extends Ext.chart.axis.Axis
+ * @class Ext.chart.series.Area
+ * @extends Ext.chart.series.Cartesian
*
- * A type of axis whose units are measured in time values. Use this axis
- * for listing dates that you will want to group or dynamically change.
- * If you just want to display dates as categories then use the
- * Category class for axis instead.
+ * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
+ * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
+ * documentation for more information. A typical configuration object for the area series could be:
*
- * For example:
+ * @example
+ * var store = Ext.create('Ext.data.JsonStore', {
+ * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ * data: [
+ * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
+ * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
+ * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
+ * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
+ * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
+ * ]
+ * });
*
- * axes: [{
- * type: 'Time',
- * position: 'bottom',
- * fields: 'date',
- * title: 'Day',
- * dateFormat: 'M d',
- * groupBy: 'year,month,day',
- * aggregateOp: 'sum',
- *
- * constrain: true,
- * fromDate: new Date('1/1/11'),
- * toDate: new Date('1/7/11')
- * }]
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 500,
+ * height: 300,
+ * store: store,
+ * axes: [
+ * {
+ * type: 'Numeric',
+ * grid: true,
+ * position: 'left',
+ * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * title: 'Sample Values',
+ * grid: {
+ * odd: {
+ * opacity: 1,
+ * fill: '#ddd',
+ * stroke: '#bbb',
+ * 'stroke-width': 1
+ * }
+ * },
+ * minimum: 0,
+ * adjustMinimumByMajorUnit: 0
+ * },
+ * {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics',
+ * grid: true,
+ * label: {
+ * rotate: {
+ * degrees: 315
+ * }
+ * }
+ * }
+ * ],
+ * series: [{
+ * type: 'area',
+ * highlight: false,
+ * axis: 'left',
+ * xField: 'name',
+ * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * style: {
+ * opacity: 0.93
+ * }
+ * }]
+ * });
*
- * In this example we're creating a time axis that has as title *Day*.
- * The field the axis is bound to is `date`.
- * The date format to use to display the text for the axis labels is `M d`
- * which is a three letter month abbreviation followed by the day number.
- * The time axis will show values for dates between `fromDate` and `toDate`.
- * Since `constrain` is set to true all other values for other dates not between
- * the fromDate and toDate will not be displayed.
- *
+ * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
+ * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
+ * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
+ * to the style object.
+ *
+ * @xtype area
*/
-Ext.define('Ext.chart.axis.Time', {
+Ext.define('Ext.chart.series.Area', {
/* Begin Definitions */
- extend: 'Ext.chart.axis.Category',
-
- alternateClassName: 'Ext.chart.TimeAxis',
+ extend: 'Ext.chart.series.Cartesian',
- alias: 'axis.time',
+ alias: 'series.area',
- requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
+ requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
/* End Definitions */
- /**
- * The minimum value drawn by the axis. If not set explicitly, the axis
- * minimum will be calculated automatically.
- * @property calculateByLabelSize
- * @type Boolean
- */
- calculateByLabelSize: true,
-
- /**
- * Indicates the format the date will be rendered on.
- * For example: 'M d' will render the dates as 'Jan 30', etc.
- *
- * @property dateFormat
- * @type {String|Boolean}
- */
- dateFormat: false,
-
- /**
- * Indicates the time unit to use for each step. Can be 'day', 'month', 'year' or a comma-separated combination of all of them.
- * Default's 'year,month,day'.
- *
- * @property timeUnit
- * @type {String}
- */
- groupBy: 'year,month,day',
-
- /**
- * Aggregation operation when grouping. Possible options are 'sum', 'avg', 'max', 'min'. Default's 'sum'.
- *
- * @property aggregateOp
- * @type {String}
- */
- aggregateOp: 'sum',
-
- /**
- * The starting date for the time axis.
- * @property fromDate
- * @type Date
- */
- fromDate: false,
-
- /**
- * The ending date for the time axis.
- * @property toDate
- * @type Date
- */
- toDate: false,
-
- /**
- * An array with two components: The first is the unit of the step (day, month, year, etc). The second one is the number of units for the step (1, 2, etc.).
- * Default's [Ext.Date.DAY, 1].
- *
- * @property step
- * @type Array
- */
- step: [Ext.Date.DAY, 1],
-
+ type: 'area',
+
+ // @private Area charts are alyways stacked
+ stacked: true,
+
/**
- * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
- * If false, the time axis will adapt to the new values by adding/removing steps.
- * Default's [Ext.Date.DAY, 1].
- *
- * @property constrain
- * @type Boolean
+ * @cfg {Object} style
+ * Append styling properties to this object for it to override theme properties.
*/
- constrain: false,
-
- // @private a wrapper for date methods.
- dateMethods: {
- 'year': function(date) {
- return date.getFullYear();
- },
- 'month': function(date) {
- return date.getMonth() + 1;
- },
- 'day': function(date) {
- return date.getDate();
- },
- 'hour': function(date) {
- return date.getHours();
- },
- 'minute': function(date) {
- return date.getMinutes();
- },
- 'second': function(date) {
- return date.getSeconds();
- },
- 'millisecond': function(date) {
- return date.getMilliseconds();
+ style: {},
+
+ constructor: function(config) {
+ this.callParent(arguments);
+ var me = this,
+ surface = me.chart.surface,
+ i, l;
+ Ext.apply(me, config, {
+ __excludes: [],
+ highlightCfg: {
+ lineWidth: 3,
+ stroke: '#55c',
+ opacity: 0.8,
+ color: '#f00'
+ }
+ });
+ if (me.highlight) {
+ me.highlightSprite = surface.add({
+ type: 'path',
+ path: ['M', 0, 0],
+ zIndex: 1000,
+ opacity: 0.3,
+ lineWidth: 5,
+ hidden: true,
+ stroke: '#444'
+ });
}
+ me.group = surface.getGroup(me.seriesId);
},
-
- // @private holds aggregate functions.
- aggregateFn: (function() {
- var etype = (function() {
- var rgxp = /^\[object\s(.*)\]$/,
- toString = Object.prototype.toString;
- return function(e) {
- return toString.call(e).match(rgxp)[1];
- };
- })();
- return {
- 'sum': function(list) {
- var i = 0, l = list.length, acum = 0;
- if (!list.length || etype(list[0]) != 'Number') {
- return list[0];
+
+ // @private Shrinks dataSets down to a smaller size
+ shrink: function(xValues, yValues, size) {
+ var len = xValues.length,
+ ratio = Math.floor(len / size),
+ i, j,
+ xSum = 0,
+ yCompLen = this.areas.length,
+ ySum = [],
+ xRes = [],
+ yRes = [];
+ //initialize array
+ for (j = 0; j < yCompLen; ++j) {
+ ySum[j] = 0;
+ }
+ for (i = 0; i < len; ++i) {
+ xSum += xValues[i];
+ for (j = 0; j < yCompLen; ++j) {
+ ySum[j] += yValues[i][j];
+ }
+ if (i % ratio == 0) {
+ //push averages
+ xRes.push(xSum/ratio);
+ for (j = 0; j < yCompLen; ++j) {
+ ySum[j] /= ratio;
}
- for (; i < l; i++) {
- acum += list[i];
+ yRes.push(ySum);
+ //reset sum accumulators
+ xSum = 0;
+ for (j = 0, ySum = []; j < yCompLen; ++j) {
+ ySum[j] = 0;
}
- return acum;
- },
- 'max': function(list) {
- if (!list.length || etype(list[0]) != 'Number') {
- return list[0];
+ }
+ }
+ return {
+ x: xRes,
+ y: yRes
+ };
+ },
+
+ // @private Get chart and data boundaries
+ getBounds: function() {
+ var me = this,
+ chart = me.chart,
+ store = chart.getChartStore(),
+ areas = [].concat(me.yField),
+ areasLen = areas.length,
+ xValues = [],
+ yValues = [],
+ infinity = Infinity,
+ minX = infinity,
+ minY = infinity,
+ maxX = -infinity,
+ maxY = -infinity,
+ math = Math,
+ mmin = math.min,
+ mmax = math.max,
+ bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
+
+ me.setBBox();
+ bbox = me.bbox;
+
+ // Run through the axis
+ if (me.axis) {
+ axis = chart.axes.get(me.axis);
+ if (axis) {
+ out = axis.calcEnds();
+ minY = out.from || axis.prevMin;
+ maxY = mmax(out.to || axis.prevMax, 0);
+ }
+ }
+
+ if (me.yField && !Ext.isNumber(minY)) {
+ axis = Ext.create('Ext.chart.axis.Axis', {
+ chart: chart,
+ fields: [].concat(me.yField)
+ });
+ out = axis.calcEnds();
+ minY = out.from || axis.prevMin;
+ maxY = mmax(out.to || axis.prevMax, 0);
+ }
+
+ if (!Ext.isNumber(minY)) {
+ minY = 0;
+ }
+ if (!Ext.isNumber(maxY)) {
+ maxY = 0;
+ }
+
+ store.each(function(record, i) {
+ xValue = record.get(me.xField);
+ yValue = [];
+ if (typeof xValue != 'number') {
+ xValue = i;
+ }
+ xValues.push(xValue);
+ acumY = 0;
+ for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
+ areaElem = record.get(areas[areaIndex]);
+ if (typeof areaElem == 'number') {
+ minY = mmin(minY, areaElem);
+ yValue.push(areaElem);
+ acumY += areaElem;
+ }
+ }
+ minX = mmin(minX, xValue);
+ maxX = mmax(maxX, xValue);
+ maxY = mmax(maxY, acumY);
+ yValues.push(yValue);
+ }, me);
+
+ xScale = bbox.width / ((maxX - minX) || 1);
+ yScale = bbox.height / ((maxY - minY) || 1);
+
+ ln = xValues.length;
+ if ((ln > bbox.width) && me.areas) {
+ sumValues = me.shrink(xValues, yValues, bbox.width);
+ xValues = sumValues.x;
+ yValues = sumValues.y;
+ }
+
+ return {
+ bbox: bbox,
+ minX: minX,
+ minY: minY,
+ xValues: xValues,
+ yValues: yValues,
+ xScale: xScale,
+ yScale: yScale,
+ areasLen: areasLen
+ };
+ },
+
+ // @private Build an array of paths for the chart
+ getPaths: function() {
+ var me = this,
+ chart = me.chart,
+ store = chart.getChartStore(),
+ first = true,
+ bounds = me.getBounds(),
+ bbox = bounds.bbox,
+ items = me.items = [],
+ componentPaths = [],
+ componentPath,
+ paths = [],
+ i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
+
+ ln = bounds.xValues.length;
+ // Start the path
+ for (i = 0; i < ln; i++) {
+ xValue = bounds.xValues[i];
+ yValue = bounds.yValues[i];
+ x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
+ acumY = 0;
+ for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
+ // Excluded series
+ if (me.__excludes[areaIndex]) {
+ continue;
}
- return Math.max.apply(Math, list);
- },
- 'min': function(list) {
- if (!list.length || etype(list[0]) != 'Number') {
- return list[0];
+ if (!componentPaths[areaIndex]) {
+ componentPaths[areaIndex] = [];
}
- return Math.min.apply(Math, list);
- },
- 'avg': function(list) {
- var i = 0, l = list.length, acum = 0;
- if (!list.length || etype(list[0]) != 'Number') {
- return list[0];
+ areaElem = yValue[areaIndex];
+ acumY += areaElem;
+ y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
+ if (!paths[areaIndex]) {
+ paths[areaIndex] = ['M', x, y];
+ componentPaths[areaIndex].push(['L', x, y]);
+ } else {
+ paths[areaIndex].push('L', x, y);
+ componentPaths[areaIndex].push(['L', x, y]);
}
- for (; i < l; i++) {
- acum += list[i];
+ if (!items[areaIndex]) {
+ items[areaIndex] = {
+ pointsUp: [],
+ pointsDown: [],
+ series: me
+ };
}
- return acum / l;
+ items[areaIndex].pointsUp.push([x, y]);
}
- };
- })(),
-
- // @private normalized the store to fill date gaps in the time interval.
- constrainDates: function() {
- var fromDate = Ext.Date.clone(this.fromDate),
- toDate = Ext.Date.clone(this.toDate),
- step = this.step,
- field = this.fields,
- store = this.chart.store,
- record, recObj, fieldNames = [],
- newStore = Ext.create('Ext.data.Store', {
- model: store.model
- });
-
- var getRecordByDate = (function() {
- var index = 0, l = store.getCount();
- return function(date) {
- var rec, recDate;
- for (; index < l; index++) {
- rec = store.getAt(index);
- recDate = rec.get(field);
- if (+recDate > +date) {
- return false;
- } else if (+recDate == +date) {
- return rec;
- }
- }
- return false;
- };
- })();
-
- if (!this.constrain) {
- this.chart.filteredStore = this.chart.store;
- return;
}
- while(+fromDate <= +toDate) {
- record = getRecordByDate(fromDate);
- recObj = {};
- if (record) {
- newStore.add(record.data);
- } else {
- newStore.model.prototype.fields.each(function(f) {
- recObj[f.name] = false;
- });
- recObj.date = fromDate;
- newStore.add(recObj);
- }
- fromDate = Ext.Date.add(fromDate, step[0], step[1]);
- }
-
- this.chart.filteredStore = newStore;
- },
-
- // @private aggregates values if multiple store elements belong to the same time step.
- aggregate: function() {
- var aggStore = {},
- aggKeys = [], key, value,
- op = this.aggregateOp,
- field = this.fields, i,
- fields = this.groupBy.split(','),
- curField,
- recFields = [],
- recFieldsLen = 0,
- obj,
- dates = [],
- json = [],
- l = fields.length,
- dateMethods = this.dateMethods,
- aggregateFn = this.aggregateFn,
- store = this.chart.filteredStore || this.chart.store;
-
- store.each(function(rec) {
- //get all record field names in a simple array
- if (!recFields.length) {
- rec.fields.each(function(f) {
- recFields.push(f.name);
- });
- recFieldsLen = recFields.length;
- }
- //get record date value
- value = rec.get(field);
- //generate key for grouping records
- for (i = 0; i < l; i++) {
- if (i == 0) {
- key = String(dateMethods[fields[i]](value));
- } else {
- key += '||' + dateMethods[fields[i]](value);
- }
+ // Close the paths
+ for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
+ // Excluded series
+ if (me.__excludes[areaIndex]) {
+ continue;
}
- //get aggregation record from hash
- if (key in aggStore) {
- obj = aggStore[key];
- } else {
- obj = aggStore[key] = {};
- aggKeys.push(key);
- dates.push(value);
+ path = paths[areaIndex];
+ // Close bottom path to the axis
+ if (areaIndex == 0 || first) {
+ first = false;
+ path.push('L', x, bbox.y + bbox.height,
+ 'L', bbox.x, bbox.y + bbox.height,
+ 'Z');
}
- //append record values to an aggregation record
- for (i = 0; i < recFieldsLen; i++) {
- curField = recFields[i];
- if (!obj[curField]) {
- obj[curField] = [];
- }
- if (rec.get(curField) !== undefined) {
- obj[curField].push(rec.get(curField));
+ // Close other paths to the one before them
+ else {
+ componentPath = componentPaths[prevAreaIndex];
+ componentPath.reverse();
+ path.push('L', x, componentPath[0][2]);
+ for (i = 0; i < ln; i++) {
+ path.push(componentPath[i][0],
+ componentPath[i][1],
+ componentPath[i][2]);
+ items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
}
+ path.push('L', bbox.x, path[2], 'Z');
}
- });
- //perform aggregation operations on fields
- for (key in aggStore) {
- obj = aggStore[key];
- for (i = 0; i < recFieldsLen; i++) {
- curField = recFields[i];
- obj[curField] = aggregateFn[op](obj[curField]);
- }
- json.push(obj);
- }
- this.chart.substore = Ext.create('Ext.data.JsonStore', {
- fields: recFields,
- data: json
- });
-
- this.dates = dates;
- },
-
- // @private creates a label array to be used as the axis labels.
- setLabels: function() {
- var store = this.chart.substore,
- fields = this.fields,
- format = this.dateFormat,
- labels, i, dates = this.dates,
- formatFn = Ext.Date.format;
- this.labels = labels = [];
- store.each(function(record, i) {
- if (!format) {
- labels.push(record.get(fields));
- } else {
- labels.push(formatFn(dates[i], format));
- }
- }, this);
- },
-
- processView: function() {
- //TODO(nico): fix this eventually...
- if (this.constrain) {
- this.constrainDates();
- this.aggregate();
- this.chart.substore = this.chart.filteredStore;
- } else {
- this.aggregate();
- }
- },
-
- // @private modifies the store and creates the labels for the axes.
- applyData: function() {
- this.setLabels();
- var count = this.chart.substore.getCount();
- return {
- from: 0,
- to: count,
- steps: count - 1,
- step: 1
- };
- }
- });
-
-
-/**
- * @class Ext.chart.series.Series
- *
- * Series is the abstract class containing the common logic to all chart series. Series includes
- * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
- * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
- *
- * ## Listeners
- *
- * The series class supports listeners via the Observable syntax. Some of these listeners are:
- *
- * - `itemmouseup` When the user interacts with a marker.
- * - `itemmousedown` When the user interacts with a marker.
- * - `itemmousemove` When the user iteracts with a marker.
- * - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
- *
- * For example:
- *
- * series: [{
- * type: 'column',
- * axis: 'left',
- * listeners: {
- * 'afterrender': function() {
- * console('afterrender');
- * }
- * },
- * xField: 'category',
- * yField: 'data1'
- * }]
- *
- */
-Ext.define('Ext.chart.series.Series', {
-
- /* Begin Definitions */
-
- mixins: {
- observable: 'Ext.util.Observable',
- labels: 'Ext.chart.Label',
- highlights: 'Ext.chart.Highlight',
- tips: 'Ext.chart.Tip',
- callouts: 'Ext.chart.Callout'
+ prevAreaIndex = areaIndex;
+ }
+ return {
+ paths: paths,
+ areasLen: bounds.areasLen
+ };
},
- /* End Definitions */
-
- /**
- * @cfg {Boolean|Object} highlight
- * If set to `true` it will highlight the markers or the series when hovering
- * with the mouse. This parameter can also be an object with the same style
- * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
- * styles to markers and series.
- */
-
- /**
- * @cfg {Object} tips
- * Add tooltips to the visualization's markers. The options for the tips are the
- * same configuration used with {@link Ext.tip.ToolTip}. For example:
- *
- * tips: {
- * trackMouse: true,
- * width: 140,
- * height: 28,
- * renderer: function(storeItem, item) {
- * this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
- * }
- * },
- */
-
- /**
- * @cfg {String} type
- * The type of series. Set in subclasses.
- */
- type: null,
-
/**
- * @cfg {String} title
- * The human-readable name of the series.
+ * Draws the series for the current chart.
*/
- title: null,
+ drawSeries: function() {
+ var me = this,
+ chart = me.chart,
+ store = chart.getChartStore(),
+ surface = chart.surface,
+ animate = chart.animate,
+ group = me.group,
+ endLineStyle = Ext.apply(me.seriesStyle, me.style),
+ colorArrayStyle = me.colorArrayStyle,
+ colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
+ areaIndex, areaElem, paths, path, rendererAttributes;
- /**
- * @cfg {Boolean} showInLegend
- * Whether to show this series in the legend.
- */
- showInLegend: true,
+ me.unHighlightItem();
+ me.cleanHighlights();
- /**
- * @cfg {Function} renderer
- * A function that can be overridden to set custom styling properties to each rendered element.
- * Passes in (sprite, record, attributes, index, store) to the function.
- */
- renderer: function(sprite, record, attributes, index, store) {
- return attributes;
- },
+ if (!store || !store.getCount()) {
+ return;
+ }
- /**
- * @cfg {Array} shadowAttributes
- * An array with shadow attributes
- */
- shadowAttributes: null,
-
- //@private triggerdrawlistener flag
- triggerAfterDraw: false,
+ paths = me.getPaths();
- /**
- * @cfg {Object} listeners
- * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
- *
- *
- * itemmouseover
- * itemmouseout
- * itemmousedown
- * itemmouseup
- *
- */
-
- constructor: function(config) {
- var me = this;
- if (config) {
- Ext.apply(me, config);
+ if (!me.areas) {
+ me.areas = [];
}
-
- me.shadowGroups = [];
-
- me.mixins.labels.constructor.call(me, config);
- me.mixins.highlights.constructor.call(me, config);
- me.mixins.tips.constructor.call(me, config);
- me.mixins.callouts.constructor.call(me, config);
- me.addEvents({
- scope: me,
- itemmouseover: true,
- itemmouseout: true,
- itemmousedown: true,
- itemmouseup: true,
- mouseleave: true,
- afterdraw: true,
+ for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
+ // Excluded series
+ if (me.__excludes[areaIndex]) {
+ continue;
+ }
+ if (!me.areas[areaIndex]) {
+ me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
+ type: 'path',
+ group: group,
+ // 'clip-rect': me.clipBox,
+ path: paths.paths[areaIndex],
+ stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
+ fill: colorArrayStyle[areaIndex % colorArrayLength]
+ }, endLineStyle || {}));
+ }
+ areaElem = me.areas[areaIndex];
+ path = paths.paths[areaIndex];
+ if (animate) {
+ //Add renderer to line. There is not a unique record associated with this.
+ rendererAttributes = me.renderer(areaElem, false, {
+ path: path,
+ // 'clip-rect': me.clipBox,
+ fill: colorArrayStyle[areaIndex % colorArrayLength],
+ stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
+ }, areaIndex, store);
+ //fill should not be used here but when drawing the special fill path object
+ me.animation = me.onAnimate(areaElem, {
+ to: rendererAttributes
+ });
+ } else {
+ rendererAttributes = me.renderer(areaElem, false, {
+ path: path,
+ // 'clip-rect': me.clipBox,
+ hidden: false,
+ fill: colorArrayStyle[areaIndex % colorArrayLength],
+ stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
+ }, areaIndex, store);
+ me.areas[areaIndex].setAttributes(rendererAttributes, true);
+ }
+ }
+ me.renderLabels();
+ me.renderCallouts();
+ },
- /**
- * @event titlechange
- * Fires when the series title is changed via {@link #setTitle}.
- * @param {String} title The new title value
- * @param {Number} index The index in the collection of titles
- */
- titlechange: true
- });
+ // @private
+ onAnimate: function(sprite, attr) {
+ sprite.show();
+ return this.callParent(arguments);
+ },
- me.mixins.observable.constructor.call(me, config);
+ // @private
+ onCreateLabel: function(storeItem, item, i, display) {
+ var me = this,
+ group = me.labelsGroup,
+ config = me.label,
+ bbox = me.bbox,
+ endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
- me.on({
- scope: me,
- itemmouseover: me.onItemMouseOver,
- itemmouseout: me.onItemMouseOut,
- mouseleave: me.onMouseLeave
- });
+ return me.chart.surface.add(Ext.apply({
+ 'type': 'text',
+ 'text-anchor': 'middle',
+ 'group': group,
+ 'x': item.point[0],
+ 'y': bbox.y + bbox.height / 2
+ }, endLabelStyle || {}));
},
- // @private set the bbox and clipBox for the series
- setBBox: function(noGutter) {
+ // @private
+ onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
var me = this,
chart = me.chart,
- chartBBox = chart.chartBBox,
- gutterX = noGutter ? 0 : chart.maxGutter[0],
- gutterY = noGutter ? 0 : chart.maxGutter[1],
- clipBox, bbox;
+ resizing = chart.resizing,
+ config = me.label,
+ format = config.renderer,
+ field = config.field,
+ bbox = me.bbox,
+ x = item.point[0],
+ y = item.point[1],
+ bb, width, height;
- clipBox = {
- x: chartBBox.x,
- y: chartBBox.y,
- width: chartBBox.width,
- height: chartBBox.height
- };
- me.clipBox = clipBox;
+ label.setAttributes({
+ text: format(storeItem.get(field[index])),
+ hidden: true
+ }, true);
- bbox = {
- x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
- y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
- width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
- height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
- };
- me.bbox = bbox;
- },
+ bb = label.getBBox();
+ width = bb.width / 2;
+ height = bb.height / 2;
- // @private set the animation for the sprite
- onAnimate: function(sprite, attr) {
- var me = this;
- sprite.stopAnimation();
- if (me.triggerAfterDraw) {
- return sprite.animate(Ext.applyIf(attr, me.chart.animate));
- } else {
- me.triggerAfterDraw = true;
- return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
- listeners: {
- 'afteranimate': function() {
- me.triggerAfterDraw = false;
- me.fireEvent('afterrender');
- }
- }
- }));
- }
- },
-
- // @private return the gutter.
- getGutters: function() {
- return [0, 0];
- },
+ x = x - width < bbox.x? bbox.x + width : x;
+ x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
+ y = y - height < bbox.y? bbox.y + height : y;
+ y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
- // @private wrapper for the itemmouseover event.
- onItemMouseOver: function(item) {
- var me = this;
- if (item.series === me) {
- if (me.highlight) {
- me.highlightItem(item);
- }
- if (me.tooltip) {
- me.showTip(item);
+ if (me.chart.animate && !me.chart.resizing) {
+ label.show(true);
+ me.onAnimate(label, {
+ to: {
+ x: x,
+ y: y
+ }
+ });
+ } else {
+ label.setAttributes({
+ x: x,
+ y: y
+ }, true);
+ if (resizing) {
+ me.animation.on('afteranimate', function() {
+ label.show(true);
+ });
+ } else {
+ label.show(true);
}
}
},
- // @private wrapper for the itemmouseout event.
- onItemMouseOut: function(item) {
- var me = this;
- if (item.series === me) {
- me.unHighlightItem();
- if (me.tooltip) {
- me.hideTip(item);
- }
+ // @private
+ onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
+ var me = this,
+ chart = me.chart,
+ surface = chart.surface,
+ resizing = chart.resizing,
+ config = me.callouts,
+ items = me.items,
+ prev = (i == 0) ? false : items[i -1].point,
+ next = (i == items.length -1) ? false : items[i +1].point,
+ cur = item.point,
+ dir, norm, normal, a, aprev, anext,
+ bbox = callout.label.getBBox(),
+ offsetFromViz = 30,
+ offsetToSide = 10,
+ offsetBox = 3,
+ boxx, boxy, boxw, boxh,
+ p, clipRect = me.clipRect,
+ x, y;
+
+ //get the right two points
+ if (!prev) {
+ prev = cur;
}
- },
+ if (!next) {
+ next = cur;
+ }
+ a = (next[1] - prev[1]) / (next[0] - prev[0]);
+ aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
+ anext = (next[1] - cur[1]) / (next[0] - cur[0]);
- // @private wrapper for the mouseleave event.
- onMouseLeave: function() {
- var me = this;
- me.unHighlightItem();
- if (me.tooltip) {
- me.hideTip();
+ norm = Math.sqrt(1 + a * a);
+ dir = [1 / norm, a / norm];
+ normal = [-dir[1], dir[0]];
+
+ //keep the label always on the outer part of the "elbow"
+ if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
+ normal[0] *= -1;
+ normal[1] *= -1;
+ } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
+ normal[0] *= -1;
+ normal[1] *= -1;
}
- },
- /**
- * For a given x/y point relative to the Surface, find a corresponding item from this
- * series, if any.
- * @param {Number} x
- * @param {Number} y
- * @return {Object} An object describing the item, or null if there is no matching item. The exact contents of
- * this object will vary by series type, but should always contain at least the following:
- *
- * {Ext.chart.series.Series} series - the Series object to which the item belongs
- * {Object} value - the value(s) of the item's data point
- * {Array} point - the x/y coordinates relative to the chart box of a single point
- * for this data item, which can be used as e.g. a tooltip anchor point.
- * {Ext.draw.Sprite} sprite - the item's rendering Sprite.
- *
- */
- getItemForPoint: function(x, y) {
- //if there are no items to query just return null.
- if (!this.items || !this.items.length || this.seriesIsHidden) {
- return null;
+ //position
+ x = cur[0] + normal[0] * offsetFromViz;
+ y = cur[1] + normal[1] * offsetFromViz;
+
+ //box position and dimensions
+ boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
+ boxy = y - bbox.height /2 - offsetBox;
+ boxw = bbox.width + 2 * offsetBox;
+ boxh = bbox.height + 2 * offsetBox;
+
+ //now check if we're out of bounds and invert the normal vector correspondingly
+ //this may add new overlaps between labels (but labels won't be out of bounds).
+ if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
+ normal[0] *= -1;
}
- var me = this,
- items = me.items,
- bbox = me.bbox,
- item, i, ln;
- // Check bounds
- if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
- return null;
+ if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
+ normal[1] *= -1;
}
- for (i = 0, ln = items.length; i < ln; i++) {
- if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
- return items[i];
- }
+
+ //update positions
+ x = cur[0] + normal[0] * offsetFromViz;
+ y = cur[1] + normal[1] * offsetFromViz;
+
+ //update box position and dimensions
+ boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
+ boxy = y - bbox.height /2 - offsetBox;
+ boxw = bbox.width + 2 * offsetBox;
+ boxh = bbox.height + 2 * offsetBox;
+
+ //set the line from the middle of the pie to the box.
+ callout.lines.setAttributes({
+ path: ["M", cur[0], cur[1], "L", x, y, "Z"]
+ }, true);
+ //set box position
+ callout.box.setAttributes({
+ x: boxx,
+ y: boxy,
+ width: boxw,
+ height: boxh
+ }, true);
+ //set text position
+ callout.label.setAttributes({
+ x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
+ y: y
+ }, true);
+ for (p in callout) {
+ callout[p].show(true);
}
-
- return null;
- },
-
- isItemInPoint: function(x, y, item, i) {
- return false;
},
- /**
- * Hides all the elements in the series.
- */
- hideAll: function() {
+ isItemInPoint: function(x, y, item, i) {
var me = this,
- items = me.items,
- item, len, i, sprite;
-
- me.seriesIsHidden = true;
- me._prevShowMarkers = me.showMarkers;
+ pointsUp = item.pointsUp,
+ pointsDown = item.pointsDown,
+ abs = Math.abs,
+ dist = Infinity, p, pln, point;
- me.showMarkers = false;
- //hide all labels
- me.hideLabels(0);
- //hide all sprites
- for (i = 0, len = items.length; i < len; i++) {
- item = items[i];
- sprite = item.sprite;
- if (sprite) {
- sprite.setAttributes({
- hidden: true
- }, true);
+ for (p = 0, pln = pointsUp.length; p < pln; p++) {
+ point = [pointsUp[p][0], pointsUp[p][1]];
+ if (dist > abs(x - point[0])) {
+ dist = abs(x - point[0]);
+ } else {
+ point = pointsUp[p -1];
+ if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
+ item.storeIndex = p -1;
+ item.storeField = me.yField[i];
+ item.storeItem = me.chart.store.getAt(p -1);
+ item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
+ return true;
+ } else {
+ break;
+ }
}
}
+ return false;
},
/**
- * Shows all the elements in the series.
+ * Highlight this entire series.
+ * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
*/
- showAll: function() {
- var me = this,
- prevAnimate = me.chart.animate;
- me.chart.animate = false;
- me.seriesIsHidden = false;
- me.showMarkers = me._prevShowMarkers;
- me.drawSeries();
- me.chart.animate = prevAnimate;
+ highlightSeries: function() {
+ var area, to, fillColor;
+ if (this._index !== undefined) {
+ area = this.areas[this._index];
+ if (area.__highlightAnim) {
+ area.__highlightAnim.paused = true;
+ }
+ area.__highlighted = true;
+ area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
+ area.__prevFill = area.__prevFill || area.attr.fill;
+ area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
+ fillColor = Ext.draw.Color.fromString(area.__prevFill);
+ to = {
+ lineWidth: (area.__prevLineWidth || 0) + 2
+ };
+ if (fillColor) {
+ to.fill = fillColor.getLighter(0.2).toString();
+ }
+ else {
+ to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
+ }
+ if (this.chart.animate) {
+ area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
+ target: area,
+ to: to
+ }, this.chart.animate));
+ }
+ else {
+ area.setAttributes(to, true);
+ }
+ }
},
-
+
/**
- * Returns a string with the color to be used for the series legend item.
+ * UnHighlight this entire series.
+ * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
*/
- getLegendColor: function(index) {
- var me = this, fill, stroke;
- if (me.seriesStyle) {
- fill = me.seriesStyle.fill;
- stroke = me.seriesStyle.stroke;
- if (fill && fill != 'none') {
- return fill;
+ unHighlightSeries: function() {
+ var area;
+ if (this._index !== undefined) {
+ area = this.areas[this._index];
+ if (area.__highlightAnim) {
+ area.__highlightAnim.paused = true;
+ }
+ if (area.__highlighted) {
+ area.__highlighted = false;
+ area.__highlightAnim = Ext.create('Ext.fx.Anim', {
+ target: area,
+ to: {
+ fill: area.__prevFill,
+ opacity: area.__prevOpacity,
+ lineWidth: area.__prevLineWidth
+ }
+ });
}
- return stroke;
}
- return '#000';
},
-
+
/**
- * Checks whether the data field should be visible in the legend
- * @private
- * @param {Number} index The index of the current item
+ * Highlight the specified item. If no item is provided the whole series will be highlighted.
+ * @param item {Object} Info about the item; same format as returned by #getItemForPoint
*/
- visibleInLegend: function(index){
- var excludes = this.__excludes;
- if (excludes) {
- return !excludes[index];
+ highlightItem: function(item) {
+ var me = this,
+ points, path;
+ if (!item) {
+ this.highlightSeries();
+ return;
}
- return !this.seriesIsHidden;
+ points = item._points;
+ path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
+ : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
+ me.highlightSprite.setAttributes({
+ path: path,
+ hidden: false
+ }, true);
},
/**
- * Changes the value of the {@link #title} for the series.
- * Arguments can take two forms:
- *
- * A single String value: this will be used as the new single title for the series (applies
- * to series with only one yField)
- * A numeric index and a String value: this will set the title for a single indexed yField.
- *
- * @param {Number} index
- * @param {String} title
+ * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
+ * @param {Object} item Info about the item; same format as returned by #getItemForPoint
*/
- setTitle: function(index, title) {
- var me = this,
- oldTitle = me.title;
-
- if (Ext.isString(index)) {
- title = index;
- index = 0;
+ unHighlightItem: function(item) {
+ if (!item) {
+ this.unHighlightSeries();
}
- if (Ext.isArray(oldTitle)) {
- oldTitle[index] = title;
- } else {
- me.title = title;
+ if (this.highlightSprite) {
+ this.highlightSprite.hide(true);
}
+ },
- me.fireEvent('titlechange', title, index);
- }
-});
-
-/**
- * @class Ext.chart.series.Cartesian
- * @extends Ext.chart.series.Series
- *
- * Common base class for series implementations which plot values using x/y coordinates.
- *
- */
-Ext.define('Ext.chart.series.Cartesian', {
-
- /* Begin Definitions */
-
- extend: 'Ext.chart.series.Series',
-
- alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
-
- /* End Definitions */
-
- /**
- * The field used to access the x axis value from the items from the data
- * source.
- *
- * @cfg xField
- * @type String
- */
- xField: null,
+ // @private
+ hideAll: function() {
+ if (!isNaN(this._index)) {
+ this.__excludes[this._index] = true;
+ this.areas[this._index].hide(true);
+ this.drawSeries();
+ }
+ },
- /**
- * The field used to access the y-axis value from the items from the data
- * source.
- *
- * @cfg yField
- * @type String
- */
- yField: null,
+ // @private
+ showAll: function() {
+ if (!isNaN(this._index)) {
+ this.__excludes[this._index] = false;
+ this.areas[this._index].show(true);
+ this.drawSeries();
+ }
+ },
/**
- * Indicates which axis the series will bind to
- *
- * @property axis
- * @type String
+ * Returns the color of the series (to be displayed as color for the series legend item).
+ * @param item {Object} Info about the item; same format as returned by #getItemForPoint
*/
- axis: 'left'
+ getLegendColor: function(index) {
+ var me = this;
+ return me.colorArrayStyle[index % me.colorArrayStyle.length];
+ }
});
-
/**
* @class Ext.chart.series.Area
* @extends Ext.chart.series.Cartesian
- *
-
- Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
- As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
- documentation for more information. A typical configuration object for the area series could be:
-
-{@img Ext.chart.series.Area/Ext.chart.series.Area.png Ext.chart.series.Area chart series}
-
- var store = Ext.create('Ext.data.JsonStore', {
- fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- data: [
- {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
- ]
- });
-
- Ext.create('Ext.chart.Chart', {
- renderTo: Ext.getBody(),
- width: 500,
- height: 300,
- store: store,
- axes: [{
- type: 'Numeric',
- grid: true,
- position: 'left',
- fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
- title: 'Sample Values',
- grid: {
- odd: {
- opacity: 1,
- fill: '#ddd',
- stroke: '#bbb',
- 'stroke-width': 1
- }
- },
- minimum: 0,
- adjustMinimumByMajorUnit: 0
- }, {
- type: 'Category',
- position: 'bottom',
- fields: ['name'],
- title: 'Sample Metrics',
- grid: true,
- label: {
- rotate: {
- degrees: 315
- }
- }
- }],
- series: [{
- type: 'area',
- highlight: false,
- axis: 'left',
- xField: 'name',
- yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
- style: {
- opacity: 0.93
- }
- }]
- });
-
-
-
-
- In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
- take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
- and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
- to the style object.
-
-
+ *
+ * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
+ * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
+ * documentation for more information. A typical configuration object for the area series could be:
+ *
+ * @example
+ * var store = Ext.create('Ext.data.JsonStore', {
+ * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
+ * data: [
+ * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
+ * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
+ * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
+ * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
+ * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
+ * ]
+ * });
+ *
+ * Ext.create('Ext.chart.Chart', {
+ * renderTo: Ext.getBody(),
+ * width: 500,
+ * height: 300,
+ * store: store,
+ * axes: [
+ * {
+ * type: 'Numeric',
+ * grid: true,
+ * position: 'left',
+ * fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * title: 'Sample Values',
+ * grid: {
+ * odd: {
+ * opacity: 1,
+ * fill: '#ddd',
+ * stroke: '#bbb',
+ * 'stroke-width': 1
+ * }
+ * },
+ * minimum: 0,
+ * adjustMinimumByMajorUnit: 0
+ * },
+ * {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics',
+ * grid: true,
+ * label: {
+ * rotate: {
+ * degrees: 315
+ * }
+ * }
+ * }
+ * ],
+ * series: [{
+ * type: 'area',
+ * highlight: false,
+ * axis: 'left',
+ * xField: 'name',
+ * yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
+ * style: {
+ * opacity: 0.93
+ * }
+ * }]
+ * });
+ *
+ * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
+ * take the left axis to measure the data in the area series, set as xField (x values) the name field of each element in the store,
+ * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
+ * to the style object.
+ *
* @xtype area
- *
*/
Ext.define('Ext.chart.series.Area', {
/* Begin Definitions */
extend: 'Ext.chart.series.Cartesian',
-
+
alias: 'series.area',
requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
@@ -47002,7 +49439,7 @@ Ext.define('Ext.chart.series.Area', {
stacked: true,
/**
- * @cfg {Object} style
+ * @cfg {Object} style
* Append styling properties to this object for it to override theme properties.
*/
style: {},
@@ -47078,7 +49515,7 @@ Ext.define('Ext.chart.series.Area', {
getBounds: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
areas = [].concat(me.yField),
areasLen = areas.length,
xValues = [],
@@ -47145,8 +49582,8 @@ Ext.define('Ext.chart.series.Area', {
yValues.push(yValue);
}, me);
- xScale = bbox.width / (maxX - minX);
- yScale = bbox.height / (maxY - minY);
+ xScale = bbox.width / ((maxX - minX) || 1);
+ yScale = bbox.height / ((maxY - minY) || 1);
ln = xValues.length;
if ((ln > bbox.width) && me.areas) {
@@ -47171,7 +49608,7 @@ Ext.define('Ext.chart.series.Area', {
getPaths: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
first = true,
bounds = me.getBounds(),
bbox = bounds.bbox,
@@ -47216,7 +49653,7 @@ Ext.define('Ext.chart.series.Area', {
items[areaIndex].pointsUp.push([x, y]);
}
}
-
+
// Close the paths
for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
// Excluded series
@@ -47258,7 +49695,7 @@ Ext.define('Ext.chart.series.Area', {
drawSeries: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
surface = chart.surface,
animate = chart.animate,
group = me.group,
@@ -47273,7 +49710,7 @@ Ext.define('Ext.chart.series.Area', {
if (!store || !store.getCount()) {
return;
}
-
+
paths = me.getPaths();
if (!me.areas) {
@@ -47299,7 +49736,7 @@ Ext.define('Ext.chart.series.Area', {
path = paths.paths[areaIndex];
if (animate) {
//Add renderer to line. There is not a unique record associated with this.
- rendererAttributes = me.renderer(areaElem, false, {
+ rendererAttributes = me.renderer(areaElem, false, {
path: path,
// 'clip-rect': me.clipBox,
fill: colorArrayStyle[areaIndex % colorArrayLength],
@@ -47310,7 +49747,7 @@ Ext.define('Ext.chart.series.Area', {
to: rendererAttributes
});
} else {
- rendererAttributes = me.renderer(areaElem, false, {
+ rendererAttributes = me.renderer(areaElem, false, {
path: path,
// 'clip-rect': me.clipBox,
hidden: false,
@@ -47359,16 +49796,16 @@ Ext.define('Ext.chart.series.Area', {
x = item.point[0],
y = item.point[1],
bb, width, height;
-
+
label.setAttributes({
text: format(storeItem.get(field[index])),
hidden: true
}, true);
-
+
bb = label.getBBox();
width = bb.width / 2;
height = bb.height / 2;
-
+
x = x - width < bbox.x? bbox.x + width : x;
x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
y = y - height < bbox.y? bbox.y + height : y;
@@ -47427,11 +49864,11 @@ Ext.define('Ext.chart.series.Area', {
a = (next[1] - prev[1]) / (next[0] - prev[0]);
aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
anext = (next[1] - cur[1]) / (next[0] - cur[0]);
-
+
norm = Math.sqrt(1 + a * a);
dir = [1 / norm, a / norm];
normal = [-dir[1], dir[0]];
-
+
//keep the label always on the outer part of the "elbow"
if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
normal[0] *= -1;
@@ -47444,13 +49881,13 @@ Ext.define('Ext.chart.series.Area', {
//position
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
//now check if we're out of bounds and invert the normal vector correspondingly
//this may add new overlaps between labels (but labels won't be out of bounds).
if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
@@ -47463,13 +49900,13 @@ Ext.define('Ext.chart.series.Area', {
//update positions
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//update box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
//set the line from the middle of the pie to the box.
callout.lines.setAttributes({
path: ["M", cur[0], cur[1], "L", x, y, "Z"]
@@ -47490,14 +49927,14 @@ Ext.define('Ext.chart.series.Area', {
callout[p].show(true);
}
},
-
+
isItemInPoint: function(x, y, item, i) {
var me = this,
pointsUp = item.pointsUp,
pointsDown = item.pointsDown,
abs = Math.abs,
dist = Infinity, p, pln, point;
-
+
for (p = 0, pln = pointsUp.length; p < pln; p++) {
point = [pointsUp[p][0], pointsUp[p][1]];
if (dist > abs(x - point[0])) {
@@ -47648,19 +50085,18 @@ Ext.define('Ext.chart.series.Area', {
* Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
* A typical configuration object for the bar series could be:
*
- * {@img Ext.chart.series.Bar/Ext.chart.series.Bar.png Ext.chart.series.Bar chart series}
- *
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
+ * { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
+ * { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
+ * { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
+ * { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
@@ -47734,12 +50170,12 @@ Ext.define('Ext.chart.series.Bar', {
* @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
*/
column: false,
-
+
/**
* @cfg style Style properties that will override the theming series styles.
*/
style: {},
-
+
/**
* @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
*/
@@ -47773,7 +50209,7 @@ Ext.define('Ext.chart.series.Bar', {
opacity: 0.8,
color: '#f00'
},
-
+
shadowAttributes: [{
"stroke-width": 6,
"stroke-opacity": 0.05,
@@ -47811,11 +50247,11 @@ Ext.define('Ext.chart.series.Bar', {
// @private sets the bar girth.
getBarGirth: function() {
var me = this,
- store = me.chart.store,
+ store = me.chart.getChartStore(),
column = me.column,
ln = store.getCount(),
gutter = me.gutter / 100;
-
+
return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
},
@@ -47831,7 +50267,7 @@ Ext.define('Ext.chart.series.Bar', {
getBounds: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
bars = [].concat(me.yField),
barsLen = bars.length,
groupBarsLen = barsLen,
@@ -47863,8 +50299,8 @@ Ext.define('Ext.chart.series.Bar', {
axis = chart.axes.get(me.axis);
if (axis) {
out = axis.calcEnds();
- minY = out.from || axis.prevMin;
- maxY = mmax(out.to || axis.prevMax, 0);
+ minY = out.from;
+ maxY = out.to;
}
}
@@ -47874,8 +50310,8 @@ Ext.define('Ext.chart.series.Bar', {
fields: [].concat(me.yField)
});
out = axis.calcEnds();
- minY = out.from || axis.prevMin;
- maxY = mmax(out.to || axis.prevMax, 0);
+ minY = out.from;
+ maxY = out.to;
}
if (!Ext.isNumber(minY)) {
@@ -47932,7 +50368,7 @@ Ext.define('Ext.chart.series.Bar', {
getPaths: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
bounds = me.bounds = me.getBounds(),
items = me.items = [],
gutter = me.gutter / 100,
@@ -47963,14 +50399,14 @@ Ext.define('Ext.chart.series.Bar', {
top = bounds.zero;
totalDim = 0;
totalNegDim = 0;
- hasShadow = false;
+ hasShadow = false;
for (j = 0, counter = 0; j < barsLen; j++) {
// Excluded series
if (me.__excludes && me.__excludes[j]) {
continue;
}
yValue = record.get(bounds.bars[j]);
- height = Math.round((yValue - ((bounds.minY < 0) ? 0 : bounds.minY)) * bounds.scale);
+ height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
barAttr = {
fill: colors[(barsLen > 1 ? j : 0) % colorLength]
};
@@ -48076,7 +50512,7 @@ Ext.define('Ext.chart.series.Bar', {
shadowGroups = me.shadowGroups,
shadowAttributes = me.shadowAttributes,
shadowGroupsLn = shadowGroups.length,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
column = me.column,
items = me.items,
shadows = [],
@@ -48134,7 +50570,7 @@ Ext.define('Ext.chart.series.Bar', {
drawSeries: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
surface = chart.surface,
animate = chart.animate,
stacked = me.stacked,
@@ -48146,11 +50582,11 @@ Ext.define('Ext.chart.series.Bar', {
seriesStyle = me.seriesStyle,
items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
bounds, endSeriesStyle, barAttr, attrs, anim;
-
+
if (!store || !store.getCount()) {
return;
}
-
+
//fill colors are taken from the colors array.
delete seriesStyle.fill;
endSeriesStyle = Ext.apply(seriesStyle, this.style);
@@ -48224,7 +50660,7 @@ Ext.define('Ext.chart.series.Bar', {
}
me.renderLabels();
},
-
+
// @private handled when creating a label.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
@@ -48238,7 +50674,7 @@ Ext.define('Ext.chart.series.Bar', {
group: group
}, endLabelStyle || {}));
},
-
+
// @private callback used when placing a label.
onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
// Determine the label's final position. Starts with the configured preferred value but
@@ -48394,14 +50830,14 @@ Ext.define('Ext.chart.series.Bar', {
sprite.show();
return this.callParent(arguments);
},
-
+
isItemInPoint: function(x, y, item) {
var bbox = item.sprite.getBBox();
return bbox.x <= x && bbox.y <= y
&& (bbox.x + bbox.width) >= x
&& (bbox.y + bbox.height) >= y;
},
-
+
// @private hide all markers
hideAll: function() {
var axes = this.chart.axes;
@@ -48431,7 +50867,7 @@ Ext.define('Ext.chart.series.Bar', {
});
}
},
-
+
/**
* Returns a string with the color to be used for the series legend item.
* @param index
@@ -48439,7 +50875,7 @@ Ext.define('Ext.chart.series.Bar', {
getLegendColor: function(index) {
var me = this,
colorLength = me.colorArrayStyle.length;
-
+
if (me.style && me.style.fill) {
return me.style.fill;
} else {
@@ -48465,50 +50901,33 @@ Ext.define('Ext.chart.series.Bar', {
/**
* @class Ext.chart.series.Column
* @extends Ext.chart.series.Bar
- *
- * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful visualization technique to display quantitative information for different
- * categories that can show some progression (or regression) in the data set.
- * As with all other series, the Column Series must be appended in the *series* Chart array configuration. See the Chart
- * documentation for more information. A typical configuration object for the column series could be:
*
- * {@img Ext.chart.series.Column/Ext.chart.series.Column.png Ext.chart.series.Column chart series}
+ * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
+ * visualization technique to display quantitative information for different categories that can
+ * show some progression (or regression) in the data set. As with all other series, the Column Series
+ * must be appended in the *series* Chart array configuration. See the Chart documentation for more
+ * information. A typical configuration object for the column series could be:
*
- * ## Example
- *
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
+ * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
+ * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
+ * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
+ * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* store: store,
- * axes: [{
- * type: 'Numeric',
- * position: 'bottom',
- * fields: ['data1'],
- * label: {
- * renderer: Ext.util.Format.numberRenderer('0,0')
- * },
- * title: 'Sample Values',
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'Category',
- * position: 'left',
- * fields: ['name'],
- * title: 'Sample Metrics'
- * }],
- * axes: [{
+ * axes: [
+ * {
* type: 'Numeric',
* position: 'left',
* fields: ['data1'],
@@ -48518,13 +50937,16 @@ Ext.define('Ext.chart.series.Bar', {
* title: 'Sample Values',
* grid: true,
* minimum: 0
- * }, {
+ * },
+ * {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics'
- * }],
- * series: [{
+ * }
+ * ],
+ * series: [
+ * {
* type: 'column',
* axis: 'left',
* highlight: true,
@@ -48546,11 +50968,13 @@ Ext.define('Ext.chart.series.Bar', {
* },
* xField: 'name',
* yField: 'data1'
- * }]
+ * }
+ * ]
* });
- *
- * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis, set `highlight` to true so that bars are smoothly highlighted
- * when hovered and bind the `xField` or category field to the data store `name` property and the `yField` as the data1 property of a store element.
+ *
+ * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
+ * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
+ * field to the data store `name` property and the `yField` as the data1 property of a store element.
*/
Ext.define('Ext.chart.series.Column', {
@@ -48584,7 +51008,7 @@ Ext.define('Ext.chart.series.Column', {
* @extends Ext.chart.series.Series
*
* Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
- * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instanciating the
+ * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
* visualization and using the `setValue` method to adjust the value you want.
*
* A chart/series configuration for the Gauge visualization could look like this:
@@ -48634,10 +51058,9 @@ Ext.define('Ext.chart.series.Gauge', {
highlightDuration: 150,
/**
- * @cfg {String} angleField
+ * @cfg {String} angleField (required)
* The store record field name to be used for the pie angles.
* The values bound to this field name must be positive real numbers.
- * This parameter is required.
*/
angleField: false,
@@ -48648,7 +51071,7 @@ Ext.define('Ext.chart.series.Gauge', {
needle: false,
/**
- * @cfg {Boolean|Number} donut
+ * @cfg {Boolean/Number} donut
* Use the entire disk or just a fraction of it for the gauge. Default's false.
*/
donut: false,
@@ -48715,7 +51138,7 @@ Ext.define('Ext.chart.series.Gauge', {
//@private updates some onbefore render parameters.
initialize: function() {
var me = this,
- store = me.chart.substore || me.chart.store;
+ store = me.chart.getChartStore();
//Add yFields to be used in Legend.js
me.yField = [];
if (me.label.field) {
@@ -48816,7 +51239,7 @@ Ext.define('Ext.chart.series.Gauge', {
drawSeries: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
group = me.group,
animate = me.chart.animate,
axis = me.chart.axes.get(0),
@@ -49037,91 +51460,96 @@ Ext.define('Ext.chart.series.Gauge', {
/**
* @class Ext.chart.series.Line
* @extends Ext.chart.series.Cartesian
- *
- * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
+ *
+ * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
* categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
- * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
+ * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information. A typical configuration object for the line series could be:
*
- * {@img Ext.chart.series.Line/Ext.chart.series.Line.png Ext.chart.series.Line chart series}
- *
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
+ * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
+ * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
+ * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
+ * { 'name': 'metric five', 'data1': 4, 'data2': 4, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* store: store,
- * axes: [{
- * type: 'Numeric',
- * position: 'bottom',
- * fields: ['data1'],
- * label: {
- * renderer: Ext.util.Format.numberRenderer('0,0')
- * },
- * title: 'Sample Values',
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'Category',
- * position: 'left',
- * fields: ['name'],
- * title: 'Sample Metrics'
- * }],
- * series: [{
- * type: 'line',
- * highlight: {
- * size: 7,
- * radius: 7
+ * axes: [
+ * {
+ * type: 'Numeric',
+ * position: 'left',
+ * fields: ['data1', 'data2'],
+ * label: {
+ * renderer: Ext.util.Format.numberRenderer('0,0')
+ * },
+ * title: 'Sample Values',
+ * grid: true,
+ * minimum: 0
* },
- * axis: 'left',
- * xField: 'name',
- * yField: 'data1',
- * markerCfg: {
- * type: 'cross',
- * size: 4,
- * radius: 4,
- * 'stroke-width': 0
+ * {
+ * type: 'Category',
+ * position: 'bottom',
+ * fields: ['name'],
+ * title: 'Sample Metrics'
* }
- * }, {
- * type: 'line',
- * highlight: {
- * size: 7,
- * radius: 7
+ * ],
+ * series: [
+ * {
+ * type: 'line',
+ * highlight: {
+ * size: 7,
+ * radius: 7
+ * },
+ * axis: 'left',
+ * xField: 'name',
+ * yField: 'data1',
+ * markerConfig: {
+ * type: 'cross',
+ * size: 4,
+ * radius: 4,
+ * 'stroke-width': 0
+ * }
* },
- * axis: 'left',
- * fill: true,
- * xField: 'name',
- * yField: 'data3',
- * markerCfg: {
- * type: 'circle',
- * size: 4,
- * radius: 4,
- * 'stroke-width': 0
+ * {
+ * type: 'line',
+ * highlight: {
+ * size: 7,
+ * radius: 7
+ * },
+ * axis: 'left',
+ * fill: true,
+ * xField: 'name',
+ * yField: 'data2',
+ * markerConfig: {
+ * type: 'circle',
+ * size: 4,
+ * radius: 4,
+ * 'stroke-width': 0
+ * }
* }
- * }]
+ * ]
* });
- *
- * In this configuration we're adding two series (or lines), one bound to the `data1`
- * property of the store and the other to `data3`. The type for both configurations is
- * `line`. The `xField` for both series is the same, the name propert of the store.
- * Both line series share the same axis, the left axis. You can set particular marker
- * configuration by adding properties onto the markerConfig object. Both series have
- * an object as highlight so that markers animate smoothly to the properties in highlight
- * when hovered. The second series has `fill=true` which means that the line will also
+ *
+ * In this configuration we're adding two series (or lines), one bound to the `data1`
+ * property of the store and the other to `data3`. The type for both configurations is
+ * `line`. The `xField` for both series is the same, the name propert of the store.
+ * Both line series share the same axis, the left axis. You can set particular marker
+ * configuration by adding properties onto the markerConfig object. Both series have
+ * an object as highlight so that markers animate smoothly to the properties in highlight
+ * when hovered. The second series has `fill=true` which means that the line will also
* have an area below it of the same color.
*
- * **Note:** In the series definition remember to explicitly set the axis to bind the
+ * **Note:** In the series definition remember to explicitly set the axis to bind the
* values of the line series to. This can be done by using the `axis` configuration property.
*/
Ext.define('Ext.chart.series.Line', {
@@ -49137,9 +51565,9 @@ Ext.define('Ext.chart.series.Line', {
/* End Definitions */
type: 'line',
-
+
alias: 'series.line',
-
+
/**
* @cfg {String} axis
* The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
@@ -49152,7 +51580,7 @@ Ext.define('Ext.chart.series.Line', {
* The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
*/
selectionTolerance: 20,
-
+
/**
* @cfg {Boolean} showMarkers
* Whether markers should be displayed at the data points along the line. If true,
@@ -49174,17 +51602,31 @@ Ext.define('Ext.chart.series.Line', {
'fill': '#f00'
}
-
+
*/
markerConfig: {},
/**
* @cfg {Object} style
- * An object containing styles for the visualization lines. These styles will override the theme styles.
- * Some options contained within the style object will are described next.
+ * An object containing style properties for the visualization lines and fill.
+ * These styles will override the theme styles. The following are valid style properties:
+ *
+ * - `stroke` - an rgb or hex color string for the background color of the line
+ * - `stroke-width` - the width of the stroke (integer)
+ * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
+ * - `opacity` - the opacity of the line and the fill color (decimal)
+ *
+ * Example usage:
+ *
+ * style: {
+ * stroke: '#00ff00',
+ * 'stroke-width': 10,
+ * fill: '#80A080',
+ * opacity: 0.2
+ * }
*/
style: {},
-
+
/**
* @cfg {Boolean/Number} smooth
* If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
@@ -49204,8 +51646,8 @@ Ext.define('Ext.chart.series.Line', {
/**
* @cfg {Boolean} fill
- * If true, the area below the line will be filled in using the {@link #style.eefill} and
- * {@link #style.opacity} config properties. Defaults to false.
+ * If true, the area below the line will be filled in using the {@link #style eefill} and
+ * {@link #style opacity} config properties. Defaults to false.
*/
fill: false,
@@ -49250,12 +51692,12 @@ Ext.define('Ext.chart.series.Line', {
me.markerGroup = surface.getGroup(me.seriesId + '-markers');
}
if (shadow) {
- for (i = 0, l = this.shadowAttributes.length; i < l; i++) {
+ for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
}
}
},
-
+
// @private makes an average of points when there are more data points than pixels to be rendered.
shrink: function(xValues, yValues, size) {
// Start at the 2nd point...
@@ -49266,7 +51708,7 @@ Ext.define('Ext.chart.series.Line', {
ySum = 0,
xRes = [xValues[0]],
yRes = [yValues[0]];
-
+
for (; i < len; ++i) {
xSum += xValues[i] || 0;
ySum += yValues[i] || 0;
@@ -49289,13 +51731,12 @@ Ext.define('Ext.chart.series.Line', {
drawSeries: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
- surface = chart.surface,
- chartBBox = chart.chartBBox,
+ chartAxes = chart.axes,
+ store = chart.getChartStore(),
+ storeCount = store.getCount(),
+ surface = me.chart.surface,
bbox = {},
group = me.group,
- gutterX = chart.maxGutter[0],
- gutterY = chart.maxGutter[1],
showMarkers = me.showMarkers,
markerGroup = me.markerGroup,
enableShadows = chart.shadow,
@@ -49305,43 +51746,53 @@ Ext.define('Ext.chart.series.Line', {
lnsh = shadowGroups.length,
dummyPath = ["M"],
path = ["M"],
+ renderPath = ["M"],
+ smoothPath = ["M"],
markerIndex = chart.markerIndex,
axes = [].concat(me.axis),
- shadowGroup,
shadowBarAttr,
xValues = [],
+ xValueMap = {},
yValues = [],
- storeIndices = [],
- numericAxis = true,
- axisCount = 0,
+ yValueMap = {},
onbreak = false,
+ storeIndices = [],
markerStyle = me.markerStyle,
- seriesStyle = me.seriesStyle,
- seriesLabelStyle = me.seriesLabelStyle,
+ seriesStyle = me.style,
colorArrayStyle = me.colorArrayStyle,
colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
- posHash = {
- 'left': 'right',
- 'right': 'left',
- 'top': 'bottom',
- 'bottom': 'top'
- },
isNumber = Ext.isNumber,
- seriesIdx = me.seriesIdx, shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
- x, y, prevX, prevY, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
+ seriesIdx = me.seriesIdx,
+ boundAxes = me.getAxesForXAndYFields(),
+ boundXAxis = boundAxes.xAxis,
+ boundYAxis = boundAxes.yAxis,
+ shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
+ x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
- endLineStyle, type, props, firstMarker, count, smoothPath, renderPath;
-
- //if store is empty then there's nothing to draw.
- if (!store || !store.getCount()) {
+ endLineStyle, type, count, items;
+
+ if (me.fireEvent('beforedraw', me) === false) {
return;
}
-
+
+ //if store is empty or the series is excluded in the legend then there's nothing to draw.
+ if (!storeCount || me.seriesIsHidden) {
+ items = this.items;
+ if (items) {
+ for (i = 0, ln = items.length; i < ln; ++i) {
+ if (items[i].sprite) {
+ items[i].sprite.hide(true);
+ }
+ }
+ }
+ return;
+ }
+
//prepare style objects for line and markers
- endMarkerStyle = Ext.apply(markerStyle, me.markerConfig);
+ endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
type = endMarkerStyle.type;
delete endMarkerStyle.type;
- endLineStyle = Ext.apply(seriesStyle, me.style);
+ endLineStyle = seriesStyle;
//if no stroke with is specified force it to 0.5 because this is
//about making *lines*
if (!endLineStyle['stroke-width']) {
@@ -49365,113 +51816,82 @@ Ext.define('Ext.chart.series.Line', {
}, true);
}
}
-
+
me.unHighlightItem();
me.cleanHighlights();
me.setBBox();
bbox = me.bbox;
-
me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
-
- chart.axes.each(function(axis) {
- //only apply position calculations to axes that affect this series
- //this means the axis in the position referred by this series and also
- //the axis in the other coordinate for this series. For example: (left, top|bottom),
- //or (top, left|right), etc.
- if (axis.position == me.axis || axis.position != posHash[me.axis]) {
- axisCount++;
- if (axis.type != 'Numeric') {
- numericAxis = false;
- return;
+ for (i = 0, ln = axes.length; i < ln; i++) {
+ axis = chartAxes.get(axes[i]);
+ if (axis) {
+ ends = axis.calcEnds();
+ if (axis.position == 'top' || axis.position == 'bottom') {
+ minX = ends.from;
+ maxX = ends.to;
}
- numericAxis = (numericAxis && axis.type == 'Numeric');
- if (axis) {
- ends = axis.calcEnds();
- if (axis.position == 'top' || axis.position == 'bottom') {
- minX = ends.from;
- maxX = ends.to;
- }
- else {
- minY = ends.from;
- maxY = ends.to;
- }
+ else {
+ minY = ends.from;
+ maxY = ends.to;
}
}
- });
-
- //If there's only one axis specified for a series, then we set the default type of the other
- //axis to a category axis. So in this case numericAxis, which would be true if both axes affecting
- //the series are numeric should be false.
- if (numericAxis && axisCount == 1) {
- numericAxis = false;
}
-
// If a field was specified without a corresponding axis, create one to get bounds
//only do this for the axis where real values are bound (that's why we check for
//me.axis)
- if (me.xField && !isNumber(minX)) {
- if (me.axis == 'bottom' || me.axis == 'top') {
- axis = Ext.create('Ext.chart.axis.Axis', {
- chart: chart,
- fields: [].concat(me.xField)
- }).calcEnds();
- minX = axis.from;
- maxX = axis.to;
- } else if (numericAxis) {
- axis = Ext.create('Ext.chart.axis.Axis', {
- chart: chart,
- fields: [].concat(me.xField),
- forceMinMax: true
- }).calcEnds();
- minX = axis.from;
- maxX = axis.to;
- }
+ if (me.xField && !isNumber(minX) &&
+ (boundXAxis == 'bottom' || boundXAxis == 'top') &&
+ !chartAxes.get(boundXAxis)) {
+ axis = Ext.create('Ext.chart.axis.Axis', {
+ chart: chart,
+ fields: [].concat(me.xField)
+ }).calcEnds();
+ minX = axis.from;
+ maxX = axis.to;
}
-
- if (me.yField && !isNumber(minY)) {
- if (me.axis == 'right' || me.axis == 'left') {
- axis = Ext.create('Ext.chart.axis.Axis', {
- chart: chart,
- fields: [].concat(me.yField)
- }).calcEnds();
- minY = axis.from;
- maxY = axis.to;
- } else if (numericAxis) {
- axis = Ext.create('Ext.chart.axis.Axis', {
- chart: chart,
- fields: [].concat(me.yField),
- forceMinMax: true
- }).calcEnds();
- minY = axis.from;
- maxY = axis.to;
- }
+ if (me.yField && !isNumber(minY) &&
+ (boundYAxis == 'right' || boundYAxis == 'left') &&
+ !chartAxes.get(boundYAxis)) {
+ axis = Ext.create('Ext.chart.axis.Axis', {
+ chart: chart,
+ fields: [].concat(me.yField)
+ }).calcEnds();
+ minY = axis.from;
+ maxY = axis.to;
}
-
if (isNaN(minX)) {
minX = 0;
- xScale = bbox.width / (store.getCount() - 1);
+ xScale = bbox.width / ((storeCount - 1) || 1);
}
else {
- //In case some person decides to set an axis' minimum and maximum
- //configuration properties to the same value, then fallback the
- //denominator to a > 0 value.
- xScale = bbox.width / ((maxX - minX) || (store.getCount() - 1));
+ xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
}
if (isNaN(minY)) {
minY = 0;
- yScale = bbox.height / (store.getCount() - 1);
- }
+ yScale = bbox.height / ((storeCount - 1) || 1);
+ }
else {
- //In case some person decides to set an axis' minimum and maximum
- //configuration properties to the same value, then fallback the
- //denominator to a > 0 value.
- yScale = bbox.height / ((maxY - minY) || (store.getCount() - 1));
+ yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
}
-
- store.each(function(record, i) {
+
+ // Extract all x and y values from the store
+ me.eachRecord(function(record, i) {
xValue = record.get(me.xField);
+
+ // Ensure a value
+ if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
+ //set as uniform distribution if the axis is a category axis.
+ || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
+ if (xValue in xValueMap) {
+ xValue = xValueMap[xValue];
+ } else {
+ xValue = xValueMap[xValue] = i;
+ }
+ }
+
+ // Filter out values that don't fit within the pan/zoom buffer area
yValue = record.get(me.yField);
//skip undefined values
if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
@@ -49483,20 +51903,15 @@ Ext.define('Ext.chart.series.Line', {
return;
}
// Ensure a value
- if (typeof xValue == 'string' || typeof xValue == 'object'
+ if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
//set as uniform distribution if the axis is a category axis.
- || (me.axis != 'top' && me.axis != 'bottom' && !numericAxis)) {
- xValue = i;
- }
- if (typeof yValue == 'string' || typeof yValue == 'object'
- //set as uniform distribution if the axis is a category axis.
- || (me.axis != 'left' && me.axis != 'right' && !numericAxis)) {
+ || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
yValue = i;
}
storeIndices.push(i);
xValues.push(xValue);
yValues.push(yValue);
- }, me);
+ });
ln = xValues.length;
if (ln > bbox.width) {
@@ -49525,11 +51940,12 @@ Ext.define('Ext.chart.series.Line', {
if (onbreak) {
onbreak = false;
path.push('M');
- }
+ }
path = path.concat([x, y]);
}
if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
firstY = y;
+ firstX = x;
}
// If this is the first line, create a dummypath to animate in from.
if (!me.line || chart.resizing) {
@@ -49564,15 +51980,16 @@ Ext.define('Ext.chart.series.Line', {
group: [group, markerGroup],
x: 0, y: 0,
translate: {
- x: prevX || x,
+ x: +(prevX || x),
y: prevY || (bbox.y + bbox.height / 2)
},
- value: '"' + xValue + ', ' + yValue + '"'
+ value: '"' + xValue + ', ' + yValue + '"',
+ zIndex: 4000
}, endMarkerStyle));
marker._to = {
translate: {
- x: x,
- y: y
+ x: +x,
+ y: +y
}
};
} else {
@@ -49583,12 +52000,12 @@ Ext.define('Ext.chart.series.Line', {
}, true);
marker._to = {
translate: {
- x: x, y: y
+ x: +x,
+ y: +y
}
};
}
}
-
me.items.push({
series: me,
value: [xValue, yValue],
@@ -49599,16 +52016,16 @@ Ext.define('Ext.chart.series.Line', {
prevX = x;
prevY = y;
}
-
+
if (path.length <= 1) {
//nothing to be rendered
- return;
+ return;
}
-
- if (smooth) {
+
+ if (me.smooth) {
smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
}
-
+
renderPath = smooth ? smoothPath : path;
//Correct path if we're animating timeAxis intervals
@@ -49620,7 +52037,7 @@ Ext.define('Ext.chart.series.Line', {
} else {
fromPath = path;
}
-
+
// Only create a line if one doesn't exist.
if (!me.line) {
me.line = surface.add(Ext.apply({
@@ -49629,9 +52046,15 @@ Ext.define('Ext.chart.series.Line', {
path: dummyPath,
stroke: endLineStyle.stroke || endLineStyle.fill
}, endLineStyle || {}));
+
+ if (enableShadows) {
+ me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
+ }
+
//unset fill here (there's always a default fill withing the themes).
me.line.setAttributes({
- fill: 'none'
+ fill: 'none',
+ zIndex: 3000
});
if (!endLineStyle.stroke && colorArrayLength) {
me.line.setAttributes({
@@ -49640,11 +52063,11 @@ Ext.define('Ext.chart.series.Line', {
}
if (enableShadows) {
//create shadows
- shadows = me.line.shadows = [];
+ shadows = me.line.shadows = [];
for (shindex = 0; shindex < lnsh; shindex++) {
shadowBarAttr = shadowAttributes[shindex];
shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
- shadow = chart.surface.add(Ext.apply({}, {
+ shadow = surface.add(Ext.apply({}, {
type: 'path',
group: shadowGroups[shindex]
}, shadowBarAttr));
@@ -49655,8 +52078,8 @@ Ext.define('Ext.chart.series.Line', {
if (me.fill) {
fillPath = renderPath.concat([
["L", x, bbox.y + bbox.height],
- ["L", bbox.x, bbox.y + bbox.height],
- ["L", bbox.x, firstY]
+ ["L", firstX, bbox.y + bbox.height],
+ ["L", firstX, firstY]
]);
if (!me.fillPath) {
me.fillPath = surface.add({
@@ -49679,6 +52102,7 @@ Ext.define('Ext.chart.series.Line', {
});
//fill should not be used here but when drawing the special fill path object
delete rendererAttributes.fill;
+ line.show(true);
if (chart.markerIndex && me.previousPath) {
me.animation = animation = me.onAnimate(line, {
to: rendererAttributes,
@@ -49695,6 +52119,7 @@ Ext.define('Ext.chart.series.Line', {
if (enableShadows) {
shadows = line.shadows;
for(j = 0; j < lnsh; j++) {
+ shadows[j].show(true);
if (chart.markerIndex && me.previousPath) {
me.onAnimate(shadows[j], {
to: { path: renderPath },
@@ -49709,10 +52134,12 @@ Ext.define('Ext.chart.series.Line', {
}
//animate fill path
if (fill) {
+ me.fillPath.show(true);
me.onAnimate(me.fillPath, {
to: Ext.apply({}, {
path: fillPath,
- fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength]
+ fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
+ 'stroke-width': 0
}, endLineStyle || {})
});
}
@@ -49727,13 +52154,18 @@ Ext.define('Ext.chart.series.Line', {
me.onAnimate(item, {
to: Ext.apply(rendererAttributes, endMarkerStyle || {})
});
+ item.show(true);
}
- }
+ }
}
for(; count < markerCount; count++) {
item = markerGroup.getAt(count);
item.hide(true);
}
+// for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
+// item = markerGroup.getAt(i);
+// item.hide(true);
+// }
}
} else {
rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
@@ -49748,13 +52180,15 @@ Ext.define('Ext.chart.series.Line', {
shadows = me.line.shadows;
for(j = 0; j < lnsh; j++) {
shadows[j].setAttributes({
- path: renderPath
+ path: renderPath,
+ hidden: false
}, true);
}
}
if (me.fill) {
me.fillPath.setAttributes({
- path: fillPath
+ path: fillPath,
+ hidden: false
}, true);
}
if (showMarkers) {
@@ -49765,8 +52199,9 @@ Ext.define('Ext.chart.series.Line', {
if (item) {
rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
+ item.show(true);
}
- }
+ }
}
for(; count < markerCount; count++) {
item = markerGroup.getAt(count);
@@ -49785,8 +52220,10 @@ Ext.define('Ext.chart.series.Line', {
}
me.renderLabels();
me.renderCallouts();
+
+ me.fireEvent('draw', me);
},
-
+
// @private called when a label is to be created.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
@@ -49803,7 +52240,7 @@ Ext.define('Ext.chart.series.Line', {
'y': bbox.y + bbox.height / 2
}, endLabelStyle || {}));
},
-
+
// @private called when a label is to be created.
onPlaceLabel: function(label, storeItem, item, i, display, animate) {
var me = this,
@@ -49817,12 +52254,12 @@ Ext.define('Ext.chart.series.Line', {
y = item.point[1],
radius = item.sprite.attr.radius,
bb, width, height;
-
+
label.setAttributes({
text: format(storeItem.get(field)),
hidden: true
}, true);
-
+
if (display == 'rotate') {
label.setAttributes({
'text-anchor': 'start',
@@ -49839,7 +52276,7 @@ Ext.define('Ext.chart.series.Line', {
x = x < bbox.x? bbox.x : x;
x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
y = (y - height < bbox.y)? bbox.y + height : y;
-
+
} else if (display == 'under' || display == 'over') {
//TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
bb = item.sprite.getBBox();
@@ -49855,7 +52292,7 @@ Ext.define('Ext.chart.series.Line', {
y = y - height < bbox.y? bbox.y + height : y;
y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
}
-
+
if (me.chart.animate && !me.chart.resizing) {
label.show(true);
me.onAnimate(label, {
@@ -49869,7 +52306,7 @@ Ext.define('Ext.chart.series.Line', {
x: x,
y: y
}, true);
- if (resizing) {
+ if (resizing && me.animation) {
me.animation.on('afteranimate', function() {
label.show(true);
});
@@ -49883,20 +52320,20 @@ Ext.define('Ext.chart.series.Line', {
highlightItem: function() {
var me = this;
me.callParent(arguments);
- if (this.line && !this.highlighted) {
- if (!('__strokeWidth' in this.line)) {
- this.line.__strokeWidth = this.line.attr['stroke-width'] || 0;
+ if (me.line && !me.highlighted) {
+ if (!('__strokeWidth' in me.line)) {
+ me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
}
- if (this.line.__anim) {
- this.line.__anim.paused = true;
+ if (me.line.__anim) {
+ me.line.__anim.paused = true;
}
- this.line.__anim = Ext.create('Ext.fx.Anim', {
- target: this.line,
+ me.line.__anim = Ext.create('Ext.fx.Anim', {
+ target: me.line,
to: {
- 'stroke-width': this.line.__strokeWidth + 3
+ 'stroke-width': me.line.__strokeWidth + 3
}
});
- this.highlighted = true;
+ me.highlighted = true;
}
},
@@ -49904,14 +52341,14 @@ Ext.define('Ext.chart.series.Line', {
unHighlightItem: function() {
var me = this;
me.callParent(arguments);
- if (this.line && this.highlighted) {
- this.line.__anim = Ext.create('Ext.fx.Anim', {
- target: this.line,
+ if (me.line && me.highlighted) {
+ me.line.__anim = Ext.create('Ext.fx.Anim', {
+ target: me.line,
to: {
- 'stroke-width': this.line.__strokeWidth
+ 'stroke-width': me.line.__strokeWidth
}
});
- this.highlighted = false;
+ me.highlighted = false;
}
},
@@ -49920,7 +52357,7 @@ Ext.define('Ext.chart.series.Line', {
if (!display) {
return;
}
-
+
var me = this,
chart = me.chart,
surface = chart.surface,
@@ -49952,11 +52389,11 @@ Ext.define('Ext.chart.series.Line', {
a = (next[1] - prev[1]) / (next[0] - prev[0]);
aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
anext = (next[1] - cur[1]) / (next[0] - cur[0]);
-
+
norm = Math.sqrt(1 + a * a);
dir = [1 / norm, a / norm];
normal = [-dir[1], dir[0]];
-
+
//keep the label always on the outer part of the "elbow"
if (aprev > 0 && anext < 0 && normal[1] < 0
|| aprev < 0 && anext > 0 && normal[1] > 0) {
@@ -49976,7 +52413,7 @@ Ext.define('Ext.chart.series.Line', {
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
//now check if we're out of bounds and invert the normal vector correspondingly
//this may add new overlaps between labels (but labels won't be out of bounds).
if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
@@ -49989,13 +52426,13 @@ Ext.define('Ext.chart.series.Line', {
//update positions
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//update box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
if (chart.animate) {
//set the line from the middle of the pie to the box.
me.onAnimate(callout.lines, {
@@ -50022,7 +52459,7 @@ Ext.define('Ext.chart.series.Line', {
callout[p].show(true);
}
},
-
+
isItemInPoint: function(x, y, item, i) {
var me = this,
items = me.items,
@@ -50041,10 +52478,10 @@ Ext.define('Ext.chart.series.Line', {
yIntersect,
dist1, dist2, dist, midx, midy,
sqrt = Math.sqrt, abs = Math.abs;
-
+
nextItem = items[i];
prevItem = i && items[i - 1];
-
+
if (i >= ln) {
prevItem = items[ln - 1];
}
@@ -50057,22 +52494,22 @@ Ext.define('Ext.chart.series.Line', {
dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
dist = Math.min(dist1, dist2);
-
+
if (dist <= tolerance) {
return dist == dist1? prevItem : nextItem;
}
return false;
},
-
+
// @private toggle visibility of all series elements (markers, sprites).
toggleAll: function(show) {
var me = this,
i, ln, shadow, shadows;
if (!show) {
- Ext.chart.series.Line.superclass.hideAll.call(me);
+ Ext.chart.series.Cartesian.prototype.hideAll.call(me);
}
else {
- Ext.chart.series.Line.superclass.showAll.call(me);
+ Ext.chart.series.Cartesian.prototype.showAll.call(me);
}
if (me.line) {
me.line.setAttributes({
@@ -50094,43 +52531,43 @@ Ext.define('Ext.chart.series.Line', {
}, true);
}
},
-
+
// @private hide all series elements (markers, sprites).
hideAll: function() {
this.toggleAll(false);
},
-
+
// @private hide all series elements (markers, sprites).
showAll: function() {
this.toggleAll(true);
}
});
+
/**
* @class Ext.chart.series.Pie
* @extends Ext.chart.series.Series
- *
- * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
+ *
+ * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
* categories that also have a meaning as a whole.
- * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
+ * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information. A typical configuration object for the pie series could be:
- *
- * {@img Ext.chart.series.Pie/Ext.chart.series.Pie.png Ext.chart.series.Pie chart series}
*
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
+ * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
+ * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
+ * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
+ * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
- * height: 300,
+ * height: 350,
* animate: true,
* store: store,
* theme: 'Base:gradients',
@@ -50139,22 +52576,22 @@ Ext.define('Ext.chart.series.Line', {
* field: 'data1',
* showInLegend: true,
* tips: {
- * trackMouse: true,
- * width: 140,
- * height: 28,
- * renderer: function(storeItem, item) {
- * //calculate and display percentage on hover
- * var total = 0;
- * store.each(function(rec) {
- * total += rec.get('data1');
- * });
- * this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
- * }
+ * trackMouse: true,
+ * width: 140,
+ * height: 28,
+ * renderer: function(storeItem, item) {
+ * // calculate and display percentage on hover
+ * var total = 0;
+ * store.each(function(rec) {
+ * total += rec.get('data1');
+ * });
+ * this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
+ * }
* },
* highlight: {
- * segment: {
- * margin: 20
- * }
+ * segment: {
+ * margin: 20
+ * }
* },
* label: {
* field: 'name',
@@ -50162,16 +52599,18 @@ Ext.define('Ext.chart.series.Line', {
* contrast: true,
* font: '18px Arial'
* }
- * }]
+ * }]
* });
- *
- * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
- * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
- * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object
- * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated.
- * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family
- * and size through the `font` parameter.
- *
+ *
+ * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
+ * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
+ *
+ * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object
+ * where we set the field name of the store field to be renderer as text for the label. The labels will also be displayed rotated.
+ *
+ * We set `contrast` to `true` to flip the color of the label if it is to similar to the background color. Finally, we set the font family
+ * and size through the `font` parameter.
+ *
* @xtype pie
*/
Ext.define('Ext.chart.series.Pie', {
@@ -50185,7 +52624,7 @@ Ext.define('Ext.chart.series.Pie', {
/* End Definitions */
type: "pie",
-
+
alias: 'series.pie',
rad: Math.PI / 180,
@@ -50197,10 +52636,9 @@ Ext.define('Ext.chart.series.Pie', {
highlightDuration: 150,
/**
- * @cfg {String} angleField
+ * @cfg {String} angleField (required)
* The store record field name to be used for the pie angles.
* The values bound to this field name must be positive real numbers.
- * This parameter is required.
*/
angleField: false,
@@ -50208,12 +52646,11 @@ Ext.define('Ext.chart.series.Pie', {
* @cfg {String} lengthField
* The store record field name to be used for the pie slice lengths.
* The values bound to this field name must be positive real numbers.
- * This parameter is optional.
*/
lengthField: false,
/**
- * @cfg {Boolean|Number} donut
+ * @cfg {Boolean/Number} donut
* Whether to set the pie chart as donut chart.
* Default's false. Can be set to a particular percentage to set the radius
* of the donut chart.
@@ -50230,13 +52667,13 @@ Ext.define('Ext.chart.series.Pie', {
* @cfg {Array} colorSet
* An array of color values which will be used, in order, as the pie slice fill colors.
*/
-
+
/**
* @cfg {Object} style
* An object containing styles for overriding series styles from Theming.
*/
style: {},
-
+
constructor: function(config) {
this.callParent(arguments);
var me = this,
@@ -50251,7 +52688,7 @@ Ext.define('Ext.chart.series.Pie', {
}
}
});
- Ext.apply(me, config, {
+ Ext.apply(me, config, {
shadowAttributes: [{
"stroke-width": 6,
"stroke-opacity": 1,
@@ -50289,12 +52726,13 @@ Ext.define('Ext.chart.series.Pie', {
surface.customAttributes.segment = function(opt) {
return me.getSegment(opt);
};
+ me.__excludes = me.__excludes || [];
},
-
+
//@private updates some onbefore render parameters.
initialize: function() {
var me = this,
- store = me.chart.substore || me.chart.store;
+ store = me.chart.getChartStore();
//Add yFields to be used in Legend.js
me.yField = [];
if (me.label.field) {
@@ -50310,58 +52748,81 @@ Ext.define('Ext.chart.series.Pie', {
rad = me.rad,
cos = Math.cos,
sin = Math.sin,
- abs = Math.abs,
x = me.centerX,
y = me.centerY,
x1 = 0, x2 = 0, x3 = 0, x4 = 0,
y1 = 0, y2 = 0, y3 = 0, y4 = 0,
+ x5 = 0, y5 = 0, x6 = 0, y6 = 0,
delta = 1e-2,
- r = opt.endRho - opt.startRho,
startAngle = opt.startAngle,
endAngle = opt.endAngle,
midAngle = (startAngle + endAngle) / 2 * rad,
margin = opt.margin || 0,
- flag = abs(endAngle - startAngle) > 180,
a1 = Math.min(startAngle, endAngle) * rad,
a2 = Math.max(startAngle, endAngle) * rad,
- singleSlice = false;
-
- x += margin * cos(midAngle);
- y += margin * sin(midAngle);
+ c1 = cos(a1), s1 = sin(a1),
+ c2 = cos(a2), s2 = sin(a2),
+ cm = cos(midAngle), sm = sin(midAngle),
+ flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
- x1 = x + opt.startRho * cos(a1);
- y1 = y + opt.startRho * sin(a1);
+ if (a2 - a1 < delta) {
+ return {path: ""};
+ }
- x2 = x + opt.endRho * cos(a1);
- y2 = y + opt.endRho * sin(a1);
+ if (margin !== 0) {
+ x += margin * cm;
+ y += margin * sm;
+ }
- x3 = x + opt.startRho * cos(a2);
- y3 = y + opt.startRho * sin(a2);
+ x2 = x + opt.endRho * c1;
+ y2 = y + opt.endRho * s1;
- x4 = x + opt.endRho * cos(a2);
- y4 = y + opt.endRho * sin(a2);
+ x4 = x + opt.endRho * c2;
+ y4 = y + opt.endRho * s2;
- if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
- singleSlice = true;
+ if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
+ cm = hsqr2;
+ sm = -hsqr2;
+ flag = 1;
}
- //Solves mysterious clipping bug with IE
- if (singleSlice) {
+
+ x6 = x + opt.endRho * cm;
+ y6 = y + opt.endRho * sm;
+
+ // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
+ // Better fix the VML engine for half circles.
+
+ if (opt.startRho !== 0) {
+ x1 = x + opt.startRho * c1;
+ y1 = y + opt.startRho * s1;
+
+ x3 = x + opt.startRho * c2;
+ y3 = y + opt.startRho * s2;
+
+ x5 = x + opt.startRho * cm;
+ y5 = y + opt.startRho * sm;
+
return {
path: [
- ["M", x1, y1],
- ["L", x2, y2],
- ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
- ["Z"]]
+ ["M", x2, y2],
+ ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
+ ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
+ ["L", x3, y3],
+ ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
+ ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
+ ["Z"]
+ ]
};
} else {
return {
path: [
- ["M", x1, y1],
- ["L", x2, y2],
- ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
- ["L", x3, y3],
- ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
- ["Z"]]
+ ["M", x, y],
+ ["L", x2, y2],
+ ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
+ ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
+ ["L", x, y],
+ ["Z"]
+ ]
};
}
},
@@ -50376,11 +52837,10 @@ Ext.define('Ext.chart.series.Pie', {
startAngle = slice.startAngle,
endAngle = slice.endAngle,
donut = +me.donut,
- a1 = Math.min(startAngle, endAngle) * rad,
- a2 = Math.max(startAngle, endAngle) * rad,
- midAngle = -(a1 + (a2 - a1) / 2),
- xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
- ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
+ midAngle = -(startAngle + endAngle) * rad / 2,
+ r = (item.endRho + item.startRho) / 2,
+ xm = x + r * Math.cos(midAngle),
+ ym = y - r * Math.sin(midAngle);
item.middle = {
x: xm,
@@ -50393,7 +52853,7 @@ Ext.define('Ext.chart.series.Pie', {
*/
drawSeries: function() {
var me = this,
- store = me.chart.substore || me.chart.store,
+ store = me.chart.getChartStore(),
group = me.group,
animate = me.chart.animate,
field = me.angleField || me.field || me.xField,
@@ -50427,6 +52887,7 @@ Ext.define('Ext.chart.series.Pie', {
colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
gutterX = chart.maxGutter[0],
gutterY = chart.maxGutter[1],
+ abs = Math.abs,
rendererAttributes,
shadowGroup,
shadowAttr,
@@ -50454,7 +52915,7 @@ Ext.define('Ext.chart.series.Pie', {
path,
p,
spriteOptions, bbox;
-
+
Ext.apply(seriesStyle, me.style || {});
me.setBBox();
@@ -50465,12 +52926,12 @@ Ext.define('Ext.chart.series.Pie', {
colorArrayStyle = me.colorSet;
colorArrayLength = colorArrayStyle.length;
}
-
+
//if not store or store is empty then there's nothing to draw
if (!store || !store.getCount()) {
return;
}
-
+
me.unHighlightItem();
me.cleanHighlights();
@@ -50495,25 +52956,26 @@ Ext.define('Ext.chart.series.Pie', {
}
}, this);
+ totalField = totalField || 1;
store.each(function(record, i) {
if (this.__excludes && this.__excludes[i]) {
- //hidden series
- return;
- }
- value = record.get(field);
- middleAngle = angle - 360 * value / totalField / 2;
- // TODO - Put up an empty circle
- if (isNaN(middleAngle)) {
- middleAngle = 360;
- value = 1;
- totalField = 1;
+ value = 0;
+ } else {
+ value = record.get(field);
+ if (first == 0) {
+ first = 1;
+ }
}
+
// First slice
- if (!i || first == 0) {
- angle = 360 - middleAngle;
- me.firstAngle = angle;
- middleAngle = angle - 360 * value / totalField / 2;
+ if (first == 1) {
+ first = 2;
+ me.firstAngle = angle = 360 * value / totalField / 2;
+ for (j = 0; j < i; j++) {
+ slices[j].startAngle = slices[j].endAngle = me.firstAngle;
+ }
}
+
endAngle = angle - 360 * value / totalField;
slice = {
series: me,
@@ -50529,20 +52991,11 @@ Ext.define('Ext.chart.series.Pie', {
slice.rho = me.radius;
}
slices[i] = slice;
- if((slice.startAngle % 360) == (slice.endAngle % 360)) {
- slice.startAngle -= 0.0001;
- }
angle = endAngle;
- first++;
}, me);
-
//do all shadows first.
if (enableShadows) {
for (i = 0, ln = slices.length; i < ln; i++) {
- if (this.__excludes && this.__excludes[i]) {
- //hidden series
- continue;
- }
slice = slices[i];
slice.shadowAttrs = [];
for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
@@ -50557,7 +53010,8 @@ Ext.define('Ext.chart.series.Pie', {
rho: slice.rho,
startRho: rhoAcum + (deltaRho * donut / 100),
endRho: rhoAcum + deltaRho
- }
+ },
+ hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
};
//create shadows
for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
@@ -50576,9 +53030,7 @@ Ext.define('Ext.chart.series.Pie', {
to: shadowAttr
});
} else {
- shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply(shadowAttr, {
- hidden: false
- }), i, store);
+ shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
shadow.setAttributes(shadowAttr, true);
}
shadows.push(shadow);
@@ -50589,10 +53041,6 @@ Ext.define('Ext.chart.series.Pie', {
}
//do pie slices after.
for (i = 0, ln = slices.length; i < ln; i++) {
- if (this.__excludes && this.__excludes[i]) {
- //hidden series
- continue;
- }
slice = slices[i];
for (j = 0, rhoAcum = 0; j < layers; j++) {
sprite = group.getAt(i * layers + j);
@@ -50606,7 +53054,8 @@ Ext.define('Ext.chart.series.Pie', {
rho: slice.rho,
startRho: rhoAcum + (deltaRho * donut / 100),
endRho: rhoAcum + deltaRho
- }
+ },
+ hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
}, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
item = Ext.apply({},
rendererAttributes.segment, {
@@ -50657,7 +53106,7 @@ Ext.define('Ext.chart.series.Pie', {
rhoAcum += deltaRho;
}
}
-
+
// Hide unused bars
ln = group.getCount();
for (i = 0; i < ln; i++) {
@@ -50690,7 +53139,7 @@ Ext.define('Ext.chart.series.Pie', {
centerY = me.centerY,
middle = item.middle,
endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
-
+
return me.chart.surface.add(Ext.apply({
'type': 'text',
'text-anchor': 'middle',
@@ -50722,9 +53171,13 @@ Ext.define('Ext.chart.series.Pie', {
theta = Math.atan2(y, x || 1),
dg = theta * 180 / Math.PI,
prevDg;
-
+ if (this.__excludes && this.__excludes[i]) {
+ opt.hidden = true;
+ }
function fixAngle(a) {
- if (a < 0) a += 360;
+ if (a < 0) {
+ a += 360;
+ }
return a % 360;
}
@@ -50768,7 +53221,7 @@ Ext.define('Ext.chart.series.Pie', {
}
//ensure the object has zero translation
opt.translate = {
- x: 0, y: 0
+ x: 0, y: 0
};
if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
me.onAnimate(label, {
@@ -50879,8 +53332,8 @@ Ext.define('Ext.chart.series.Pie', {
startAngle = item.startAngle,
endAngle = item.endAngle,
rho = Math.sqrt(dx * dx + dy * dy),
- angle = Math.atan2(y - cy, x - cx) / me.rad + 360;
-
+ angle = Math.atan2(y - cy, x - cx) / me.rad;
+
// normalize to the same range of angles created by drawSeries
if (angle > me.firstAngle) {
angle -= 360;
@@ -50888,7 +53341,7 @@ Ext.define('Ext.chart.series.Pie', {
return (angle <= startAngle && angle > endAngle
&& rho >= item.startRho && rho <= item.endRho);
},
-
+
// @private hides all elements in the series.
hideAll: function() {
var i, l, shadow, shadows, sh, lsh, sprite;
@@ -50914,7 +53367,7 @@ Ext.define('Ext.chart.series.Pie', {
this.drawSeries();
}
},
-
+
// @private shows all elements in the series.
showAll: function() {
if (!isNaN(this._index)) {
@@ -50931,13 +53384,13 @@ Ext.define('Ext.chart.series.Pie', {
var me = this,
rad = me.rad;
item = item || this.items[this._index];
-
+
//TODO(nico): sometimes in IE itemmouseover is triggered
//twice without triggering itemmouseout in between. This
//fixes the highlighting bug. Eventually, events should be
//changed to trigger one itemmouseout between two itemmouseovers.
this.unHighlightItem();
-
+
if (!item || item.sprite && item.sprite._animating) {
return;
}
@@ -50970,7 +53423,7 @@ Ext.define('Ext.chart.series.Pie', {
if (Math.abs(y) < 1e-10) {
y = 0;
}
-
+
if (animate) {
label.stopAnimation();
label.animate({
@@ -51025,7 +53478,7 @@ Ext.define('Ext.chart.series.Pie', {
},
/**
- * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
+ * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
* @param item {Object} Info about the item; same format as returned by #getItemForPoint
*/
unHighlightItem: function() {
@@ -51112,7 +53565,7 @@ Ext.define('Ext.chart.series.Pie', {
}
me.callParent(arguments);
},
-
+
/**
* Returns the color of the series (to be displayed as color for the series legend item).
* @param item {Object} Info about the item; same format as returned by #getItemForPoint
@@ -51127,25 +53580,25 @@ Ext.define('Ext.chart.series.Pie', {
/**
* @class Ext.chart.series.Radar
* @extends Ext.chart.series.Series
- *
- * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
+ *
+ * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
* a constrained number of categories.
- * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
+ *
+ * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information. A typical configuration object for the radar series could be:
- *
- * {@img Ext.chart.series.Radar/Ext.chart.series.Radar.png Ext.chart.series.Radar chart series}
*
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
+ * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
+ * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
+ * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
+ * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
@@ -51168,7 +53621,7 @@ Ext.define('Ext.chart.series.Pie', {
* showMarkers: true,
* markerConfig: {
* radius: 5,
- * size: 5
+ * size: 5
* },
* style: {
* 'stroke-width': 2,
@@ -51202,13 +53655,15 @@ Ext.define('Ext.chart.series.Pie', {
* 'stroke-width': 2,
* fill: 'none'
* }
- * }]
+ * }]
* });
- *
- * In this configuration we add three series to the chart. Each of these series is bound to the same categories field, `name` but bound to different properties for each category,
- * `data1`, `data2` and `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration for the markers of each series can be set by adding properties onto
- * the markerConfig object. Finally we override some theme styling properties by adding properties to the `style` object.
- *
+ *
+ * In this configuration we add three series to the chart. Each of these series is bound to the same
+ * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
+ * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
+ * for the markers of each series can be set by adding properties onto the markerConfig object.
+ * Finally we override some theme styling properties by adding properties to the `style` object.
+ *
* @xtype radar
*/
Ext.define('Ext.chart.series.Radar', {
@@ -51224,7 +53679,7 @@ Ext.define('Ext.chart.series.Radar', {
type: "radar",
alias: 'series.radar',
-
+
rad: Math.PI / 180,
showInLegend: false,
@@ -51234,7 +53689,7 @@ Ext.define('Ext.chart.series.Radar', {
* An object containing styles for overriding series styles from Theming.
*/
style: {},
-
+
constructor: function(config) {
this.callParent(arguments);
var me = this,
@@ -51250,7 +53705,7 @@ Ext.define('Ext.chart.series.Radar', {
*/
drawSeries: function() {
var me = this,
- store = me.chart.substore || me.chart.store,
+ store = me.chart.getChartStore(),
group = me.group,
sprite,
chart = me.chart,
@@ -51276,18 +53731,18 @@ Ext.define('Ext.chart.series.Radar', {
first = chart.resizing || !me.radar,
axis = chart.axes && chart.axes.get(0),
aggregate = !(axis && axis.maximum);
-
+
me.setBBox();
maxValue = aggregate? 0 : (axis.maximum || 0);
-
+
Ext.apply(seriesStyle, me.style || {});
-
+
//if the store is empty then there's nothing to draw
if (!store || !store.getCount()) {
return;
}
-
+
me.unHighlightItem();
me.cleanHighlights();
@@ -51363,7 +53818,7 @@ Ext.define('Ext.chart.series.Radar', {
me.renderLabels();
me.renderCallouts();
},
-
+
// @private draws the markers for the lines (if any).
drawMarkers: function() {
var me = this,
@@ -51371,15 +53826,15 @@ Ext.define('Ext.chart.series.Radar', {
surface = chart.surface,
markerStyle = Ext.apply({}, me.markerStyle || {}),
endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
- items = me.items,
+ items = me.items,
type = endMarkerStyle.type,
markerGroup = me.markerGroup,
centerX = me.centerX,
centerY = me.centerY,
item, i, l, marker;
-
+
delete endMarkerStyle.type;
-
+
for (i = 0, l = items.length; i < l; i++) {
item = items[i];
marker = markerGroup.getAt(i);
@@ -51424,7 +53879,7 @@ Ext.define('Ext.chart.series.Radar', {
}
}
},
-
+
isItemInPoint: function(x, y, item) {
var point,
tolerance = 10,
@@ -51443,7 +53898,7 @@ Ext.define('Ext.chart.series.Radar', {
centerY = me.centerY,
point = item.point,
endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
-
+
return me.chart.surface.add(Ext.apply({
'type': 'text',
'text-anchor': 'middle',
@@ -51475,14 +53930,14 @@ Ext.define('Ext.chart.series.Radar', {
hidden: true
},
true);
-
+
if (resizing) {
label.setAttributes({
x: centerX,
y: centerY
}, true);
}
-
+
if (animate) {
label.show(true);
me.onAnimate(label, {
@@ -51494,7 +53949,7 @@ Ext.define('Ext.chart.series.Radar', {
}
},
- // @private for toggling (show/hide) series.
+ // @private for toggling (show/hide) series.
toggleAll: function(show) {
var me = this,
i, ln, shadow, shadows;
@@ -51519,18 +53974,18 @@ Ext.define('Ext.chart.series.Radar', {
}
}
},
-
+
// @private hide all elements in the series.
hideAll: function() {
this.toggleAll(false);
this.hideMarkers(0);
},
-
+
// @private show all elements in the series.
showAll: function() {
this.toggleAll(true);
},
-
+
// @private hide all markers that belong to `markerGroup`
hideMarkers: function(index) {
var me = this,
@@ -51546,25 +54001,24 @@ Ext.define('Ext.chart.series.Radar', {
/**
* @class Ext.chart.series.Scatter
* @extends Ext.chart.series.Cartesian
- *
- * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
+ *
+ * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
* These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
- * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
+ * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information on creating charts. A typical configuration object for the scatter could be:
*
- * {@img Ext.chart.series.Scatter/Ext.chart.series.Scatter.png Ext.chart.series.Scatter chart series}
- *
+ * @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
+ * { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
+ * { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
+ * { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
+ * { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
+ * { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
- *
+ *
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
@@ -51574,14 +54028,14 @@ Ext.define('Ext.chart.series.Radar', {
* store: store,
* axes: [{
* type: 'Numeric',
- * position: 'bottom',
- * fields: ['data1', 'data2', 'data3'],
+ * position: 'left',
+ * fields: ['data2', 'data3'],
* title: 'Sample Values',
* grid: true,
* minimum: 0
* }, {
* type: 'Category',
- * position: 'left',
+ * position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics'
* }],
@@ -51603,14 +54057,14 @@ Ext.define('Ext.chart.series.Radar', {
* axis: 'left',
* xField: 'name',
* yField: 'data3'
- * }]
+ * }]
* });
- *
- * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
- * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
- * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
+ *
+ * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
+ * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
+ * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
* axis to show the current values of the elements.
- *
+ *
* @xtype scatter
*/
Ext.define('Ext.chart.series.Scatter', {
@@ -51630,11 +54084,18 @@ Ext.define('Ext.chart.series.Scatter', {
* @cfg {Object} markerConfig
* The display style for the scatter series markers.
*/
-
+
/**
- * @cfg {Object} style
+ * @cfg {Object} style
* Append styling properties to this object for it to override theme properties.
*/
+
+ /**
+ * @cfg {String/Array} axis
+ * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
+ * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
+ * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
+ */
constructor: function(config) {
this.callParent(arguments);
@@ -51670,14 +54131,14 @@ Ext.define('Ext.chart.series.Scatter', {
getBounds: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
axes = [].concat(me.axis),
bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
me.setBBox();
bbox = me.bbox;
- for (i = 0, ln = axes.length; i < ln; i++) {
+ for (i = 0, ln = axes.length; i < ln; i++) {
axis = chart.axes.get(axes[i]);
if (axis) {
ends = axis.calcEnds();
@@ -51722,7 +54183,7 @@ Ext.define('Ext.chart.series.Scatter', {
minY = 0;
maxY = store.getCount() - 1;
yScale = bbox.height / (store.getCount() - 1);
- }
+ }
else {
yScale = bbox.height / (maxY - minY);
}
@@ -51741,7 +54202,7 @@ Ext.define('Ext.chart.series.Scatter', {
var me = this,
chart = me.chart,
enableShadows = chart.shadow,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
group = me.group,
bounds = me.bounds = me.getBounds(),
bbox = me.bbox,
@@ -51769,10 +54230,10 @@ Ext.define('Ext.chart.series.Scatter', {
return;
}
// Ensure a value
- if (typeof xValue == 'string' || typeof xValue == 'object') {
+ if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
xValue = i;
}
- if (typeof yValue == 'string' || typeof yValue == 'object') {
+ if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
yValue = i;
}
x = boxX + (xValue - minX) * xScale;
@@ -51898,7 +54359,7 @@ Ext.define('Ext.chart.series.Scatter', {
drawSeries: function() {
var me = this,
chart = me.chart,
- store = chart.substore || chart.store,
+ store = chart.getChartStore(),
group = me.group,
enableShadows = chart.shadow,
shadowGroups = me.shadowGroups,
@@ -51945,23 +54406,28 @@ Ext.define('Ext.chart.series.Scatter', {
for (shindex = 0; shindex < lnsh; shindex++) {
shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
+ hidden: false,
translate: {
x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
- }
+ }
}, shadowAttribute), i, store);
me.onAnimate(shadows[shindex], { to: rendererAttributes });
}
}
else {
- rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply({ translate: attr }, { hidden: false }), i, store);
+ rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
+ sprite._to = rendererAttributes;
sprite.setAttributes(rendererAttributes, true);
- //update shadows
+ //animate shadows
for (shindex = 0; shindex < lnsh; shindex++) {
- shadowAttribute = shadowAttributes[shindex];
- rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({
- x: attr.x,
- y: attr.y
+ shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
+ rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
+ hidden: false,
+ translate: {
+ x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
+ y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
+ }
}, shadowAttribute), i, store);
shadows[shindex].setAttributes(rendererAttributes, true);
}
@@ -51977,7 +54443,7 @@ Ext.define('Ext.chart.series.Scatter', {
me.renderLabels();
me.renderCallouts();
},
-
+
// @private callback for when creating a label sprite.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
@@ -51985,7 +54451,7 @@ Ext.define('Ext.chart.series.Scatter', {
config = me.label,
endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
bbox = me.bbox;
-
+
return me.chart.surface.add(Ext.apply({
type: 'text',
group: group,
@@ -51993,7 +54459,7 @@ Ext.define('Ext.chart.series.Scatter', {
y: bbox.y + bbox.height / 2
}, endLabelStyle));
},
-
+
// @private callback for when placing a label sprite.
onPlaceLabel: function(label, storeItem, item, i, display, animate) {
var me = this,
@@ -52007,12 +54473,12 @@ Ext.define('Ext.chart.series.Scatter', {
y = item.point[1],
radius = item.sprite.attr.radius,
bb, width, height, anim;
-
+
label.setAttributes({
text: format(storeItem.get(field)),
hidden: true
}, true);
-
+
if (display == 'rotate') {
label.setAttributes({
'text-anchor': 'start',
@@ -52029,7 +54495,7 @@ Ext.define('Ext.chart.series.Scatter', {
x = x < bbox.x? bbox.x : x;
x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
y = (y - height < bbox.y)? bbox.y + height : y;
-
+
} else if (display == 'under' || display == 'over') {
//TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
bb = item.sprite.getBBox();
@@ -52063,7 +54529,7 @@ Ext.define('Ext.chart.series.Scatter', {
y: y
}, true);
label.show(true);
- });
+ });
}
else {
label.show(true);
@@ -52079,8 +54545,8 @@ Ext.define('Ext.chart.series.Scatter', {
}
}
},
-
- // @private callback for when placing a callout sprite.
+
+ // @private callback for when placing a callout sprite.
onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
var me = this,
chart = me.chart,
@@ -52097,18 +54563,18 @@ Ext.define('Ext.chart.series.Scatter', {
boxx, boxy, boxw, boxh,
p, clipRect = me.bbox,
x, y;
-
+
//position
normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
//now check if we're out of bounds and invert the normal vector correspondingly
//this may add new overlaps between labels (but labels won't be out of bounds).
if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
@@ -52117,17 +54583,17 @@ Ext.define('Ext.chart.series.Scatter', {
if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
normal[1] *= -1;
}
-
+
//update positions
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
-
+
//update box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
-
+
if (chart.animate) {
//set the line from the middle of the pie to the box.
me.onAnimate(callout.lines, {
@@ -52388,8 +54854,7 @@ Ext.define('Ext.chart.theme.Base', {
*
* An object literal of this form could also be used as the {@link #data} config option.
*
- * **Note:** Although not listed here, this class accepts all of the configuration options of
- * **{@link Ext.data.reader.Array ArrayReader}**.
+ * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
*/
Ext.define('Ext.data.ArrayStore', {
extend: 'Ext.data.Store',
@@ -52433,75 +54898,68 @@ Ext.define('Ext.data.ArrayStore', {
/**
* @author Ed Spencer
* @class Ext.data.Batch
- *
+ *
* Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
* after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
* event if any of the Operations encounter an exception.
- *
+ *
* Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes
- *
+ *
*/
Ext.define('Ext.data.Batch', {
mixins: {
observable: 'Ext.util.Observable'
},
-
+
/**
- * True to immediately start processing the batch as soon as it is constructed (defaults to false)
- * @property autoStart
- * @type Boolean
+ * @property {Boolean} autoStart
+ * True to immediately start processing the batch as soon as it is constructed.
*/
autoStart: false,
-
+
/**
+ * @property {Number} current
* The index of the current operation being executed
- * @property current
- * @type Number
*/
current: -1,
-
+
/**
+ * @property {Number} total
* The total number of operations in this batch. Read only
- * @property total
- * @type Number
*/
total: 0,
-
+
/**
+ * @property {Boolean} isRunning
* True if the batch is currently running
- * @property isRunning
- * @type Boolean
*/
isRunning: false,
-
+
/**
+ * @property {Boolean} isComplete
* True if this batch has been executed completely
- * @property isComplete
- * @type Boolean
*/
isComplete: false,
-
+
/**
+ * @property {Boolean} hasException
* True if this batch has encountered an exception. This is cleared at the start of each operation
- * @property hasException
- * @type Boolean
*/
hasException: false,
-
+
/**
- * True to automatically pause the execution of the batch if any operation encounters an exception (defaults to true)
- * @property pauseOnException
- * @type Boolean
+ * @property {Boolean} pauseOnException
+ * True to automatically pause the execution of the batch if any operation encounters an exception
*/
pauseOnException: true,
-
+
/**
* Creates new Batch object.
- * @param {Object} config (optional) Config object
+ * @param {Object} [config] Config object
*/
- constructor: function(config) {
+ constructor: function(config) {
var me = this;
-
+
me.addEvents(
/**
* @event complete
@@ -52510,7 +54968,7 @@ Ext.define('Ext.data.Batch', {
* @param {Object} operation The last operation that was executed
*/
'complete',
-
+
/**
* @event exception
* Fired when a operation encountered an exception
@@ -52518,7 +54976,7 @@ Ext.define('Ext.data.Batch', {
* @param {Object} operation The operation that encountered the exception
*/
'exception',
-
+
/**
* @event operationcomplete
* Fired when each operation of the batch completes
@@ -52527,29 +54985,28 @@ Ext.define('Ext.data.Batch', {
*/
'operationcomplete'
);
-
+
me.mixins.observable.constructor.call(me, config);
-
+
/**
* Ordered array of operations that will be executed by this batch
- * @property operations
- * @type Array
+ * @property {Ext.data.Operation[]} operations
*/
me.operations = [];
},
-
+
/**
* Adds a new operation to this batch
* @param {Object} operation The {@link Ext.data.Operation Operation} object
*/
add: function(operation) {
this.total++;
-
+
operation.setBatch(this);
-
+
this.operations.push(operation);
},
-
+
/**
* Kicks off the execution of the batch, continuing from the next operation if the previous
* operation encountered an exception, or if execution was paused
@@ -52557,10 +55014,10 @@ Ext.define('Ext.data.Batch', {
start: function() {
this.hasException = false;
this.isRunning = true;
-
+
this.runNextOperation();
},
-
+
/**
* @private
* Runs the next operation, relative to this.current.
@@ -52568,14 +55025,14 @@ Ext.define('Ext.data.Batch', {
runNextOperation: function() {
this.runOperation(this.current + 1);
},
-
+
/**
* Pauses execution of the batch, but does not cancel the current operation
*/
pause: function() {
this.isRunning = false;
},
-
+
/**
* Executes a operation by its numeric index
* @param {Number} index The operation index to run
@@ -52585,17 +55042,17 @@ Ext.define('Ext.data.Batch', {
operations = me.operations,
operation = operations[index],
onProxyReturn;
-
+
if (operation === undefined) {
me.isRunning = false;
me.isComplete = true;
me.fireEvent('complete', me, operations[operations.length - 1]);
} else {
me.current = index;
-
+
onProxyReturn = function(operation) {
var hasException = operation.hasException();
-
+
if (hasException) {
me.hasException = true;
me.fireEvent('exception', me, operation);
@@ -52610,9 +55067,9 @@ Ext.define('Ext.data.Batch', {
me.runNextOperation();
}
};
-
+
operation.setStarted();
-
+
me.proxy[operation.action](operation, onProxyReturn, me);
}
}
@@ -52622,117 +55079,110 @@ Ext.define('Ext.data.Batch', {
* @class Ext.data.BelongsToAssociation
* @extends Ext.data.Association
*
- * Represents a many to one association with another model. The owner model is expected to have
- * a foreign key which references the primary key of the associated model:
+ * Represents a many to one association with another model. The owner model is expected to have
+ * a foreign key which references the primary key of the associated model:
*
-
-Ext.define('Category', {
- extend: 'Ext.data.Model',
- fields: [
- {name: 'id', type: 'int'},
- {name: 'name', type: 'string'}
- ]
-});
-
-Ext.define('Product', {
- extend: 'Ext.data.Model',
- fields: [
- {name: 'id', type: 'int'},
- {name: 'category_id', type: 'int'},
- {name: 'name', type: 'string'}
- ],
- // we can use the belongsTo shortcut on the model to create a belongsTo association
- belongsTo: {type: 'belongsTo', model: 'Category'}
-});
-
- * In the example above we have created models for Products and Categories, and linked them together
+ * Ext.define('Category', {
+ * extend: 'Ext.data.Model',
+ * fields: [
+ * { name: 'id', type: 'int' },
+ * { name: 'name', type: 'string' }
+ * ]
+ * });
+ *
+ * Ext.define('Product', {
+ * extend: 'Ext.data.Model',
+ * fields: [
+ * { name: 'id', type: 'int' },
+ * { name: 'category_id', type: 'int' },
+ * { name: 'name', type: 'string' }
+ * ],
+ * // we can use the belongsTo shortcut on the model to create a belongsTo association
+ * associations: [
+ * { type: 'belongsTo', model: 'Category' }
+ * ]
+ * });
+ *
+ * In the example above we have created models for Products and Categories, and linked them together
* by saying that each Product belongs to a Category. This automatically links each Product to a Category
- * based on the Product's category_id, and provides new functions on the Product model:
+ * based on the Product's category_id, and provides new functions on the Product model:
*
- * Generated getter function
+ * ## Generated getter function
*
- * The first function that is added to the owner model is a getter function:
+ * The first function that is added to the owner model is a getter function:
*
-
-var product = new Product({
- id: 100,
- category_id: 20,
- name: 'Sneakers'
-});
-
-product.getCategory(function(category, operation) {
- //do something with the category object
- alert(category.get('id')); //alerts 20
-}, this);
-
-*
- * The getCategory function was created on the Product model when we defined the association. This uses the
+ * var product = new Product({
+ * id: 100,
+ * category_id: 20,
+ * name: 'Sneakers'
+ * });
+ *
+ * product.getCategory(function(category, operation) {
+ * // do something with the category object
+ * alert(category.get('id')); // alerts 20
+ * }, this);
+ *
+ * The getCategory function was created on the Product model when we defined the association. This uses the
* Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
- * callback when it has loaded.
+ * callback when it has loaded.
*
- * The new getCategory function will also accept an object containing success, failure and callback properties
+ * The new getCategory function will also accept an object containing success, failure and callback properties
* - callback will always be called, success will only be called if the associated model was loaded successfully
- * and failure will only be called if the associatied model could not be loaded:
+ * and failure will only be called if the associatied model could not be loaded:
*
-
-product.getCategory({
- callback: function(category, operation) {}, //a function that will always be called
- success : function(category, operation) {}, //a function that will only be called if the load succeeded
- failure : function(category, operation) {}, //a function that will only be called if the load did not succeed
- scope : this //optionally pass in a scope object to execute the callbacks in
-});
-
+ * product.getCategory({
+ * callback: function(category, operation) {}, // a function that will always be called
+ * success : function(category, operation) {}, // a function that will only be called if the load succeeded
+ * failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
+ * scope : this // optionally pass in a scope object to execute the callbacks in
+ * });
*
- * In each case above the callbacks are called with two arguments - the associated model instance and the
+ * In each case above the callbacks are called with two arguments - the associated model instance and the
* {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
- * useful when the instance could not be loaded.
+ * useful when the instance could not be loaded.
*
- * Generated setter function
+ * ## Generated setter function
*
- * The second generated function sets the associated model instance - if only a single argument is passed to
- * the setter then the following two calls are identical:
+ * The second generated function sets the associated model instance - if only a single argument is passed to
+ * the setter then the following two calls are identical:
*
-
-//this call
-product.setCategory(10);
-
-//is equivalent to this call:
-product.set('category_id', 10);
-
- * If we pass in a second argument, the model will be automatically saved and the second argument passed to
- * the owner model's {@link Ext.data.Model#save save} method:
-
-product.setCategory(10, function(product, operation) {
- //the product has been saved
- alert(product.get('category_id')); //now alerts 10
-});
-
-//alternative syntax:
-product.setCategory(10, {
- callback: function(product, operation), //a function that will always be called
- success : function(product, operation), //a function that will only be called if the load succeeded
- failure : function(product, operation), //a function that will only be called if the load did not succeed
- scope : this //optionally pass in a scope object to execute the callbacks in
-})
-
-*
- * Customisation
+ * // this call...
+ * product.setCategory(10);
*
- * Associations reflect on the models they are linking to automatically set up properties such as the
- * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
+ * // is equivalent to this call:
+ * product.set('category_id', 10);
*
-
-Ext.define('Product', {
- fields: [...],
-
- associations: [
- {type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id'}
- ]
-});
-
+ * If we pass in a second argument, the model will be automatically saved and the second argument passed to
+ * the owner model's {@link Ext.data.Model#save save} method:
+ *
+ * product.setCategory(10, function(product, operation) {
+ * // the product has been saved
+ * alert(product.get('category_id')); //now alerts 10
+ * });
*
- * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
- * with our own settings. Usually this will not be needed.
+ * //alternative syntax:
+ * product.setCategory(10, {
+ * callback: function(product, operation), // a function that will always be called
+ * success : function(product, operation), // a function that will only be called if the load succeeded
+ * failure : function(product, operation), // a function that will only be called if the load did not succeed
+ * scope : this //optionally pass in a scope object to execute the callbacks in
+ * })
+ *
+ * ## Customisation
+ *
+ * Associations reflect on the models they are linking to automatically set up properties such as the
+ * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
+ *
+ * Ext.define('Product', {
+ * fields: [...],
+ *
+ * associations: [
+ * { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
+ * ]
+ * });
+ *
+ * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
+ * with our own settings. Usually this will not be needed.
*/
Ext.define('Ext.data.BelongsToAssociation', {
extend: 'Ext.data.Association',
@@ -52743,26 +55193,25 @@ Ext.define('Ext.data.BelongsToAssociation', {
* @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
* model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
* model called Product would set up a product_id foreign key.
- *
-Ext.define('Order', {
- extend: 'Ext.data.Model',
- fields: ['id', 'date'],
- hasMany: 'Product'
-});
-
-Ext.define('Product', {
- extend: 'Ext.data.Model',
- fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
- belongsTo: 'Group'
-});
-var product = new Product({
- id: 1,
- name: 'Product 1',
- order_id: 22
-}, 1);
-product.getOrder(); // Will make a call to the server asking for order_id 22
-
- *
+ *
+ * Ext.define('Order', {
+ * extend: 'Ext.data.Model',
+ * fields: ['id', 'date'],
+ * hasMany: 'Product'
+ * });
+ *
+ * Ext.define('Product', {
+ * extend: 'Ext.data.Model',
+ * fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
+ * belongsTo: 'Group'
+ * });
+ * var product = new Product({
+ * id: 1,
+ * name: 'Product 1',
+ * order_id: 22
+ * }, 1);
+ * product.getOrder(); // Will make a call to the server asking for order_id 22
+ *
*/
/**
@@ -52774,18 +55223,16 @@ product.getOrder(); // Will make a call to the server asking for order_id 22
* @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
* Defaults to 'set' + the name of the foreign model, e.g. setCategory
*/
-
+
/**
* @cfg {String} type The type configuration can be used when creating associations using a configuration object.
* Use 'belongsTo' to create a HasManyAssocation
- *
-associations: [{
- type: 'belongsTo',
- model: 'User'
-}]
- *
+ *
+ * associations: [{
+ * type: 'belongsTo',
+ * model: 'User'
+ * }]
*/
-
constructor: function(config) {
this.callParent(arguments);
@@ -52877,7 +55324,7 @@ associations: [{
instance = model[instanceName];
args = [instance];
scope = scope || model;
-
+
//TODO: We're duplicating the callback invokation code that the instance.load() call above
//makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
//instead of the association layer.
@@ -52919,73 +55366,62 @@ Ext.define('Ext.data.BufferStore', {
}
});
/**
- * @class Ext.direct.Manager
- * Overview
+ * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
+ * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
+ * error conditions, etc).
*
- * Ext.Direct aims to streamline communication between the client and server
- * by providing a single interface that reduces the amount of common code
- * typically required to validate data and handle returned data packets
- * (reading data, error conditions, etc).
+ * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
+ * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
*
- * The Ext.direct namespace includes several classes for a closer integration
- * with the server-side. The Ext.data namespace also includes classes for working
- * with Ext.data.Stores which are backed by data from an Ext.Direct method.
+ * # Specification
*
- * Specification
+ * For additional information consult the [Ext.Direct Specification][1].
*
- * For additional information consult the
- * Ext.Direct Specification .
+ * # Providers
*
- * Providers
+ * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
+ * server. There are several providers that exist in the core at the moment:
*
- * Ext.Direct uses a provider architecture, where one or more providers are
- * used to transport data to and from the server. There are several providers
- * that exist in the core at the moment:
+ * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
+ * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
+ * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
*
- * {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
- * {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
- * {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side
- * on the client.
- *
+ * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
*
- * A provider does not need to be invoked directly, providers are added via
- * {@link Ext.direct.Manager}.{@link Ext.direct.Manager#addProvider addProvider}.
+ * # Router
*
- * Router
+ * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
+ * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
+ * solution and replace it with one that uses C# without changing the client side JavaScript at all.
*
- * Ext.Direct utilizes a "router" on the server to direct requests from the client
- * to the appropriate server-side method. Because the Ext.Direct API is completely
- * platform-agnostic, you could completely swap out a Java based server solution
- * and replace it with one that uses C# without changing the client side JavaScript
- * at all.
+ * # Server side events
*
- * Server side events
+ * Custom events from the server may be handled by the client by adding listeners, for example:
+ *
+ * {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
+ *
+ * // add a handler for a 'message' event sent by the server
+ * Ext.direct.Manager.on('message', function(e){
+ * out.append(String.format('{0}
', e.data));
+ * out.el.scrollTo('t', 100000, true);
+ * });
+ *
+ * [1]: http://sencha.com/products/extjs/extdirect
*
- * Custom events from the server may be handled by the client by adding
- * listeners, for example:
- *
-{"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
-
-// add a handler for a 'message' event sent by the server
-Ext.direct.Manager.on('message', function(e){
- out.append(String.format('<p><i>{0}</i></p>', e.data));
- out.el.scrollTo('t', 100000, true);
-});
- *
* @singleton
+ * @alternateClassName Ext.Direct
*/
-
Ext.define('Ext.direct.Manager', {
-
+
/* Begin Definitions */
singleton: true,
-
+
mixins: {
observable: 'Ext.util.Observable'
},
-
+
requires: ['Ext.util.MixedCollection'],
-
+
statics: {
exceptions: {
TRANSPORT: 'xhr',
@@ -52994,74 +55430,75 @@ Ext.define('Ext.direct.Manager', {
SERVER: 'exception'
}
},
-
+
/* End Definitions */
-
+
constructor: function(){
var me = this;
-
+
me.addEvents(
/**
* @event event
* Fires after an event.
- * @param {event} e The Ext.direct.Event type that occurred.
+ * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
* @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
*/
'event',
/**
* @event exception
* Fires after an event exception.
- * @param {event} e The Ext.direct.Event type that occurred.
+ * @param {Ext.direct.Event} e The event type that occurred.
*/
'exception'
);
me.transactions = Ext.create('Ext.util.MixedCollection');
me.providers = Ext.create('Ext.util.MixedCollection');
-
+
me.mixins.observable.constructor.call(me);
},
-
- /**
- * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods.
- * If the provider is not already connected, it will auto-connect.
- *
-var pollProv = new Ext.direct.PollingProvider({
- url: 'php/poll2.php'
-});
-Ext.direct.Manager.addProvider({
- "type":"remoting", // create a {@link Ext.direct.RemotingProvider}
- "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
- "actions":{ // each property within the actions object represents a Class
- "TestAction":[ // array of methods within each server side Class
- {
- "name":"doEcho", // name of method
- "len":1
- },{
- "name":"multiply",
- "len":1
- },{
- "name":"doForm",
- "formHandler":true, // handle form on server with Ext.Direct.Transaction
- "len":1
- }]
- },
- "namespace":"myApplication",// namespace to create the Remoting Provider in
-},{
- type: 'polling', // create a {@link Ext.direct.PollingProvider}
- url: 'php/poll.php'
-}, pollProv); // reference to previously created instance
- *
- * @param {Object/Array} provider Accepts either an Array of Provider descriptions (an instance
- * or config object for a Provider) or any number of Provider descriptions as arguments. Each
- * Provider description instructs Ext.Direct how to create client-side stub methods.
+ /**
+ * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
+ * is not already connected, it will auto-connect.
+ *
+ * var pollProv = new Ext.direct.PollingProvider({
+ * url: 'php/poll2.php'
+ * });
+ *
+ * Ext.direct.Manager.addProvider({
+ * "type":"remoting", // create a {@link Ext.direct.RemotingProvider}
+ * "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
+ * "actions":{ // each property within the actions object represents a Class
+ * "TestAction":[ // array of methods within each server side Class
+ * {
+ * "name":"doEcho", // name of method
+ * "len":1
+ * },{
+ * "name":"multiply",
+ * "len":1
+ * },{
+ * "name":"doForm",
+ * "formHandler":true, // handle form on server with Ext.Direct.Transaction
+ * "len":1
+ * }]
+ * },
+ * "namespace":"myApplication",// namespace to create the Remoting Provider in
+ * },{
+ * type: 'polling', // create a {@link Ext.direct.PollingProvider}
+ * url: 'php/poll.php'
+ * }, pollProv); // reference to previously created instance
+ *
+ * @param {Ext.direct.Provider/Object...} provider
+ * Accepts any number of Provider descriptions (an instance or config object for
+ * a Provider). Each Provider description instructs Ext.Directhow to create
+ * client-side stub methods.
*/
addProvider : function(provider){
var me = this,
args = arguments,
i = 0,
len;
-
+
if (args.length > 1) {
for (len = args.length; i < len; ++i) {
me.addProvider(args[i]);
@@ -53083,17 +55520,16 @@ Ext.direct.Manager.addProvider({
return provider;
},
-
+
/**
- * Retrieve a {@link Ext.direct.Provider provider} by the
- * {@link Ext.direct.Provider#id id} specified when the provider is
- * {@link #addProvider added}.
- * @param {String/Ext.data.Provider} id The id of the provider, or the provider instance.
+ * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
+ * provider is {@link #addProvider added}.
+ * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
*/
getProvider : function(id){
return id.isProvider ? id : this.providers.get(id);
},
-
+
/**
* Removes the provider.
* @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
@@ -53101,9 +55537,10 @@ Ext.direct.Manager.addProvider({
*/
removeProvider : function(provider){
var me = this,
- providers = me.providers,
- provider = provider.isProvider ? provider : providers.get(provider);
-
+ providers = me.providers;
+
+ provider = provider.isProvider ? provider : providers.get(provider);
+
if (provider) {
provider.un('data', me.onProviderData, me);
providers.remove(provider);
@@ -53111,9 +55548,9 @@ Ext.direct.Manager.addProvider({
}
return null;
},
-
+
/**
- * Add a transaction to the manager.
+ * Adds a transaction to the manager.
* @private
* @param {Ext.direct.Transaction} transaction The transaction to add
* @return {Ext.direct.Transaction} transaction
@@ -53124,7 +55561,7 @@ Ext.direct.Manager.addProvider({
},
/**
- * Remove a transaction from the manager.
+ * Removes a transaction from the manager.
* @private
* @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
* @return {Ext.direct.Transaction} transaction
@@ -53144,12 +55581,12 @@ Ext.direct.Manager.addProvider({
getTransaction: function(transaction){
return transaction.isTransaction ? transaction : this.transactions.get(transaction);
},
-
+
onProviderData : function(provider, event){
var me = this,
i = 0,
len;
-
+
if (Ext.isArray(event)) {
for (len = event.length; i < len; ++i) {
me.onProviderData(provider, event[i]);
@@ -53158,7 +55595,7 @@ Ext.direct.Manager.addProvider({
}
if (event.name && event.name != 'event' && event.name != 'exception') {
me.fireEvent(event.name, event);
- } else if (event.type == 'exception') {
+ } else if (event.status === false) {
me.fireEvent('exception', event);
}
me.fireEvent('event', event, provider);
@@ -53169,27 +55606,26 @@ Ext.direct.Manager.addProvider({
});
/**
- * @class Ext.data.proxy.Direct
- * @extends Ext.data.proxy.Server
- *
- * This class is used to send requests to the server using {@link Ext.direct}. When a request is made,
- * the transport mechanism is handed off to the appropriate {@link Ext.direct.RemotingProvider Provider}
- * to complete the call.
- *
- * ## Specifying the function
+ * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
+ * request is made, the transport mechanism is handed off to the appropriate
+ * {@link Ext.direct.RemotingProvider Provider} to complete the call.
+ *
+ * # Specifying the function
+ *
* This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
* This can be done by specifying the {@link #directFn} configuration. This will use the same direct
* method for all requests. Alternatively, you can provide an {@link #api} configuration. This
* allows you to specify a different remoting method for each CRUD action.
- *
- * ## Parameters
+ *
+ * # Parameters
+ *
* This proxy provides options to help configure which parameters will be sent to the server.
* By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
* of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
* the remoting method parameters are passed.
- *
- * ## Example Usage
- *
+ *
+ * # Example Usage
+ *
* Ext.define('User', {
* extend: 'Ext.data.Model',
* fields: ['firstName', 'lastName'],
@@ -53203,34 +55639,34 @@ Ext.direct.Manager.addProvider({
*/
Ext.define('Ext.data.proxy.Direct', {
/* Begin Definitions */
-
+
extend: 'Ext.data.proxy.Server',
alternateClassName: 'Ext.data.DirectProxy',
-
+
alias: 'proxy.direct',
-
+
requires: ['Ext.direct.Manager'],
-
+
/* End Definitions */
-
- /**
- * @cfg {Array/String} paramOrder Defaults to undefined . A list of params to be executed
- * server side. Specify the params in the order in which they must be executed on the server-side
- * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
- * comma, or pipe. For example,
- * any of the following would be acceptable:
-paramOrder: ['param1','param2','param3']
-paramOrder: 'param1 param2 param3'
-paramOrder: 'param1,param2,param3'
-paramOrder: 'param1|param2|param'
-
+
+ /**
+ * @cfg {String/String[]} paramOrder
+ * Defaults to undefined. A list of params to be executed server side. Specify the params in the order in
+ * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
+ * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
+ * acceptable:
+ *
+ * paramOrder: ['param1','param2','param3']
+ * paramOrder: 'param1 param2 param3'
+ * paramOrder: 'param1,param2,param3'
+ * paramOrder: 'param1|param2|param'
*/
paramOrder: undefined,
/**
* @cfg {Boolean} paramsAsHash
- * Send parameters as a collection of named arguments (defaults to true ). Providing a
- * {@link #paramOrder} nullifies this configuration.
+ * Send parameters as a collection of named arguments.
+ * Providing a {@link #paramOrder} nullifies this configuration.
*/
paramsAsHash: true,
@@ -53240,30 +55676,32 @@ paramOrder: 'param1|param2|param'
* for Store's which will not implement a full CRUD api.
*/
directFn : undefined,
-
+
/**
- * @cfg {Object} api The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
+ * @cfg {Object} api
+ * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
* function call.
*/
-
+
/**
- * @cfg {Object} extraParams Extra parameters that will be included on every read request. Individual requests with params
+ * @cfg {Object} extraParams
+ * Extra parameters that will be included on every read request. Individual requests with params
* of the same name will override these params when they are in conflict.
*/
-
+
// private
paramOrderRe: /[\s,|]/,
-
+
constructor: function(config){
var me = this;
-
+
Ext.apply(me, config);
if (Ext.isString(me.paramOrder)) {
me.paramOrder = me.paramOrder.split(me.paramOrderRe);
}
me.callParent(arguments);
},
-
+
doRequest: function(operation, callback, scope) {
var me = this,
writer = me.getWriter(),
@@ -53275,21 +55713,21 @@ paramOrder: 'param1|param2|param'
method,
i = 0,
len;
-
+
//
if (!fn) {
Ext.Error.raise('No direct function specified for this proxy');
}
//
-
+
if (operation.allowWrite()) {
request = writer.write(request);
}
-
+
if (operation.action == 'read') {
// We need to pass params
method = fn.directCfg.method;
-
+
if (method.ordered) {
if (method.len > 0) {
if (paramOrder) {
@@ -53306,7 +55744,7 @@ paramOrder: 'param1|param2|param'
} else {
args.push(request.jsonData);
}
-
+
Ext.apply(request, {
args: args,
directFn: fn
@@ -53314,7 +55752,7 @@ paramOrder: 'param1|param2|param'
args.push(me.createRequestCallback(request, operation, callback, scope), me);
fn.apply(window, args);
},
-
+
/*
* Inherit docs. We don't apply any encoding here because
* all of the direct requests go out as jsonData
@@ -53322,25 +55760,25 @@ paramOrder: 'param1|param2|param'
applyEncoding: function(value){
return value;
},
-
+
createRequestCallback: function(request, operation, callback, scope){
var me = this;
-
+
return function(data, event){
me.processResponse(event.status, operation, request, event, callback, scope);
};
},
-
+
// inherit docs
extractResponseData: function(response){
return Ext.isDefined(response.result) ? response.result : response.data;
},
-
+
// inherit docs
setException: function(operation, response) {
operation.setException(response.message);
},
-
+
// inherit docs
buildUrl: function(){
return '';
@@ -53348,36 +55786,28 @@ paramOrder: 'param1|param2|param'
});
/**
- * @class Ext.data.DirectStore
- * @extends Ext.data.Store
- * Small helper class to create an {@link Ext.data.Store} configured with an
- * {@link Ext.data.proxy.Direct} and {@link Ext.data.reader.Json} to make interacting
- * with an {@link Ext.Direct} Server-side {@link Ext.direct.Provider Provider} easier.
- * To create a different proxy/reader combination create a basic {@link Ext.data.Store}
- * configured as needed.
+ * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
+ * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
+ * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
+ * {@link Ext.data.Store} configured as needed.
*
- * *Note: Although they are not listed, this class inherits all of the config options of:
- *
- * {@link Ext.data.Store Store}
- *
+ * **Note:** Although they are not listed, this class inherits all of the config options of:
*
- *
- * {@link Ext.data.reader.Json JsonReader}
- *
- * {@link Ext.data.reader.Json#root root}
- * {@link Ext.data.reader.Json#idProperty idProperty}
- * {@link Ext.data.reader.Json#totalProperty totalProperty}
- *
+ * - **{@link Ext.data.Store Store}**
+ *
+ * - **{@link Ext.data.reader.Json JsonReader}**
+ *
+ * - **{@link Ext.data.reader.Json#root root}**
+ * - **{@link Ext.data.reader.Json#idProperty idProperty}**
+ * - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
+ *
+ * - **{@link Ext.data.proxy.Direct DirectProxy}**
+ *
+ * - **{@link Ext.data.proxy.Direct#directFn directFn}**
+ * - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
+ * - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
*
- * {@link Ext.data.proxy.Direct DirectProxy}
- *
- * {@link Ext.data.proxy.Direct#directFn directFn}
- * {@link Ext.data.proxy.Direct#paramOrder paramOrder}
- * {@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}
- *
- *
*/
-
Ext.define('Ext.data.DirectStore', {
/* Begin Definitions */
@@ -53389,9 +55819,6 @@ Ext.define('Ext.data.DirectStore', {
/* End Definitions */
- /**
- * @param {Object} config (optional) Config object.
- */
constructor : function(config){
config = Ext.apply({}, config);
if (!config.proxy) {
@@ -53410,51 +55837,40 @@ Ext.define('Ext.data.DirectStore', {
});
/**
- * @class Ext.util.Inflector
- * @extends Object
- * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
- * {@link #ordinalize ordinalizes} words. Sample usage:
- *
-
-//turning singular words into plurals
-Ext.util.Inflector.pluralize('word'); //'words'
-Ext.util.Inflector.pluralize('person'); //'people'
-Ext.util.Inflector.pluralize('sheep'); //'sheep'
-
-//turning plurals into singulars
-Ext.util.Inflector.singularize('words'); //'word'
-Ext.util.Inflector.singularize('people'); //'person'
-Ext.util.Inflector.singularize('sheep'); //'sheep'
-
-//ordinalizing numbers
-Ext.util.Inflector.ordinalize(11); //"11th"
-Ext.util.Inflector.ordinalize(21); //"21th"
-Ext.util.Inflector.ordinalize(1043); //"1043rd"
-
- *
- * Customization
- *
- * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
+ * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
+ * {@link #ordinalize ordinalizes} words. Sample usage:
+ *
+ * //turning singular words into plurals
+ * Ext.util.Inflector.pluralize('word'); //'words'
+ * Ext.util.Inflector.pluralize('person'); //'people'
+ * Ext.util.Inflector.pluralize('sheep'); //'sheep'
+ *
+ * //turning plurals into singulars
+ * Ext.util.Inflector.singularize('words'); //'word'
+ * Ext.util.Inflector.singularize('people'); //'person'
+ * Ext.util.Inflector.singularize('sheep'); //'sheep'
+ *
+ * //ordinalizing numbers
+ * Ext.util.Inflector.ordinalize(11); //"11th"
+ * Ext.util.Inflector.ordinalize(21); //"21th"
+ * Ext.util.Inflector.ordinalize(1043); //"1043rd"
+ *
+ * # Customization
+ *
+ * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
* rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
- * Here is how we might add a rule that pluralizes "ox" to "oxen":
- *
-
-Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
-
- *
- * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string.
- * In this case, the regular expression will only match the string "ox", and will replace that match with "oxen".
- * Here's how we could add the inverse rule:
- *
-
-Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
-
- *
- * Note that the ox/oxen rules are present by default.
- *
- * @singleton
+ * Here is how we might add a rule that pluralizes "ox" to "oxen":
+ *
+ * Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
+ *
+ * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
+ * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
+ * how we could add the inverse rule:
+ *
+ * Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
+ *
+ * Note that the ox/oxen rules are present by default.
*/
-
Ext.define('Ext.util.Inflector', {
/* Begin Definitions */
@@ -53468,8 +55884,7 @@ Ext.define('Ext.util.Inflector', {
* The registered plural tuples. Each item in the array should contain two items - the first must be a regular
* expression that matchers the singular form of a word, the second must be a String that replaces the matched
* part of the regular expression. This is managed by the {@link #plural} method.
- * @property plurals
- * @type Array
+ * @property {Array} plurals
*/
plurals: [
[(/(quiz)$/i), "$1zes" ],
@@ -53493,14 +55908,13 @@ Ext.define('Ext.util.Inflector', {
[(/s$/i), "s" ],
[(/$/), "s" ]
],
-
+
/**
* @private
- * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
- * regular expression that matches the plural form of a word, the second must be a String that replaces the
+ * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
+ * regular expression that matches the plural form of a word, the second must be a String that replaces the
* matched part of the regular expression. This is managed by the {@link #singular} method.
- * @property singulars
- * @type Array
+ * @property {Array} singulars
*/
singulars: [
[(/(quiz)zes$/i), "$1" ],
@@ -53529,12 +55943,11 @@ Ext.define('Ext.util.Inflector', {
[(/people$/i), "person" ],
[(/s$/i), "" ]
],
-
+
/**
* @private
* The registered uncountable words
- * @property uncountable
- * @type Array
+ * @property {String[]} uncountable
*/
uncountable: [
"sheep",
@@ -53551,7 +55964,7 @@ Ext.define('Ext.util.Inflector', {
"deer",
"means"
],
-
+
/**
* Adds a new singularization rule to the Inflector. See the intro docs for more information
* @param {RegExp} matcher The matcher regex
@@ -53560,7 +55973,7 @@ Ext.define('Ext.util.Inflector', {
singular: function(matcher, replacer) {
this.singulars.unshift([matcher, replacer]);
},
-
+
/**
* Adds a new pluralization rule to the Inflector. See the intro docs for more information
* @param {RegExp} matcher The matcher regex
@@ -53569,21 +55982,21 @@ Ext.define('Ext.util.Inflector', {
plural: function(matcher, replacer) {
this.plurals.unshift([matcher, replacer]);
},
-
+
/**
* Removes all registered singularization rules
*/
clearSingulars: function() {
this.singulars = [];
},
-
+
/**
* Removes all registered pluralization rules
*/
clearPlurals: function() {
this.plurals = [];
},
-
+
/**
* Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
* @param {String} word The word to test
@@ -53606,19 +56019,19 @@ Ext.define('Ext.util.Inflector', {
var plurals = this.plurals,
length = plurals.length,
tuple, regex, i;
-
+
for (i = 0; i < length; i++) {
tuple = plurals[i];
regex = tuple[0];
-
+
if (regex == word || (regex.test && regex.test(word))) {
return word.replace(regex, tuple[1]);
}
}
-
+
return word;
},
-
+
/**
* Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
* @param {String} word The word to singularize
@@ -53632,21 +56045,21 @@ Ext.define('Ext.util.Inflector', {
var singulars = this.singulars,
length = singulars.length,
tuple, regex, i;
-
+
for (i = 0; i < length; i++) {
tuple = singulars[i];
regex = tuple[0];
-
+
if (regex == word || (regex.test && regex.test(word))) {
return word.replace(regex, tuple[1]);
}
}
-
+
return word;
},
-
+
/**
- * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
+ * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
* package
* @param {String} word The word to classify
* @return {String} The classified version of the word
@@ -53654,9 +56067,9 @@ Ext.define('Ext.util.Inflector', {
classify: function(word) {
return Ext.String.capitalize(this.singularize(word));
},
-
+
/**
- * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
+ * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
* number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
* @param {Number} number The number to ordinalize
* @return {String} The ordinalized number
@@ -53665,7 +56078,7 @@ Ext.define('Ext.util.Inflector', {
var parsed = parseInt(number, 10),
mod10 = parsed % 10,
mod100 = parsed % 100;
-
+
//11 through 13 are a special case
if (11 <= mod100 && mod100 <= 13) {
return number + "th";
@@ -53714,7 +56127,7 @@ Ext.define('Ext.util.Inflector', {
vita: 'vitae'
},
singular;
-
+
for (singular in irregulars) {
this.plural(singular, irregulars[singular]);
this.singular(irregulars[singular], singular);
@@ -53757,7 +56170,7 @@ Ext.define('User', {
*
//first, we load up a User with id of 1
-var user = Ext.ModelManager.create({id: 1, name: 'Ed'}, 'User');
+var user = Ext.create('User', {id: 1, name: 'Ed'});
//the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
//the created store is automatically scoped to the set of Products for the User with id of 1
@@ -53813,7 +56226,7 @@ var store = new Search({query: 'Sencha Touch'}).tweets();
* equivalent to this:
*
-var store = new Ext.data.Store({
+var store = Ext.create('Ext.data.Store', {
model: 'Tweet',
filters: [
{
@@ -54009,22 +56422,21 @@ associations: [{
* @class Ext.data.JsonP
* @singleton
* This class is used to create JSONP requests. JSONP is a mechanism that allows for making
- * requests for data cross domain. More information is available here:
- * http://en.wikipedia.org/wiki/JSONP
+ * requests for data cross domain. More information is available here .
*/
Ext.define('Ext.data.JsonP', {
-
+
/* Begin Definitions */
-
+
singleton: true,
-
+
statics: {
requestCount: 0,
requests: {}
},
-
+
/* End Definitions */
-
+
/**
* @property timeout
* @type Number
@@ -54032,21 +56444,21 @@ Ext.define('Ext.data.JsonP', {
* failure callback will be fired. The timeout is in ms. Defaults to 30000 .
*/
timeout: 30000,
-
+
/**
* @property disableCaching
* @type Boolean
* True to add a unique cache-buster param to requests. Defaults to true .
*/
disableCaching: true,
-
+
/**
- * @property disableCachingParam
+ * @property disableCachingParam
* @type String
* Change the parameter which is sent went disabling caching through a cache buster. Defaults to '_dc' .
*/
disableCachingParam: '_dc',
-
+
/**
* @property callbackKey
* @type String
@@ -54055,7 +56467,7 @@ Ext.define('Ext.data.JsonP', {
* url?callback=Ext.data.JsonP.callback1
*/
callbackKey: 'callback',
-
+
/**
* Makes a JSONP request.
* @param {Object} options An object which may contain the following properties. Note that options will
@@ -54075,7 +56487,7 @@ Ext.define('Ext.data.JsonP', {
* disableCachingParam : String (Optional) See {@link #disableCachingParam}
* success : Function (Optional) A function to execute if the request succeeds.
* failure : Function (Optional) A function to execute if the request fails.
- * callback : Function (Optional) A function to execute when the request
+ *
callback : Function (Optional) A function to execute when the request
* completes, whether it is a success or failure.
*
scope : Object (Optional)The scope in
* which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.
@@ -54084,32 +56496,33 @@ Ext.define('Ext.data.JsonP', {
*/
request: function(options){
options = Ext.apply({}, options);
-
+
//
if (!options.url) {
Ext.Error.raise('A url must be specified for a JSONP request.');
}
//
-
- var me = this,
- disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
- cacheParam = options.disableCachingParam || me.disableCachingParam,
- id = ++me.statics().requestCount,
- callbackName = options.callbackName || 'callback' + id,
- callbackKey = options.callbackKey || me.callbackKey,
- timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
- params = Ext.apply({}, options.params),
+
+ var me = this,
+ disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
+ cacheParam = options.disableCachingParam || me.disableCachingParam,
+ id = ++me.statics().requestCount,
+ callbackName = options.callbackName || 'callback' + id,
+ callbackKey = options.callbackKey || me.callbackKey,
+ timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
+ params = Ext.apply({}, options.params),
url = options.url,
- request,
+ name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
+ request,
script;
-
- params[callbackKey] = 'Ext.data.JsonP.' + callbackName;
+
+ params[callbackKey] = name + '.data.JsonP.' + callbackName;
if (disableCaching) {
params[cacheParam] = new Date().getTime();
}
-
+
script = me.createScript(url, params);
-
+
me.statics().requests[id] = request = {
url: url,
params: params,
@@ -54121,17 +56534,17 @@ Ext.define('Ext.data.JsonP', {
callback: options.callback,
callbackName: callbackName
};
-
+
if (timeout > 0) {
request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
}
-
+
me.setupErrorHandling(request);
me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
Ext.getHead().appendChild(script);
return request;
},
-
+
/**
* Abort a request. If the request parameter is not specified all open requests will
* be aborted.
@@ -54140,7 +56553,7 @@ Ext.define('Ext.data.JsonP', {
abort: function(request){
var requests = this.statics().requests,
key;
-
+
if (request) {
if (!request.id) {
request = requests[request];
@@ -54154,7 +56567,7 @@ Ext.define('Ext.data.JsonP', {
}
}
},
-
+
/**
* Sets up error handling for the script
* @private
@@ -54163,7 +56576,7 @@ Ext.define('Ext.data.JsonP', {
setupErrorHandling: function(request){
request.script.onerror = Ext.bind(this.handleError, this, [request]);
},
-
+
/**
* Handles any aborts when loading the script
* @private
@@ -54173,7 +56586,7 @@ Ext.define('Ext.data.JsonP', {
request.errorType = 'abort';
this.handleResponse(null, request);
},
-
+
/**
* Handles any script errors when loading the script
* @private
@@ -54183,7 +56596,7 @@ Ext.define('Ext.data.JsonP', {
request.errorType = 'error';
this.handleResponse(null, request);
},
-
+
/**
* Cleans up anu script handling errors
* @private
@@ -54192,7 +56605,7 @@ Ext.define('Ext.data.JsonP', {
cleanupErrorHandling: function(request){
request.script.onerror = null;
},
-
+
/**
* Handle any script timeouts
* @private
@@ -54202,7 +56615,7 @@ Ext.define('Ext.data.JsonP', {
request.errorType = 'timeout';
this.handleResponse(null, request);
},
-
+
/**
* Handle a successful response
* @private
@@ -54210,9 +56623,9 @@ Ext.define('Ext.data.JsonP', {
* @param {Object} request The request
*/
handleResponse: function(result, request){
-
+
var success = true;
-
+
if (request.timeout) {
clearTimeout(request.timeout);
}
@@ -54220,7 +56633,7 @@ Ext.define('Ext.data.JsonP', {
delete this.statics()[request.id];
this.cleanupErrorHandling(request);
Ext.fly(request.script).remove();
-
+
if (request.errorType) {
success = false;
Ext.callback(request.failure, request.scope, [request.errorType]);
@@ -54229,7 +56642,7 @@ Ext.define('Ext.data.JsonP', {
}
Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
},
-
+
/**
* Create the script tag
* @private
@@ -54248,9 +56661,7 @@ Ext.define('Ext.data.JsonP', {
/**
* @class Ext.data.JsonPStore
* @extends Ext.data.Store
- * @ignore
* @private
- *
NOTE: This class is in need of migration to the new API.
*
Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
* A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.
*
A store configuration would be something like:
@@ -54299,21 +56710,170 @@ Ext.define('Ext.data.JsonPStore', {
});
/**
- * @class Ext.data.NodeInterface
- * This class is meant to be used as a set of methods that are applied to the prototype of a
- * Record to decorate it with a Node API. This means that models used in conjunction with a tree
+ * This class is used as a set of methods that are applied to the prototype of a
+ * Model to decorate it with a Node API. This means that models used in conjunction with a tree
* will have all of the tree related methods available on the model. In general this class will
- * not be used directly by the developer.
+ * not be used directly by the developer. This class also creates extra fields on the model if
+ * they do not exist, to help maintain the tree state and UI. These fields are documented as
+ * config options.
*/
Ext.define('Ext.data.NodeInterface', {
requires: ['Ext.data.Field'],
-
+
+ /**
+ * @cfg {String} parentId
+ * ID of parent node.
+ */
+
+ /**
+ * @cfg {Number} index
+ * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
+ * index will be 2.
+ */
+
+ /**
+ * @cfg {Number} depth
+ * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
+ */
+
+ /**
+ * @cfg {Boolean} [expanded=false]
+ * True if the node is expanded.
+ */
+
+ /**
+ * @cfg {Boolean} [expandable=false]
+ * Set to true to allow for expanding/collapsing of this node.
+ */
+
+ /**
+ * @cfg {Boolean} [checked=null]
+ * Set to true or false to show a checkbox alongside this node.
+ */
+
+ /**
+ * @cfg {Boolean} [leaf=false]
+ * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
+ * rendered for this node.
+ */
+
+ /**
+ * @cfg {String} cls
+ * CSS class to apply for this node.
+ */
+
+ /**
+ * @cfg {String} iconCls
+ * CSS class to apply for this node's icon.
+ */
+
+ /**
+ * @cfg {String} icon
+ * URL for this node's icon.
+ */
+
+ /**
+ * @cfg {Boolean} root
+ * True if this is the root node.
+ */
+
+ /**
+ * @cfg {Boolean} isLast
+ * True if this is the last node.
+ */
+
+ /**
+ * @cfg {Boolean} isFirst
+ * True if this is the first node.
+ */
+
+ /**
+ * @cfg {Boolean} [allowDrop=true]
+ * Set to false to deny dropping on this node.
+ */
+
+ /**
+ * @cfg {Boolean} [allowDrag=true]
+ * Set to false to deny dragging of this node.
+ */
+
+ /**
+ * @cfg {Boolean} [loaded=false]
+ * True if the node has finished loading.
+ */
+
+ /**
+ * @cfg {Boolean} [loading=false]
+ * True if the node is currently loading.
+ */
+
+ /**
+ * @cfg {String} href
+ * An URL for a link that's created when this config is specified.
+ */
+
+ /**
+ * @cfg {String} hrefTarget
+ * Target for link. Only applicable when {@link #href} also specified.
+ */
+
+ /**
+ * @cfg {String} qtip
+ * Tooltip text to show on this node.
+ */
+
+ /**
+ * @cfg {String} qtitle
+ * Tooltip title.
+ */
+
+ /**
+ * @cfg {String} text
+ * The text for to show on node label.
+ */
+
+ /**
+ * @cfg {Ext.data.NodeInterface[]} children
+ * Array of child nodes.
+ */
+
+
+ /**
+ * @property nextSibling
+ * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
+ */
+
+ /**
+ * @property previousSibling
+ * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
+ */
+
+ /**
+ * @property parentNode
+ * A reference to this node's parent node. `null` if this node is the root node.
+ */
+
+ /**
+ * @property lastChild
+ * A reference to this node's last child node. `null` if this node has no children.
+ */
+
+ /**
+ * @property firstChild
+ * A reference to this node's first child node. `null` if this node has no children.
+ */
+
+ /**
+ * @property childNodes
+ * An array of this nodes children. Array will be empty if this node has no chidren.
+ */
+
statics: {
/**
* This method allows you to decorate a Record's prototype to implement the NodeInterface.
* This adds a set of methods, new events, new properties and new fields on every Record
* with the same Model as the passed Record.
- * @param {Ext.data.Record} record The Record you want to decorate the prototype of.
+ * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
* @static
*/
decorate: function(record) {
@@ -54333,13 +56893,14 @@ Ext.define('Ext.data.NodeInterface', {
{name: idName, type: 'string', defaultValue: null},
{name: 'parentId', type: 'string', defaultValue: null},
{name: 'index', type: 'int', defaultValue: null},
- {name: 'depth', type: 'int', defaultValue: 0},
+ {name: 'depth', type: 'int', defaultValue: 0},
{name: 'expanded', type: 'bool', defaultValue: false, persist: false},
{name: 'expandable', type: 'bool', defaultValue: true, persist: false},
{name: 'checked', type: 'auto', defaultValue: null},
{name: 'leaf', type: 'bool', defaultValue: false, persist: false},
{name: 'cls', type: 'string', defaultValue: null, persist: false},
{name: 'iconCls', type: 'string', defaultValue: null, persist: false},
+ {name: 'icon', type: 'string', defaultValue: null, persist: false},
{name: 'root', type: 'boolean', defaultValue: false, persist: false},
{name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
{name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
@@ -54362,7 +56923,7 @@ Ext.define('Ext.data.NodeInterface', {
}
}
}
-
+
Ext.applyIf(record, {
firstChild: null,
lastChild: null,
@@ -54373,13 +56934,13 @@ Ext.define('Ext.data.NodeInterface', {
});
// Commit any fields so the record doesn't show as dirty initially
record.commit(true);
-
+
record.enableBubble([
/**
* @event append
* Fires when a new child node is appended
- * @param {Node} this This node
- * @param {Node} node The newly appended node
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} node The newly appended node
* @param {Number} index The index of the newly appended node
*/
"append",
@@ -54387,17 +56948,17 @@ Ext.define('Ext.data.NodeInterface', {
/**
* @event remove
* Fires when a child node is removed
- * @param {Node} this This node
- * @param {Node} node The removed node
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} node The removed node
*/
"remove",
/**
* @event move
* Fires when this node is moved to a new location in the tree
- * @param {Node} this This node
- * @param {Node} oldParent The old parent of this node
- * @param {Node} newParent The new parent of this node
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} oldParent The old parent of this node
+ * @param {Ext.data.NodeInterface} newParent The new parent of this node
* @param {Number} index The index it was moved to
*/
"move",
@@ -54405,34 +56966,34 @@ Ext.define('Ext.data.NodeInterface', {
/**
* @event insert
* Fires when a new child node is inserted.
- * @param {Node} this This node
- * @param {Node} node The child node inserted
- * @param {Node} refNode The child node the node was inserted before
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} node The child node inserted
+ * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
*/
"insert",
/**
* @event beforeappend
* Fires before a new child is appended, return false to cancel the append.
- * @param {Node} this This node
- * @param {Node} node The child node to be appended
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} node The child node to be appended
*/
"beforeappend",
/**
* @event beforeremove
* Fires before a child is removed, return false to cancel the remove.
- * @param {Node} this This node
- * @param {Node} node The child node to be removed
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} node The child node to be removed
*/
"beforeremove",
/**
* @event beforemove
* Fires before this node is moved to a new location in the tree. Return false to cancel the move.
- * @param {Node} this This node
- * @param {Node} oldParent The parent of this node
- * @param {Node} newParent The new parent this node is moving to
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} oldParent The parent of this node
+ * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
* @param {Number} index The index it is being moved to
*/
"beforemove",
@@ -54440,52 +57001,52 @@ Ext.define('Ext.data.NodeInterface', {
/**
* @event beforeinsert
* Fires before a new child is inserted, return false to cancel the insert.
- * @param {Node} this This node
- * @param {Node} node The child node to be inserted
- * @param {Node} refNode The child node the node is being inserted before
+ * @param {Ext.data.NodeInterface} this This node
+ * @param {Ext.data.NodeInterface} node The child node to be inserted
+ * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
*/
"beforeinsert",
-
+
/**
* @event expand
* Fires when this node is expanded.
- * @param {Node} this The expanding node
+ * @param {Ext.data.NodeInterface} this The expanding node
*/
"expand",
-
+
/**
* @event collapse
* Fires when this node is collapsed.
- * @param {Node} this The collapsing node
+ * @param {Ext.data.NodeInterface} this The collapsing node
*/
"collapse",
-
+
/**
* @event beforeexpand
* Fires before this node is expanded.
- * @param {Node} this The expanding node
+ * @param {Ext.data.NodeInterface} this The expanding node
*/
"beforeexpand",
-
+
/**
* @event beforecollapse
* Fires before this node is collapsed.
- * @param {Node} this The collapsing node
+ * @param {Ext.data.NodeInterface} this The collapsing node
*/
"beforecollapse",
-
+
/**
* @event sort
* Fires when this node's childNodes are sorted.
- * @param {Node} this This node.
- * @param {Array} The childNodes of this node.
+ * @param {Ext.data.NodeInterface} this This node.
+ * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
*/
"sort"
]);
-
+
return record;
},
-
+
applyFields: function(modelClass, addFields) {
var modelPrototype = modelClass.prototype,
fields = modelPrototype.fields,
@@ -54493,20 +57054,20 @@ Ext.define('Ext.data.NodeInterface', {
ln = addFields.length,
addField, i, name,
newFields = [];
-
+
for (i = 0; i < ln; i++) {
addField = addFields[i];
if (!Ext.Array.contains(keys, addField.name)) {
addField = Ext.create('data.field', addField);
-
+
newFields.push(addField);
fields.add(addField);
}
}
-
+
return newFields;
},
-
+
getPrototypeBody: function() {
return {
isNode: true,
@@ -54522,7 +57083,7 @@ Ext.define('Ext.data.NodeInterface', {
// Make sure the node implements the node interface
return Ext.data.NodeInterface.decorate(node);
},
-
+
/**
* Returns true if this node is a leaf
* @return {Boolean}
@@ -54571,8 +57132,8 @@ Ext.define('Ext.data.NodeInterface', {
while (parent.parentNode) {
++depth;
parent = parent.parentNode;
- }
-
+ }
+
me.beginEdit();
me.set({
isFirst: isFirst,
@@ -54585,7 +57146,7 @@ Ext.define('Ext.data.NodeInterface', {
if (silent) {
me.commit();
}
-
+
for (i = 0; i < len; i++) {
children[i].updateInfo(silent);
}
@@ -54617,12 +57178,12 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Returns true if this node has one or more child nodes, or if the expandable
- * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
+ * node attribute is explicitly specified as true, otherwise returns false.
* @return {Boolean}
*/
isExpandable : function() {
var me = this;
-
+
if (me.get('expandable')) {
return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
}
@@ -54630,10 +57191,12 @@ Ext.define('Ext.data.NodeInterface', {
},
/**
- * Insert node(s) as the last child node of this node.
- * If the node was previously a child node of another parent node, it will be removed from that node first.
- * @param {Node/Array} node The node or Array of nodes to append
- * @return {Node} The appended node if single append, or null if an array was passed
+ * Inserts node(s) as the last child node of this node.
+ *
+ * If the node was previously a child node of another parent node, it will be removed from that node first.
+ *
+ * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
+ * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
*/
appendChild : function(node, suppressEvents, suppressNodeUpdate) {
var me = this,
@@ -54650,9 +57213,9 @@ Ext.define('Ext.data.NodeInterface', {
} else {
// Make sure it is a record
node = me.createNode(node);
-
+
if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
- return false;
+ return false;
}
index = me.childNodes.length;
@@ -54676,7 +57239,7 @@ Ext.define('Ext.data.NodeInterface', {
node.nextSibling = null;
me.setLastChild(node);
-
+
ps = me.childNodes[index - 1];
if (ps) {
node.previousSibling = ps;
@@ -54687,28 +57250,28 @@ Ext.define('Ext.data.NodeInterface', {
}
node.updateInfo(suppressNodeUpdate);
-
+
// As soon as we append a child to this node, we are loaded
if (!me.isLoaded()) {
- me.set('loaded', true);
+ me.set('loaded', true);
}
// If this node didnt have any childnodes before, update myself
else if (me.childNodes.length === 1) {
me.set('loaded', me.isLoaded());
}
-
+
if (suppressEvents !== true) {
me.fireEvent("append", me, node, index);
if (oldParent) {
node.fireEvent("move", node, oldParent, me, index);
- }
+ }
}
return node;
}
},
-
+
/**
* Returns the bubble target for this node
* @private
@@ -54720,14 +57283,14 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Removes a child node from this node.
- * @param {Node} node The node to remove
- * @param {Boolean} destroy true to destroy the node upon removal. Defaults to false .
- * @return {Node} The removed node
+ * @param {Ext.data.NodeInterface} node The node to remove
+ * @param {Boolean} [destroy=false] True to destroy the node upon removal.
+ * @return {Ext.data.NodeInterface} The removed node
*/
removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
var me = this,
index = me.indexOf(node);
-
+
if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
return false;
}
@@ -54742,7 +57305,7 @@ Ext.define('Ext.data.NodeInterface', {
if (me.lastChild == node) {
me.setLastChild(node.previousSibling);
}
-
+
// update siblings
if (node.previousSibling) {
node.previousSibling.nextSibling = node.nextSibling;
@@ -54756,13 +57319,13 @@ Ext.define('Ext.data.NodeInterface', {
if (suppressEvents !== true) {
me.fireEvent("remove", me, node);
}
-
-
+
+
// If this node suddenly doesnt have childnodes anymore, update myself
if (!me.childNodes.length) {
me.set('loaded', me.isLoaded());
}
-
+
if (destroy) {
node.destroy(true);
} else {
@@ -54774,10 +57337,10 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Creates a copy (clone) of this Node.
- * @param {String} id (optional) A new id, defaults to this Node's id. See {@link #id}
.
- * @param {Boolean} deep (optional) If passed as true
, all child Nodes are recursively copied into the new Node.
- * If omitted or false, the copy will have no child Nodes.
- * @return {Node} A copy of this Node.
+ * @param {String} [id] A new id, defaults to this Node's id.
+ * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
+ * False to copy without child Nodes.
+ * @return {Ext.data.NodeInterface} A copy of this Node.
*/
copy: function(newId, deep) {
var me = this,
@@ -54795,13 +57358,13 @@ Ext.define('Ext.data.NodeInterface', {
},
/**
- * Clear the node.
+ * Clears the node.
* @private
- * @param {Boolean} destroy True to destroy the node.
+ * @param {Boolean} [destroy=false] True to destroy the node.
*/
clear : function(destroy) {
var me = this;
-
+
// clear any references from the node
me.parentNode = me.previousSibling = me.nextSibling = null;
if (destroy) {
@@ -54821,7 +57384,7 @@ Ext.define('Ext.data.NodeInterface', {
*/
var me = this,
options = me.destroyOptions;
-
+
if (silent === true) {
me.clear(true);
Ext.each(me.childNodes, function(n) {
@@ -54839,9 +57402,9 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Inserts the first node before the second node in this nodes childNodes collection.
- * @param {Node} node The node to insert
- * @param {Node} refNode The node to insert before (if null the node is appended)
- * @return {Node} The inserted node
+ * @param {Ext.data.NodeInterface} node The node to insert
+ * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
+ * @return {Ext.data.NodeInterface} The inserted node
*/
insertBefore : function(node, refNode, suppressEvents) {
var me = this,
@@ -54849,11 +57412,11 @@ Ext.define('Ext.data.NodeInterface', {
oldParent = node.parentNode,
refIndex = index,
ps;
-
+
if (!refNode) { // like standard Dom, refNode can be null for append
return me.appendChild(node);
}
-
+
// nothing to do
if (node == refNode) {
return false;
@@ -54861,11 +57424,11 @@ Ext.define('Ext.data.NodeInterface', {
// Make sure it is a record with the NodeInterface
node = me.createNode(node);
-
+
if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
return false;
}
-
+
// when moving internally, indexes will change after remove
if (oldParent == me && me.indexOf(node) < index) {
refIndex--;
@@ -54885,10 +57448,10 @@ Ext.define('Ext.data.NodeInterface', {
Ext.Array.splice(me.childNodes, refIndex, 0, node);
node.parentNode = me;
-
+
node.nextSibling = refNode;
refNode.previousSibling = node;
-
+
ps = me.childNodes[refIndex - 1];
if (ps) {
node.previousSibling = ps;
@@ -54897,12 +57460,12 @@ Ext.define('Ext.data.NodeInterface', {
} else {
node.previousSibling = null;
}
-
+
node.updateInfo();
-
+
if (!me.isLoaded()) {
- me.set('loaded', true);
- }
+ me.set('loaded', true);
+ }
// If this node didnt have any childnodes before, update myself
else if (me.childNodes.length === 1) {
me.set('loaded', me.isLoaded());
@@ -54913,18 +57476,18 @@ Ext.define('Ext.data.NodeInterface', {
if (oldParent) {
node.fireEvent("move", node, oldParent, me, refIndex, refNode);
- }
+ }
}
return node;
},
-
+
/**
* Insert a node into this node
* @param {Number} index The zero-based index to insert the node at
* @param {Ext.data.Model} node The node to insert
- * @return {Ext.data.Record} The record you just inserted
- */
+ * @return {Ext.data.Model} The record you just inserted
+ */
insertChild: function(index, node) {
var sibling = this.childNodes[index];
if (sibling) {
@@ -54937,8 +57500,8 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Removes this node from its parent
- * @param {Boolean} destroy true to destroy the node upon removal. Defaults to false .
- * @return {Node} this
+ * @param {Boolean} [destroy=false] True to destroy the node upon removal.
+ * @return {Ext.data.NodeInterface} this
*/
remove : function(destroy, suppressEvents) {
var parentNode = this.parentNode;
@@ -54951,8 +57514,8 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Removes all child nodes from this node.
- * @param {Boolean} destroy true to destroy the node upon removal. Defaults to false .
- * @return {Node} this
+ * @param {Boolean} [destroy=false] this
reference) in which the function is executed. Defaults to the current Node.
- * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
+ * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
+ * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
*/
bubble : function(fn, scope, args) {
var p = this;
@@ -55036,8 +57619,8 @@ Ext.define('Ext.data.NodeInterface', {
* will be the args provided or the current node. If the function returns false at any point,
* the cascade is stopped on that branch.
* @param {Function} fn The function to call
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the current Node.
- * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
+ * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
+ * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
*/
cascadeBy : function(fn, scope, args) {
if (fn.apply(scope || this, args || [this]) !== false) {
@@ -55056,8 +57639,8 @@ Ext.define('Ext.data.NodeInterface', {
* will be the args provided or the current node. If the function returns false at any point,
* the iteration stops.
* @param {Function} fn The function to call
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the current Node in the iteration.
- * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
+ * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
+ * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
*/
eachChild : function(fn, scope, args) {
var childNodes = this.childNodes,
@@ -55074,9 +57657,9 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Finds the first child that has the attribute with the specified value.
* @param {String} attribute The attribute name
- * @param {Mixed} value The value to search for
- * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
- * @return {Node} The found child or null if none was found
+ * @param {Object} value The value to search for
+ * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
+ * @return {Ext.data.NodeInterface} The found child or null if none was found
*/
findChild : function(attribute, value, deep) {
return this.findChildBy(function() {
@@ -55085,11 +57668,11 @@ Ext.define('Ext.data.NodeInterface', {
},
/**
- * Finds the first child by a custom function. The child matches if the function passed returns true
.
- * @param {Function} fn A function which must return true
if the passed Node is the required Node.
- * @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the Node being tested.
- * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
- * @return {Node} The found child or null if none was found
+ * Finds the first child by a custom function. The child matches if the function passed returns true.
+ * @param {Function} fn A function which must return true if the passed Node is the required Node.
+ * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
+ * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
+ * @return {Ext.data.NodeInterface} The found child or null if none was found
*/
findChildBy : function(fn, scope, deep) {
var cs = this.childNodes,
@@ -55114,7 +57697,7 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Returns true if this node is an ancestor (at any point) of the passed node.
- * @param {Node} node
+ * @param {Ext.data.NodeInterface} node
* @return {Boolean}
*/
contains : function(node) {
@@ -55123,7 +57706,7 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Returns true if the passed node is an ancestor (at any point) of this node.
- * @param {Node} node
+ * @param {Ext.data.NodeInterface} node
* @return {Boolean}
*/
isAncestor : function(node) {
@@ -55140,21 +57723,21 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Sorts this nodes children using the supplied sort function.
* @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
- * @param {Boolean} recursive Whether or not to apply this sort recursively
- * @param {Boolean} suppressEvent Set to true to not fire a sort event.
+ * @param {Boolean} [recursive=false] True to apply this sort recursively
+ * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
*/
sort : function(sortFn, recursive, suppressEvent) {
var cs = this.childNodes,
ln = cs.length,
i, n;
-
+
if (ln > 0) {
Ext.Array.sort(cs, sortFn);
for (i = 0; i < ln; i++) {
n = cs[i];
n.previousSibling = cs[i-1];
n.nextSibling = cs[i+1];
-
+
if (i === 0) {
this.setFirstChild(n);
n.updateInfo();
@@ -55167,25 +57750,25 @@ Ext.define('Ext.data.NodeInterface', {
n.sort(sortFn, true, true);
}
}
-
+
if (suppressEvent !== true) {
this.fireEvent('sort', this, cs);
}
}
},
-
+
/**
* Returns true if this node is expaned
* @return {Boolean}
- */
+ */
isExpanded: function() {
return this.get('expanded');
},
-
+
/**
* Returns true if this node is loaded
* @return {Boolean}
- */
+ */
isLoaded: function() {
return this.get('loaded');
},
@@ -55193,23 +57776,23 @@ Ext.define('Ext.data.NodeInterface', {
/**
* Returns true if this node is loading
* @return {Boolean}
- */
+ */
isLoading: function() {
return this.get('loading');
},
-
+
/**
* Returns true if this node is the root node
* @return {Boolean}
- */
+ */
isRoot: function() {
return !this.parentNode;
},
-
+
/**
* Returns true if this node is visible
* @return {Boolean}
- */
+ */
isVisible: function() {
var parent = this.parentNode;
while (parent) {
@@ -55220,12 +57803,12 @@ Ext.define('Ext.data.NodeInterface', {
}
return true;
},
-
+
/**
* Expand this node.
- * @param {Function} recursive (Optional) True to recursively expand all the children
- * @param {Function} callback (Optional) The function to execute once the expand completes
- * @param {Object} scope (Optional) The scope to run the callback in
+ * @param {Boolean} [recursive=false] True to recursively expand all the children
+ * @param {Function} [callback] The function to execute once the expand completes
+ * @param {Object} [scope] The scope to run the callback in
*/
expand: function(recursive, callback, scope) {
var me = this;
@@ -55235,48 +57818,47 @@ Ext.define('Ext.data.NodeInterface', {
// First we start by checking if this node is a parent
if (!me.isLeaf()) {
- // Now we check if this record is already expanding or expanded
- if (!me.isLoading() && !me.isExpanded()) {
- // The TreeStore actually listens for the beforeexpand method and checks
- // whether we have to asynchronously load the children from the server
- // first. Thats why we pass a callback function to the event that the
- // store can call once it has loaded and parsed all the children.
- me.fireEvent('beforeexpand', me, function() {
- me.set('expanded', true);
- me.fireEvent('expand', me, me.childNodes, false);
-
- // Call the expandChildren method if recursive was set to true
- if (recursive) {
- me.expandChildren(true, callback, scope);
- }
- else {
- Ext.callback(callback, scope || me, [me.childNodes]);
- }
- }, me);
- }
- // If it is is already expanded but we want to recursively expand then call expandChildren
- else if (recursive) {
- me.expandChildren(true, callback, scope);
- }
- else {
- Ext.callback(callback, scope || me, [me.childNodes]);
+ // If it's loaded, wait until it loads before proceeding
+ if (me.isLoading()) {
+ me.on('expand', function(){
+ me.expand(recursive, callback, scope);
+ }, me, {single: true});
+ } else {
+ // Now we check if this record is already expanding or expanded
+ if (!me.isExpanded()) {
+ // The TreeStore actually listens for the beforeexpand method and checks
+ // whether we have to asynchronously load the children from the server
+ // first. Thats why we pass a callback function to the event that the
+ // store can call once it has loaded and parsed all the children.
+ me.fireEvent('beforeexpand', me, function(){
+ me.set('expanded', true);
+ me.fireEvent('expand', me, me.childNodes, false);
+
+ // Call the expandChildren method if recursive was set to true
+ if (recursive) {
+ me.expandChildren(true, callback, scope);
+ } else {
+ Ext.callback(callback, scope || me, [me.childNodes]);
+ }
+ }, me);
+ } else if (recursive) {
+ // If it is is already expanded but we want to recursively expand then call expandChildren
+ me.expandChildren(true, callback, scope);
+ } else {
+ Ext.callback(callback, scope || me, [me.childNodes]);
+ }
}
-
- // TODO - if the node isLoading, we probably need to defer the
- // callback until it is loaded (e.g., selectPath would need us
- // to not make the callback until the childNodes exist).
- }
- // If it's not then we fire the callback right away
- else {
+ } else {
+ // If it's not then we fire the callback right away
Ext.callback(callback, scope || me); // leaf = no childNodes
}
},
-
+
/**
* Expand all the children of this node.
- * @param {Function} recursive (Optional) True to recursively expand all the children
- * @param {Function} callback (Optional) The function to execute once all the children are expanded
- * @param {Object} scope (Optional) The scope to run the callback in
+ * @param {Boolean} [recursive=false] True to recursively expand all the children
+ * @param {Function} [callback] The function to execute once all the children are expanded
+ * @param {Object} [scope] The scope to run the callback in
*/
expandChildren: function(recursive, callback, scope) {
var me = this,
@@ -55293,21 +57875,21 @@ Ext.define('Ext.data.NodeInterface', {
nodes[i].expand(recursive, function () {
expanding--;
if (callback && !expanding) {
- Ext.callback(callback, scope || me, [me.childNodes]);
+ Ext.callback(callback, scope || me, [me.childNodes]);
}
- });
+ });
}
}
-
+
if (!expanding && callback) {
Ext.callback(callback, scope || me, [me.childNodes]); }
},
/**
* Collapse this node.
- * @param {Function} recursive (Optional) True to recursively collapse all the children
- * @param {Function} callback (Optional) The function to execute once the collapse completes
- * @param {Object} scope (Optional) The scope to run the callback in
+ * @param {Boolean} [recursive=false] True to recursively collapse all the children
+ * @param {Function} [callback] The function to execute once the collapse completes
+ * @param {Object} [scope] The scope to run the callback in
*/
collapse: function(recursive, callback, scope) {
var me = this;
@@ -55317,17 +57899,17 @@ Ext.define('Ext.data.NodeInterface', {
// Now we check if this record is already collapsing or collapsed
if (!me.collapsing && me.isExpanded()) {
me.fireEvent('beforecollapse', me, function() {
- me.set('expanded', false);
+ me.set('expanded', false);
me.fireEvent('collapse', me, me.childNodes, false);
-
- // Call the collapseChildren method if recursive was set to true
+
+ // Call the collapseChildren method if recursive was set to true
if (recursive) {
me.collapseChildren(true, callback, scope);
}
else {
- Ext.callback(callback, scope || me, [me.childNodes]);
+ Ext.callback(callback, scope || me, [me.childNodes]);
}
- }, me);
+ }, me);
}
// If it is is already collapsed but we want to recursively collapse then call collapseChildren
else if (recursive) {
@@ -55336,15 +57918,15 @@ Ext.define('Ext.data.NodeInterface', {
}
// If it's not then we fire the callback right away
else {
- Ext.callback(callback, scope || me, [me.childNodes]);
+ Ext.callback(callback, scope || me, [me.childNodes]);
}
},
-
+
/**
* Collapse all the children of this node.
- * @param {Function} recursive (Optional) True to recursively collapse all the children
- * @param {Function} callback (Optional) The function to execute once all the children are collapsed
- * @param {Object} scope (Optional) The scope to run the callback in
+ * @param {Function} [recursive=false] True to recursively collapse all the children
+ * @param {Function} [callback] The function to execute once all the children are collapsed
+ * @param {Object} [scope] The scope to run the callback in
*/
collapseChildren: function(recursive, callback, scope) {
var me = this,
@@ -55361,12 +57943,12 @@ Ext.define('Ext.data.NodeInterface', {
nodes[i].collapse(recursive, function () {
collapsing--;
if (callback && !collapsing) {
- Ext.callback(callback, scope || me, [me.childNodes]);
+ Ext.callback(callback, scope || me, [me.childNodes]);
}
- });
+ });
}
}
-
+
if (!collapsing && callback) {
Ext.callback(callback, scope || me, [me.childNodes]);
}
@@ -55387,14 +57969,16 @@ Ext.define('Ext.data.NodeStore', {
requires: ['Ext.data.NodeInterface'],
/**
- * @cfg {Ext.data.Record} node The Record you want to bind this Store to. Note that
+ * @cfg {Ext.data.Model} node
+ * The Record you want to bind this Store to. Note that
* this record will be decorated with the Ext.data.NodeInterface if this is not the
* case yet.
*/
node: null,
/**
- * @cfg {Boolean} recursive Set this to true if you want this NodeStore to represent
+ * @cfg {Boolean} recursive
+ * Set this to true if you want this NodeStore to represent
* all the descendents of the node in its flat data collection. This is useful for
* rendering a tree structure to a DataView and is being used internally by
* the TreeView. Any records that are moved, removed, inserted or appended to the
@@ -55404,7 +57988,8 @@ Ext.define('Ext.data.NodeStore', {
recursive: false,
/**
- * @cfg {Boolean} rootVisible false to not include the root node in this Stores collection (defaults to true )
+ * @cfg {Boolean} rootVisible
+ * False to not include the root node in this Stores collection.
*/
rootVisible: false,
@@ -55624,79 +58209,142 @@ Ext.define('Ext.data.NodeStore', {
});
/**
* @author Ed Spencer
- * @class Ext.data.Request
- * @extends Object
*
- * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
+ * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
* All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
- * it does not contain any actual logic or perform the request itself.
- *
+ * it does not contain any actual logic or perform the request itself.
*/
Ext.define('Ext.data.Request', {
/**
- * @cfg {String} action The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'
+ * @cfg {String} action
+ * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
*/
action: undefined,
/**
- * @cfg {Object} params HTTP request params. The Proxy and its Writer have access to and can modify this object.
+ * @cfg {Object} params
+ * HTTP request params. The Proxy and its Writer have access to and can modify this object.
*/
params: undefined,
/**
- * @cfg {String} method The HTTP method to use on this Request (defaults to 'GET'). Should be one of 'GET', 'POST', 'PUT' or 'DELETE'
+ * @cfg {String} method
+ * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
*/
method: 'GET',
/**
- * @cfg {String} url The url to access on this Request
+ * @cfg {String} url
+ * The url to access on this Request
*/
url: undefined,
/**
* Creates the Request object.
- * @param {Object} config (optional) Config object.
+ * @param {Object} [config] Config object.
*/
constructor: function(config) {
Ext.apply(this, config);
}
});
+/**
+ * @author Don Griffin
+ *
+ * This class is a sequential id generator. A simple use of this class would be like so:
+ *
+ * Ext.define('MyApp.data.MyModel', {
+ * extend: 'Ext.data.Model',
+ * idgen: 'sequential'
+ * });
+ * // assign id's of 1, 2, 3, etc.
+ *
+ * An example of a configured generator would be:
+ *
+ * Ext.define('MyApp.data.MyModel', {
+ * extend: 'Ext.data.Model',
+ * idgen: {
+ * type: 'sequential',
+ * prefix: 'ID_',
+ * seed: 1000
+ * }
+ * });
+ * // assign id's of ID_1000, ID_1001, ID_1002, etc.
+ *
+ */
+Ext.define('Ext.data.SequentialIdGenerator', {
+ extend: 'Ext.data.IdGenerator',
+ alias: 'idgen.sequential',
+
+ constructor: function() {
+ var me = this;
+
+ me.callParent(arguments);
+
+ me.parts = [ me.prefix, ''];
+ },
+
+ /**
+ * @cfg {String} prefix
+ * The string to place in front of the sequential number for each generated id. The
+ * default is blank.
+ */
+ prefix: '',
+
+ /**
+ * @cfg {Number} seed
+ * The number at which to start generating sequential id's. The default is 1.
+ */
+ seed: 1,
+
+ /**
+ * Generates and returns the next id.
+ * @return {String} The next id.
+ */
+ generate: function () {
+ var me = this,
+ parts = me.parts;
+
+ parts[1] = me.seed++;
+ return parts.join('');
+ }
+});
+
/**
* @class Ext.data.Tree
- *
+ *
* This class is used as a container for a series of nodes. The nodes themselves maintain
* the relationship between parent/child. The tree itself acts as a manager. It gives functionality
- * to retrieve a node by its identifier: {@link #getNodeById}.
+ * to retrieve a node by its identifier: {@link #getNodeById}.
*
- * The tree also relays events from any of it's child nodes, allowing them to be handled in a
- * centralized fashion. In general this class is not used directly, rather used internally
+ * The tree also relays events from any of it's child nodes, allowing them to be handled in a
+ * centralized fashion. In general this class is not used directly, rather used internally
* by other parts of the framework.
*
*/
Ext.define('Ext.data.Tree', {
alias: 'data.tree',
-
+
mixins: {
observable: "Ext.util.Observable"
},
/**
+ * @property {Ext.data.NodeInterface}
* The root node for this tree
- * @type Node
*/
root: null,
/**
* Creates new Tree object.
- * @param {Node} root (optional) The root node
+ * @param {Ext.data.NodeInterface} root (optional) The root node
*/
constructor: function(root) {
var me = this;
+
- me.nodeHash = {};
me.mixins.observable.constructor.call(me);
-
+
if (root) {
me.setRootNode(root);
}
@@ -55717,119 +58365,84 @@ Ext.define('Ext.data.Tree', {
*/
setRootNode : function(node) {
var me = this;
-
+
me.root = node;
Ext.data.NodeInterface.decorate(node);
-
+
if (me.fireEvent('beforeappend', null, node) !== false) {
node.set('root', true);
node.updateInfo();
-
+
me.relayEvents(node, [
/**
* @event append
- * Fires when a new child node is appended to a node in this tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The newly appended node
- * @param {Number} index The index of the newly appended node
+ * @alias Ext.data.NodeInterface#append
*/
"append",
/**
* @event remove
- * Fires when a child node is removed from a node in this tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node removed
+ * @alias Ext.data.NodeInterface#remove
*/
"remove",
/**
* @event move
- * Fires when a node is moved to a new location in the tree
- * @param {Tree} tree The owner tree
- * @param {Node} node The node moved
- * @param {Node} oldParent The old parent of this node
- * @param {Node} newParent The new parent of this node
- * @param {Number} index The index it was moved to
+ * @alias Ext.data.NodeInterface#move
*/
"move",
/**
* @event insert
- * Fires when a new child node is inserted in a node in this tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node inserted
- * @param {Node} refNode The child node the node was inserted before
+ * @alias Ext.data.NodeInterface#insert
*/
"insert",
/**
* @event beforeappend
- * Fires before a new child is appended to a node in this tree, return false to cancel the append.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be appended
+ * @alias Ext.data.NodeInterface#beforeappend
*/
"beforeappend",
/**
* @event beforeremove
- * Fires before a child is removed from a node in this tree, return false to cancel the remove.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be removed
+ * @alias Ext.data.NodeInterface#beforeremove
*/
"beforeremove",
/**
* @event beforemove
- * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
- * @param {Tree} tree The owner tree
- * @param {Node} node The node being moved
- * @param {Node} oldParent The parent of the node
- * @param {Node} newParent The new parent the node is moving to
- * @param {Number} index The index it is being moved to
+ * @alias Ext.data.NodeInterface#beforemove
*/
"beforemove",
/**
* @event beforeinsert
- * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be inserted
- * @param {Node} refNode The child node the node is being inserted before
+ * @alias Ext.data.NodeInterface#beforeinsert
*/
"beforeinsert",
/**
* @event expand
- * Fires when this node is expanded.
- * @param {Node} this The expanding node
+ * @alias Ext.data.NodeInterface#expand
*/
"expand",
/**
* @event collapse
- * Fires when this node is collapsed.
- * @param {Node} this The collapsing node
+ * @alias Ext.data.NodeInterface#collapse
*/
"collapse",
/**
* @event beforeexpand
- * Fires before this node is expanded.
- * @param {Node} this The expanding node
+ * @alias Ext.data.NodeInterface#beforeexpand
*/
"beforeexpand",
/**
* @event beforecollapse
- * Fires before this node is collapsed.
- * @param {Node} this The collapsing node
+ * @alias Ext.data.NodeInterface#beforecollapse
*/
"beforecollapse" ,
@@ -55840,7 +58453,7 @@ Ext.define('Ext.data.Tree', {
*/
"rootchange"
]);
-
+
node.on({
scope: me,
insert: me.onNodeInsert,
@@ -55848,24 +58461,25 @@ Ext.define('Ext.data.Tree', {
remove: me.onNodeRemove
});
- me.registerNode(node);
+ me.nodeHash = {};
+ me.registerNode(node);
me.fireEvent('append', null, node);
me.fireEvent('rootchange', node);
}
-
+
return node;
},
-
+
/**
* Flattens all the nodes in the tree into an array.
* @private
- * @return {Array} The flattened nodes.
+ * @return {Ext.data.NodeInterface[]} The flattened nodes.
*/
flatten: function(){
var nodes = [],
hash = this.nodeHash,
key;
-
+
for (key in hash) {
if (hash.hasOwnProperty(key)) {
nodes.push(hash[key]);
@@ -55873,7 +58487,7 @@ Ext.define('Ext.data.Tree', {
}
return nodes;
},
-
+
/**
* Fired when a node is inserted into the root or one of it's children
* @private
@@ -55881,9 +58495,9 @@ Ext.define('Ext.data.Tree', {
* @param {Ext.data.NodeInterface} node The inserted node
*/
onNodeInsert: function(parent, node) {
- this.registerNode(node);
+ this.registerNode(node, true);
},
-
+
/**
* Fired when a node is appended into the root or one of it's children
* @private
@@ -55891,9 +58505,9 @@ Ext.define('Ext.data.Tree', {
* @param {Ext.data.NodeInterface} node The appended node
*/
onNodeAppend: function(parent, node) {
- this.registerNode(node);
+ this.registerNode(node, true);
},
-
+
/**
* Fired when a node is removed from the root or one of it's children
* @private
@@ -55901,7 +58515,7 @@ Ext.define('Ext.data.Tree', {
* @param {Ext.data.NodeInterface} node The removed node
*/
onNodeRemove: function(parent, node) {
- this.unregisterNode(node);
+ this.unregisterNode(node, true);
},
/**
@@ -55917,20 +58531,32 @@ Ext.define('Ext.data.Tree', {
* Registers a node with the tree
* @private
* @param {Ext.data.NodeInterface} The node to register
+ * @param {Boolean} [includeChildren] True to unregister any child nodes
*/
- registerNode : function(node) {
+ registerNode : function(node, includeChildren) {
this.nodeHash[node.getId() || node.internalId] = node;
+ if (includeChildren === true) {
+ node.eachChild(function(child){
+ this.registerNode(child, true);
+ }, this);
+ }
},
/**
* Unregisters a node with the tree
* @private
* @param {Ext.data.NodeInterface} The node to unregister
+ * @param {Boolean} [includeChildren] True to unregister any child nodes
*/
- unregisterNode : function(node) {
+ unregisterNode : function(node, includeChildren) {
delete this.nodeHash[node.getId() || node.internalId];
+ if (includeChildren === true) {
+ node.eachChild(function(child){
+ this.unregisterNode(child, true);
+ }, this);
+ }
},
-
+
/**
* Sorts this tree
* @private
@@ -55940,7 +58566,7 @@ Ext.define('Ext.data.Tree', {
sort: function(sorterFn, recursive) {
this.getRootNode().sort(sorterFn, recursive);
},
-
+
/**
* Filters this tree
* @private
@@ -55957,14 +58583,15 @@ Ext.define('Ext.data.Tree', {
* the hierarchical tree structure combined with a store. This class is generally used
* in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
* the Tree for convenience.
- *
+ *
* # Using Models
- *
+ *
* If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
- * The standard Tree fields will also be copied onto the Model for maintaining their state.
- *
+ * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
+ * in the {@link Ext.data.NodeInterface} documentation.
+ *
* # Reading Nested Data
- *
+ *
* For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
* so the reader can find nested data for each node. If a root is not specified, it will default to
* 'children'.
@@ -55977,9 +58604,9 @@ Ext.define('Ext.data.TreeStore', {
/**
* @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
* The root node for this store. For example:
- *
+ *
* root: {
- * expanded: true,
+ * expanded: true,
* text: "My Root",
* children: [
* { text: "Child 1", leaf: true },
@@ -55988,7 +58615,7 @@ Ext.define('Ext.data.TreeStore', {
* ] }
* ]
* }
- *
+ *
* Setting the `root` config option is the same as calling {@link #setRootNode}.
*/
@@ -56010,7 +58637,7 @@ Ext.define('Ext.data.TreeStore', {
* The default root id. Defaults to 'root'
*/
defaultRootId: 'root',
-
+
/**
* @cfg {String} defaultRootProperty
* The root property to specify on the reader if one is not explicitly defined.
@@ -56022,14 +58649,14 @@ Ext.define('Ext.data.TreeStore', {
* Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
*/
folderSort: false,
-
+
constructor: function(config) {
- var me = this,
+ var me = this,
root,
fields;
-
+
config = Ext.apply({}, config);
-
+
/**
* If we have no fields declare for the store, add some defaults.
* These will be ignored if a model is explicitly specified.
@@ -56040,129 +58667,86 @@ Ext.define('Ext.data.TreeStore', {
}
me.callParent([config]);
-
+
// We create our data tree.
me.tree = Ext.create('Ext.data.Tree');
me.relayEvents(me.tree, [
/**
* @event append
- * Fires when a new child node is appended to a node in this store's tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The newly appended node
- * @param {Number} index The index of the newly appended node
+ * @alias Ext.data.Tree#append
*/
"append",
-
+
/**
* @event remove
- * Fires when a child node is removed from a node in this store's tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node removed
+ * @alias Ext.data.Tree#remove
*/
"remove",
-
+
/**
* @event move
- * Fires when a node is moved to a new location in the store's tree
- * @param {Tree} tree The owner tree
- * @param {Node} node The node moved
- * @param {Node} oldParent The old parent of this node
- * @param {Node} newParent The new parent of this node
- * @param {Number} index The index it was moved to
+ * @alias Ext.data.Tree#move
*/
"move",
-
+
/**
* @event insert
- * Fires when a new child node is inserted in a node in this store's tree.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node inserted
- * @param {Node} refNode The child node the node was inserted before
+ * @alias Ext.data.Tree#insert
*/
"insert",
-
+
/**
* @event beforeappend
- * Fires before a new child is appended to a node in this store's tree, return false to cancel the append.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be appended
+ * @alias Ext.data.Tree#beforeappend
*/
"beforeappend",
-
+
/**
* @event beforeremove
- * Fires before a child is removed from a node in this store's tree, return false to cancel the remove.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be removed
+ * @alias Ext.data.Tree#beforeremove
*/
"beforeremove",
-
+
/**
* @event beforemove
- * Fires before a node is moved to a new location in the store's tree. Return false to cancel the move.
- * @param {Tree} tree The owner tree
- * @param {Node} node The node being moved
- * @param {Node} oldParent The parent of the node
- * @param {Node} newParent The new parent the node is moving to
- * @param {Number} index The index it is being moved to
+ * @alias Ext.data.Tree#beforemove
*/
"beforemove",
-
+
/**
* @event beforeinsert
- * Fires before a new child is inserted in a node in this store's tree, return false to cancel the insert.
- * @param {Tree} tree The owner tree
- * @param {Node} parent The parent node
- * @param {Node} node The child node to be inserted
- * @param {Node} refNode The child node the node is being inserted before
+ * @alias Ext.data.Tree#beforeinsert
*/
"beforeinsert",
-
+
/**
* @event expand
- * Fires when this node is expanded.
- * @param {Node} this The expanding node
+ * @alias Ext.data.Tree#expand
*/
"expand",
-
+
/**
* @event collapse
- * Fires when this node is collapsed.
- * @param {Node} this The collapsing node
+ * @alias Ext.data.Tree#collapse
*/
"collapse",
-
+
/**
* @event beforeexpand
- * Fires before this node is expanded.
- * @param {Node} this The expanding node
+ * @alias Ext.data.Tree#beforeexpand
*/
"beforeexpand",
-
+
/**
* @event beforecollapse
- * Fires before this node is collapsed.
- * @param {Node} this The collapsing node
+ * @alias Ext.data.Tree#beforecollapse
*/
"beforecollapse",
- /**
- * @event sort
- * Fires when this TreeStore is sorted.
- * @param {Node} node The node that is sorted.
- */
- "sort",
-
/**
* @event rootchange
- * Fires whenever the root node is changed in the tree.
- * @param {Ext.data.Model} root The new root
+ * @alias Ext.data.Tree#rootchange
*/
"rootchange"
]);
@@ -56185,17 +58769,16 @@ Ext.define('Ext.data.TreeStore', {
delete me.root;
me.setRootNode(root);
}
-
+
me.addEvents(
/**
- * @event rootchange
- * Fires when the root node on this TreeStore is changed.
- * @param {Ext.data.TreeStore} store This TreeStore
- * @param {Node} The new root node.
+ * @event sort
+ * Fires when this TreeStore is sorted.
+ * @param {Ext.data.NodeInterface} node The node that is sorted.
*/
- 'rootchange'
+ 'sort'
);
-
+
//
if (Ext.isDefined(me.nodeParameter)) {
if (Ext.isDefined(Ext.global.console)) {
@@ -56206,12 +58789,12 @@ Ext.define('Ext.data.TreeStore', {
}
//
},
-
+
// inherit docs
setProxy: function(proxy) {
var reader,
needsRoot;
-
+
if (proxy instanceof Ext.data.proxy.Proxy) {
// proxy instance, check if a root was set
needsRoot = Ext.isEmpty(proxy.getReader().root);
@@ -56231,17 +58814,17 @@ Ext.define('Ext.data.TreeStore', {
reader.buildExtractors(true);
}
},
-
+
// inherit docs
onBeforeSort: function() {
if (this.folderSort) {
this.sort({
property: 'leaf',
direction: 'ASC'
- }, 'prepend', false);
+ }, 'prepend', false);
}
},
-
+
/**
* Called before a node is expanded.
* @private
@@ -56264,10 +58847,10 @@ Ext.define('Ext.data.TreeStore', {
callback: function() {
Ext.callback(callback, scope || node, [node.childNodes]);
}
- });
+ });
}
},
-
+
//inherit docs
getNewRecords: function() {
return Ext.Array.filter(this.tree.flatten(), this.filterNew);
@@ -56277,7 +58860,7 @@ Ext.define('Ext.data.TreeStore', {
getUpdatedRecords: function() {
return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
},
-
+
/**
* Called before a node is collapsed.
* @private
@@ -56288,23 +58871,23 @@ Ext.define('Ext.data.TreeStore', {
onBeforeNodeCollapse: function(node, callback, scope) {
callback.call(scope || node, node.childNodes);
},
-
+
onNodeRemove: function(parent, node) {
var removed = this.removed;
-
+
if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
removed.push(node);
}
},
-
+
onNodeAdded: function(parent, node) {
var proxy = this.getProxy(),
reader = proxy.getReader(),
data = node.raw || node.data,
dataRoot, children;
-
- Ext.Array.remove(this.removed, node);
-
+
+ Ext.Array.remove(this.removed, node);
+
if (!node.isLeaf() && !node.isLoaded()) {
dataRoot = reader.getRoot(data);
if (dataRoot) {
@@ -56313,7 +58896,7 @@ Ext.define('Ext.data.TreeStore', {
}
}
},
-
+
/**
* Sets the root node for this store. See also the {@link #root} config option.
* @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
@@ -56322,9 +58905,9 @@ Ext.define('Ext.data.TreeStore', {
setRootNode: function(root) {
var me = this;
- root = root || {};
+ root = root || {};
if (!root.isNode) {
- // create a default rootNode and create internal data struct.
+ // create a default rootNode and create internal data struct.
Ext.applyIf(root, {
id: me.defaultRootId,
text: 'Root',
@@ -56337,20 +58920,20 @@ Ext.define('Ext.data.TreeStore', {
// Because we have decorated the model with new fields,
// we need to build new extactor functions on the reader.
me.getProxy().getReader().buildExtractors(true);
-
+
// When we add the root to the tree, it will automaticaly get the NodeInterface
me.tree.setRootNode(root);
-
+
// If the user has set expanded: true on the root, we want to call the expand function
- if (!root.isLoaded() && root.isExpanded()) {
+ if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
me.load({
node: root
});
}
-
+
return root;
},
-
+
/**
* Returns the root node for this tree.
* @return {Ext.data.NodeInterface}
@@ -56369,7 +58952,7 @@ Ext.define('Ext.data.TreeStore', {
/**
* Loads the Store using its configured {@link #proxy}.
- * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
+ * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
* object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
* The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
* default to the root node.
@@ -56377,11 +58960,11 @@ Ext.define('Ext.data.TreeStore', {
load: function(options) {
options = options || {};
options.params = options.params || {};
-
+
var me = this,
node = options.node || me.tree.getRootNode(),
root;
-
+
// If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
// create one for them.
if (!node) {
@@ -56389,29 +58972,29 @@ Ext.define('Ext.data.TreeStore', {
expanded: true
});
}
-
+
if (me.clearOnLoad) {
- node.removeAll();
+ node.removeAll(true);
}
-
+
Ext.applyIf(options, {
node: node
});
options.params[me.nodeParam] = node ? node.getId() : 'root';
-
+
if (node) {
node.set('loading', true);
}
-
+
return me.callParent([options]);
},
-
+
/**
* Fills a node with a series of child records.
* @private
* @param {Ext.data.NodeInterface} node The node to fill
- * @param {Array} records The records to add
+ * @param {Ext.data.Model[]} records The records to add
*/
fillNode: function(node, records) {
var me = this,
@@ -56424,12 +59007,12 @@ Ext.define('Ext.data.TreeStore', {
sortCollection.sort(me.sorters.items);
records = sortCollection.items;
}
-
+
node.set('loaded', true);
for (; i < ln; i++) {
node.appendChild(records[i], undefined, true);
}
-
+
return records;
},
@@ -56440,21 +59023,32 @@ Ext.define('Ext.data.TreeStore', {
records = operation.getRecords(),
node = operation.node;
+ me.loading = false;
node.set('loading', false);
if (successful) {
records = me.fillNode(node, records);
}
+ // The load event has an extra node parameter
+ // (differing from the load event described in AbstractStore)
+ /**
+ * @event load
+ * Fires whenever the store reads data from a remote data source.
+ * @param {Ext.data.TreeStore} this
+ * @param {Ext.data.NodeInterface} node The node that was loaded.
+ * @param {Ext.data.Model[]} records An array of records.
+ * @param {Boolean} successful True if the operation was successful.
+ */
// deprecate read?
me.fireEvent('read', me, operation.node, records, successful);
me.fireEvent('load', me, operation.node, records, successful);
//this is a callback that would have been passed to the 'read' function and is optional
Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
},
-
+
/**
* Creates any new records when a write is returned from the server.
* @private
- * @param {Array} records The array of new records
+ * @param {Ext.data.Model[]} records The array of new records
* @param {Ext.data.Operation} operation The operation that just completed
* @param {Boolean} success True if the operation was successful
*/
@@ -56493,7 +59087,7 @@ Ext.define('Ext.data.TreeStore', {
/**
* Updates any records when a write is returned from the server.
* @private
- * @param {Array} records The array of updated records
+ * @param {Ext.data.Model[]} records The array of updated records
* @param {Ext.data.Operation} operation The operation that just completed
* @param {Boolean} success True if the operation was successful
*/
@@ -56524,7 +59118,7 @@ Ext.define('Ext.data.TreeStore', {
/**
* Removes any records when a write is returned from the server.
* @private
- * @param {Array} records The array of removed records
+ * @param {Ext.data.Model[]} records The array of removed records
* @param {Ext.data.Operation} operation The operation that just completed
* @param {Boolean} success True if the operation was successful
*/
@@ -56549,10 +59143,227 @@ Ext.define('Ext.data.TreeStore', {
} else {
me.tree.sort(sorterFn, true);
me.fireEvent('datachanged', me);
- }
+ }
me.fireEvent('sort', me);
}
});
+
+/**
+ * @extend Ext.data.IdGenerator
+ * @author Don Griffin
+ *
+ * This class generates UUID's according to RFC 4122. This class has a default id property.
+ * This means that a single instance is shared unless the id property is overridden. Thus,
+ * two {@link Ext.data.Model} instances configured like the following share one generator:
+ *
+ * Ext.define('MyApp.data.MyModelX', {
+ * extend: 'Ext.data.Model',
+ * idgen: 'uuid'
+ * });
+ *
+ * Ext.define('MyApp.data.MyModelY', {
+ * extend: 'Ext.data.Model',
+ * idgen: 'uuid'
+ * });
+ *
+ * This allows all models using this class to share a commonly configured instance.
+ *
+ * # Using Version 1 ("Sequential") UUID's
+ *
+ * If a server can provide a proper timestamp and a "cryptographic quality random number"
+ * (as described in RFC 4122), the shared instance can be configured as follows:
+ *
+ * Ext.data.IdGenerator.get('uuid').reconfigure({
+ * version: 1,
+ * clockSeq: clock, // 14 random bits
+ * salt: salt, // 48 secure random bits (the Node field)
+ * timestamp: ts // timestamp per Section 4.1.4
+ * });
+ *
+ * // or these values can be split into 32-bit chunks:
+ *
+ * Ext.data.IdGenerator.get('uuid').reconfigure({
+ * version: 1,
+ * clockSeq: clock,
+ * salt: { lo: saltLow32, hi: saltHigh32 },
+ * timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
+ * });
+ *
+ * This approach improves the generator's uniqueness by providing a valid timestamp and
+ * higher quality random data. Version 1 UUID's should not be used unless this information
+ * can be provided by a server and care should be taken to avoid caching of this data.
+ *
+ * See http://www.ietf.org/rfc/rfc4122.txt for details.
+ */
+Ext.define('Ext.data.UuidGenerator', function () {
+ var twoPow14 = Math.pow(2, 14),
+ twoPow16 = Math.pow(2, 16),
+ twoPow28 = Math.pow(2, 28),
+ twoPow32 = Math.pow(2, 32);
+
+ function toHex (value, length) {
+ var ret = value.toString(16);
+ if (ret.length > length) {
+ ret = ret.substring(ret.length - length); // right-most digits
+ } else if (ret.length < length) {
+ ret = Ext.String.leftPad(ret, length, '0');
+ }
+ return ret;
+ }
+
+ function rand (lo, hi) {
+ var v = Math.random() * (hi - lo + 1);
+ return Math.floor(v) + lo;
+ }
+
+ function split (bignum) {
+ if (typeof(bignum) == 'number') {
+ var hi = Math.floor(bignum / twoPow32);
+ return {
+ lo: Math.floor(bignum - hi * twoPow32),
+ hi: hi
+ };
+ }
+ return bignum;
+ }
+
+ return {
+ extend: 'Ext.data.IdGenerator',
+
+ alias: 'idgen.uuid',
+
+ id: 'uuid', // shared by default
+
+ /**
+ * @property {Number/Object} salt
+ * When created, this value is a 48-bit number. For computation, this value is split
+ * into 32-bit parts and stored in an object with `hi` and `lo` properties.
+ */
+
+ /**
+ * @property {Number/Object} timestamp
+ * When created, this value is a 60-bit number. For computation, this value is split
+ * into 32-bit parts and stored in an object with `hi` and `lo` properties.
+ */
+
+ /**
+ * @cfg {Number} version
+ * The Version of UUID. Supported values are:
+ *
+ * * 1 : Time-based, "sequential" UUID.
+ * * 4 : Pseudo-random UUID.
+ *
+ * The default is 4.
+ */
+ version: 4,
+
+ constructor: function() {
+ var me = this;
+
+ me.callParent(arguments);
+
+ me.parts = [];
+ me.init();
+ },
+
+ generate: function () {
+ var me = this,
+ parts = me.parts,
+ ts = me.timestamp;
+
+ /*
+ The magic decoder ring (derived from RFC 4122 Section 4.2.2):
+
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | time_low |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | time_mid | ver | time_hi |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |res| clock_hi | clock_low | salt 0 |M| salt 1 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | salt (2-5) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ time_mid clock_hi (low 6 bits)
+ time_low | time_hi |clock_lo
+ | | | || salt[0]
+ | | | || | salt[1..5]
+ v v v vv v v
+ 0badf00d-aced-1def-b123-dfad0badbeef
+ ^ ^ ^
+ version | multicast (low bit)
+ |
+ reserved (upper 2 bits)
+ */
+ parts[0] = toHex(ts.lo, 8);
+ parts[1] = toHex(ts.hi & 0xFFFF, 4);
+ parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
+ parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
+ toHex(me.clockSeq & 0xFF, 2);
+ parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
+
+ if (me.version == 4) {
+ me.init(); // just regenerate all the random values...
+ } else {
+ // sequentially increment the timestamp...
+ ++ts.lo;
+ if (ts.lo >= twoPow32) { // if (overflow)
+ ts.lo = 0;
+ ++ts.hi;
+ }
+ }
+
+ return parts.join('-').toLowerCase();
+ },
+
+ getRecId: function (rec) {
+ return rec.getId();
+ },
+
+ /**
+ * @private
+ */
+ init: function () {
+ var me = this,
+ salt, time;
+
+ if (me.version == 4) {
+ // See RFC 4122 (Secion 4.4)
+ // o If the state was unavailable (e.g., non-existent or corrupted),
+ // or the saved node ID is different than the current node ID,
+ // generate a random clock sequence value.
+ me.clockSeq = rand(0, twoPow14-1);
+
+ // we run this on every id generation...
+ salt = me.salt || (me.salt = {});
+ time = me.timestamp || (me.timestamp = {});
+
+ // See RFC 4122 (Secion 4.4)
+ salt.lo = rand(0, twoPow32-1);
+ salt.hi = rand(0, twoPow16-1);
+ time.lo = rand(0, twoPow32-1);
+ time.hi = rand(0, twoPow28-1);
+ } else {
+ // this is run only once per-instance
+ me.salt = split(me.salt);
+ me.timestamp = split(me.timestamp);
+
+ // Set multicast bit: "the least significant bit of the first octet of the
+ // node ID" (nodeId = salt for this implementation):
+ me.salt.hi |= 0x100;
+ }
+ },
+
+ /**
+ * Reconfigures this generator given new config properties.
+ */
+ reconfigure: function (config) {
+ Ext.apply(this, config);
+ this.init();
+ }
+ };
+}());
+
/**
* @author Ed Spencer
* @class Ext.data.XmlStore
@@ -56608,7 +59419,7 @@ var store = new Ext.data.XmlStore({
</ItemSearchResponse>
*
* An object literal of this form could also be used as the {@link #data} config option.
- *
Note: Although not listed here, this class accepts all of the configuration options of
+ *
Note: This class accepts all of the configuration options of
* {@link Ext.data.reader.Xml XmlReader} .
* @xtype xmlstore
*/
@@ -56638,16 +59449,15 @@ Ext.define('Ext.data.XmlStore', {
/**
* @author Ed Spencer
- * @class Ext.data.proxy.Client
- * @extends Ext.data.proxy.Proxy
- *
- *
Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
- * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
+ *
+ * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
+ * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
+ * @private
*/
Ext.define('Ext.data.proxy.Client', {
extend: 'Ext.data.proxy.Proxy',
alternateClassName: 'Ext.data.ClientProxy',
-
+
/**
* Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
* from the client side storage, as well as removing any supporting data (such as lists of record IDs)
@@ -56660,154 +59470,130 @@ Ext.define('Ext.data.proxy.Client', {
});
/**
* @author Ed Spencer
- * @class Ext.data.proxy.JsonP
- * @extends Ext.data.proxy.Server
*
- *
JsonPProxy is useful when you need to load data from a domain other than the one your application is running
- * on. If your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its
- * data from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
+ * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
+ * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
+ * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
*
- *
We can get around this using a JsonPProxy. JsonPProxy injects a <script> tag into the DOM whenever
- * an AJAX request would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag
- * that would be injected might look like this:
+ * We can get around this using a JsonP proxy. JsonP proxy injects a `
*
- *
When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
- * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we
- * want to be notified when the result comes in and that it should call our callback function with the data it sends
- * back. So long as the server formats the response to look like this, everything will work:
+ * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
+ * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
+ * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
+ * long as the server formats the response to look like this, everything will work:
*
-
-someCallback({
- users: [
- {
- id: 1,
- name: "Ed Spencer",
- email: "ed@sencha.com"
- }
- ]
-});
-
+ * someCallback({
+ * users: [
+ * {
+ * id: 1,
+ * name: "Ed Spencer",
+ * email: "ed@sencha.com"
+ * }
+ * ]
+ * });
*
- *
As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the
- * JSON object that the server returned.
+ * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
+ * object that the server returned.
*
- *
JsonPProxy takes care of all of this automatically. It formats the url you pass, adding the callback
- * parameter automatically. It even creates a temporary callback function, waits for it to be called and then puts
- * the data into the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}.
- * Here's how we might set that up:
+ * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
+ * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
+ * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
+ * we might set that up:
*
-
-Ext.define('User', {
- extend: 'Ext.data.Model',
- fields: ['id', 'name', 'email']
-});
-
-var store = new Ext.data.Store({
- model: 'User',
- proxy: {
- type: 'jsonp',
- url : 'http://domainB.com/users'
- }
-});
-
-store.load();
-
+ * Ext.define('User', {
+ * extend: 'Ext.data.Model',
+ * fields: ['id', 'name', 'email']
+ * });
*
- *
That's all we need to do - JsonPProxy takes care of the rest. In this case the Proxy will have injected a
- * script tag like this:
+ * var store = Ext.create('Ext.data.Store', {
+ * model: 'User',
+ * proxy: {
+ * type: 'jsonp',
+ * url : 'http://domainB.com/users'
+ * }
+ * });
*
-
-<script src="http://domainB.com/users?callback=stcCallback001" id="stcScript001"></script>
-
+ * store.load();
*
- *
Customization
+ * That's all we need to do - JsonP proxy takes care of the rest. In this case the Proxy will have injected a script tag
+ * like this:
*
- *
Most parts of this script tag can be customized using the {@link #callbackParam}, {@link #callbackPrefix} and
- * {@link #scriptIdPrefix} configurations. For example:
+ *
*
-
-var store = new Ext.data.Store({
- model: 'User',
- proxy: {
- type: 'jsonp',
- url : 'http://domainB.com/users',
- callbackParam: 'theCallbackFunction',
- callbackPrefix: 'ABC',
- scriptIdPrefix: 'injectedScript'
- }
-});
-
-store.load();
-
+ * # Customization
*
- *
Would inject a script tag like this:
+ * This script tag can be customized using the {@link #callbackKey} configuration. For example:
*
-
-<script src="http://domainB.com/users?theCallbackFunction=ABC001" id="injectedScript001"></script>
-
+ * var store = Ext.create('Ext.data.Store', {
+ * model: 'User',
+ * proxy: {
+ * type: 'jsonp',
+ * url : 'http://domainB.com/users',
+ * callbackKey: 'theCallbackFunction'
+ * }
+ * });
*
- *
Implementing on the server side
+ * store.load();
*
- *
The remote server side needs to be configured to return data in this format. Here are suggestions for how you
- * might achieve this using Java, PHP and ASP.net:
+ * Would inject a script tag like this:
*
- *
Java:
+ *
*
-
-boolean jsonP = false;
-String cb = request.getParameter("callback");
-if (cb != null) {
- jsonP = true;
- response.setContentType("text/javascript");
-} else {
- response.setContentType("application/x-json");
-}
-Writer out = response.getWriter();
-if (jsonP) {
- out.write(cb + "(");
-}
-out.print(dataBlock.toJsonString());
-if (jsonP) {
- out.write(");");
-}
-
+ * # Implementing on the server side
*
- *
PHP:
+ * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
+ * achieve this using Java, PHP and ASP.net:
*
-
-$callback = $_REQUEST['callback'];
-
-// Create the output object.
-$output = array('a' => 'Apple', 'b' => 'Banana');
-
-//start output
-if ($callback) {
- header('Content-Type: text/javascript');
- echo $callback . '(' . json_encode($output) . ');';
-} else {
- header('Content-Type: application/x-json');
- echo json_encode($output);
-}
-
+ * Java:
*
- *
ASP.net:
+ * boolean jsonP = false;
+ * String cb = request.getParameter("callback");
+ * if (cb != null) {
+ * jsonP = true;
+ * response.setContentType("text/javascript");
+ * } else {
+ * response.setContentType("application/x-json");
+ * }
+ * Writer out = response.getWriter();
+ * if (jsonP) {
+ * out.write(cb + "(");
+ * }
+ * out.print(dataBlock.toJsonString());
+ * if (jsonP) {
+ * out.write(");");
+ * }
*
-
-String jsonString = "{success: true}";
-String cb = Request.Params.Get("callback");
-String responseString = "";
-if (!String.IsNullOrEmpty(cb)) {
- responseString = cb + "(" + jsonString + ")";
-} else {
- responseString = jsonString;
-}
-Response.Write(responseString);
-
+ * PHP:
*
+ * $callback = $_REQUEST['callback'];
+ *
+ * // Create the output object.
+ * $output = array('a' => 'Apple', 'b' => 'Banana');
+ *
+ * //start output
+ * if ($callback) {
+ * header('Content-Type: text/javascript');
+ * echo $callback . '(' . json_encode($output) . ');';
+ * } else {
+ * header('Content-Type: application/x-json');
+ * echo json_encode($output);
+ * }
+ *
+ * ASP.net:
+ *
+ * String jsonString = "{success: true}";
+ * String cb = Request.Params.Get("callback");
+ * String responseString = "";
+ * if (!String.IsNullOrEmpty(cb)) {
+ * responseString = cb + "(" + jsonString + ")";
+ * } else {
+ * responseString = jsonString;
+ * }
+ * Response.Write(responseString);
*/
Ext.define('Ext.data.proxy.JsonP', {
extend: 'Ext.data.proxy.Server',
@@ -56818,26 +59604,28 @@ Ext.define('Ext.data.proxy.JsonP', {
defaultWriterType: 'base',
/**
- * @cfg {String} callbackKey (Optional) See {@link Ext.data.JsonP#callbackKey}.
+ * @cfg {String} callbackKey
+ * See {@link Ext.data.JsonP#callbackKey}.
*/
callbackKey : 'callback',
/**
* @cfg {String} recordParam
- * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString').
- * Defaults to 'records'
+ * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
+ * 'records'
*/
recordParam: 'records',
/**
- * @cfg {Boolean} autoAppendParams True to automatically append the request's params to the generated url. Defaults to true
+ * @cfg {Boolean} autoAppendParams
+ * True to automatically append the request's params to the generated url. Defaults to true
*/
autoAppendParams: true,
constructor: function(){
this.addEvents(
/**
- * @event exception
+ * @event
* Fires when the server returns an exception
* @param {Ext.data.proxy.Proxy} this
* @param {Ext.data.Request} request The request that was sent
@@ -56850,7 +59638,7 @@ Ext.define('Ext.data.proxy.JsonP', {
/**
* @private
- * Performs the read request to the remote domain. JsonPProxy does not actually create an Ajax request,
+ * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
* instead we write out a