'Ext.Logger.log("XTemplate Error: " + e.message);',
//
'}',
'}');
me.body.push(name + me.callFn + '\n');
},
//-----------------------------------
// Internal
//
addFn: function (body) {
var me = this,
name = 'f' + me.definitions.length;
if (body === '.') {
me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
' return values',
'}');
} else if (body === '..') {
me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
' return parent',
'}');
} else {
me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
' try { with(values) {',
' return(' + body + ')',
' }} catch(e) {',
//
'Ext.Logger.log("XTemplate Error: " + e.message);',
//
'}',
'}');
}
return name;
},
parseTag: function (tag) {
var me = this,
m = me.tagRe.exec(tag),
name = m[1],
format = m[2],
args = m[3],
math = m[4],
v;
// name = "." - Just use the values object.
if (name == '.') {
// filter to not include arrays/objects/nulls
if (!me.validTypes) {
me.definitions.push('var validTypes={string:1,number:1,boolean:1};');
me.validTypes = true;
}
v = 'validTypes[typeof values] || ts.call(values) === "[object Date]" ? values : ""';
}
// name = "#" - Use the xindex
else if (name == '#') {
v = 'xindex';
}
else if (name.substr(0, 7) == "parent.") {
v = name;
}
// compound JavaScript property name (e.g., "foo.bar")
else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
v = "values." + name;
}
// number or a '-' in it or a single word (maybe a keyword): use array notation
// (http://jsperf.com/string-property-access/4)
else {
v = "values['" + name + "']";
}
if (math) {
v = '(' + v + math + ')';
}
if (format && me.useFormat) {
args = args ? ',' + args : "";
if (format.substr(0, 5) != "this.") {
format = "fm." + format + '(';
} else {
format += '(';
}
} else {
return v;
}
return format + v + args + ')';
},
// @private
evalTpl: function ($) {
// We have to use eval to realize the code block and capture the inner func we also
// don't want a deep scope chain. We only do this in Firefox and it is also unhappy
// with eval containing a return statement, so instead we assign to "$" and return
// that. Because we use "eval", we are automatically sandboxed properly.
eval($);
return $;
},
newLineRe: /\r\n|\r|\n/g,
aposRe: /[']/g,
intRe: /^\s*(\d+)\s*$/,
tagRe: /([\w-\.\#\$]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/
}, function () {
var proto = this.prototype;
proto.fnArgs = 'out,values,parent,xindex,xcount';
proto.callFn = '.call(this,' + proto.fnArgs + ')';
});
/**
* A template class that supports advanced functionality like:
*
* - Autofilling arrays using templates and sub-templates
* - Conditional processing with basic comparison operators
* - Basic math function support
* - Execute arbitrary inline code with special built-in template variables
* - Custom member functions
* - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
*
* XTemplate provides the templating mechanism built into {@link Ext.DataView}.
*
* The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
* demonstrate all of the supported features.
*
* # Sample Data
*
* This is the data object used for reference in each code example:
*
* var data = {
* name: 'Don Griffin',
* title: 'Senior Technomage',
* company: 'Sencha Inc.',
* drinks: ['Coffee', 'Water', 'More Coffee'],
* kids: [
* { name: 'Aubrey', age: 17 },
* { name: 'Joshua', age: 13 },
* { name: 'Cale', age: 10 },
* { name: 'Nikol', age: 5 },
* { name: 'Solomon', age: 0 }
* ]
* };
*
* # Auto filling of arrays
*
* The **tpl** tag and the **for** operator are used to process the provided data object:
*
* - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
* tag for each item in the array.
* - If for="." is specified, the data object provided is examined.
* - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
*
* Examples:
*
* ... // loop through array at root node
* ... // loop through array at foo node
* ... // loop through array at foo.bar node
*
* Using the sample data above:
*
* var tpl = new Ext.XTemplate(
* 'Kids: ',
* '', // process the data.kids node
* '{#}. {name}
', // use current array index to autonumber
* '
'
* );
* tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
*
* An example illustrating how the **for** property can be leveraged to access specified members of the provided data
* object to populate the template:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Title: {title}
',
* 'Company: {company}
',
* 'Kids: ',
* '', // interrogate the kids property within the data
* '{name}
',
* '
'
* );
* tpl.overwrite(panel.body, data); // pass the root node of the data object
*
* Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
* loop. This variable will represent the value of the array at the current index:
*
* var tpl = new Ext.XTemplate(
* '{name}\'s favorite beverages:
',
* '',
* ' - {.}
',
* ''
* );
* tpl.overwrite(panel.body, data);
*
* When processing a sub-template, for example while looping through a child array, you can access the parent object's
* members via the **parent** object:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '',
* '{name}
',
* 'Dad: {parent.name}
',
* '',
* '
'
* );
* tpl.overwrite(panel.body, data);
*
* # Conditional processing with basic comparison operators
*
* The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
* specific parts of the template.
*
* Using the sample data above:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '',
* '{name}
',
* '',
* '
'
* );
* tpl.overwrite(panel.body, data);
*
* More advanced conditionals are also supported:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '{name} is a ',
* '',
* 'teenager
',
* '',
* 'kid
',
* '',
* 'baby
',
* '',
* '
'
* );
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '{name} is a ',
* '',
* '',
* 'girl
',
* '',
* 'boy
',
* '',
* '
'
* );
*
* A `break` is implied between each case and default, however, multiple cases can be listed
* in a single <tpl> tag.
*
* # Using double quotes
*
* Examples:
*
* var tpl = new Ext.XTemplate(
* "Child",
* "Teenager",
* "...",
* '...',
* "",
* "Hello"
* );
*
* # Basic math support
*
* The following basic math operators may be applied directly on numeric data values:
*
* + - * /
*
* For example:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '', // <-- Note that the > is encoded
* '{#}: {name}
', // <-- Auto-number each item
* 'In 5 Years: {age+5}
', // <-- Basic math
* 'Dad: {parent.name}
',
* '',
* '
'
* );
* tpl.overwrite(panel.body, data);
*
* # Execute arbitrary inline code with special built-in template variables
*
* Anything between `{[ ... ]}` is considered code to be executed in the scope of the template.
* The expression is evaluated and the result is included in the generated result. There are
* some special variables available in that code:
*
* - **out**: The output array into which the template is being appended (using `push` to later
* `join`).
* - **values**: The values in the current scope. If you are using scope changing sub-templates,
* you can change what values is.
* - **parent**: The scope (values) of the ancestor template.
* - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
* - **xcount**: If you are in a looping template, the total length of the array you are looping.
*
* This example demonstrates basic row striping using an inline code block and the xindex variable:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Company: {[values.company.toUpperCase() + ", " + values.title]}
',
* 'Kids: ',
* '',
* '',
* '{name}',
* '
',
* '
'
* );
*
* Any code contained in "verbatim" blocks (using "{% ... %}") will be inserted directly in
* the generated code for the template. These blocks are not included in the output. This
* can be used for simple things like break/continue in a loop, or control structures or
* method calls (when they don't produce output). The `this` references the template instance.
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Company: {[values.company.toUpperCase() + ", " + values.title]}
',
* 'Kids: ',
* '',
* '{% if (xindex % 2 === 0) continue; %}',
* '{name}',
* '{% if (xindex > 100) break; %}',
* '',
* '
'
* );
*
* # Template member functions
*
* One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
* more complex processing:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '',
* 'Girl: {name} - {age}
',
* '',
* 'Boy: {name} - {age}
',
* '',
* '',
* '{name} is a baby!
',
* '',
* '
',
* {
* // XTemplate configuration:
* disableFormats: true,
* // member functions:
* isGirl: function(name){
* return name == 'Sara Grace';
* },
* isBaby: function(age){
* return age < 1;
* }
* }
* );
* tpl.overwrite(panel.body, data);
*/
Ext.define('Ext.XTemplate', {
extend: Ext.Template ,
/**
* @private
*/
emptyObj: {},
/**
* @cfg {Boolean} compiled
* Only applies to {@link Ext.Template}, XTemplates are compiled automatically on the
* first call to {@link #apply} or {@link #applyOut}.
* @hide
*/
apply: function(values) {
return this.applyOut(values, []).join('');
},
/**
* Appends the result of this template to the provided output array.
* @param {Object/Array} values The template values. See {@link #apply}.
* @param {Array} out The array to which output is pushed.
* @param {Object} parent
* @return {Array} The given out array.
*/
applyOut: function(values, out, parent) {
var me = this,
xindex = values.xindex,
xcount = values.xcount,
compiler;
if (!me.fn) {
compiler = new Ext.XTemplateCompiler({
useFormat : me.disableFormats !== true,
definitions : me.definitions
});
me.fn = compiler.compile(me.html);
}
try {
xindex = typeof xindex === 'number' ? xindex : 1;
xcount = typeof xcount === 'number' ? xcount : 1;
me.fn.call(me, out, values, parent || me.emptyObj, xindex, xcount);
} catch (e) {
//
Ext.Logger.log('Error: ' + e.message);
//
}
return out;
},
/**
* Does nothing. XTemplates are compiled automatically, so this function simply returns this.
* @return {Ext.XTemplate} this
*/
compile: function() {
return this;
},
statics: {
/**
* Gets an `XTemplate` from an object (an instance of an {@link Ext#define}'d class).
* Many times, templates are configured high in the class hierarchy and are to be
* shared by all classes that derive from that base. To further complicate matters,
* these templates are seldom actual instances but are rather configurations. For
* example:
*
* Ext.define('MyApp.Class', {
* someTpl: [
* 'tpl text here'
* ]
* });
*
* The goal being to share that template definition with all instances and even
* instances of derived classes, until `someTpl` is overridden. This method will
* "upgrade" these configurations to be real `XTemplate` instances *in place* (to
* avoid creating one instance per object).
*
* @param {Object} instance The object from which to get the `XTemplate` (must be
* an instance of an {@link Ext#define}'d class).
* @param {String} name The name of the property by which to get the `XTemplate`.
* @return {Ext.XTemplate} The `XTemplate` instance or null if not found.
* @protected
*/
getTpl: function (instance, name) {
var tpl = instance[name], // go for it! 99% of the time we will get it!
proto;
if (tpl && !tpl.isTemplate) { // tpl is just a configuration (not an instance)
// create the template instance from the configuration:
tpl = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
// and replace the reference with the new instance:
if (instance.hasOwnProperty(name)) { // the tpl is on the instance
instance[name] = tpl;
} else { // must be somewhere in the prototype chain
for (proto = instance.self.prototype; proto; proto = proto.superclass) {
if (proto.hasOwnProperty(name)) {
proto[name] = tpl;
break;
}
}
}
}
// else !tpl (no such tpl) or the tpl is an instance already... either way, tpl
// is ready to return
return tpl || null;
}
}
});
/**
* @private
*/
Ext.define('Ext.behavior.Behavior', {
constructor: function(component) {
this.component = component;
component.on('destroy', 'onComponentDestroy', this);
},
onComponentDestroy: Ext.emptyFn
});
/**
* @private
*/
Ext.define('Ext.fx.easing.Abstract', {
config: {
startTime: 0,
startValue: 0
},
isEasing: true,
isEnded: false,
constructor: function(config) {
this.initConfig(config);
return this;
},
applyStartTime: function(startTime) {
if (!startTime) {
startTime = Ext.Date.now();
}
return startTime;
},
updateStartTime: function(startTime) {
this.reset();
},
reset: function() {
this.isEnded = false;
},
getValue: Ext.emptyFn
});
/**
* @private
*/
Ext.define('Ext.fx.easing.Linear', {
extend: Ext.fx.easing.Abstract ,
alias: 'easing.linear',
config: {
duration: 0,
endValue: 0
},
updateStartValue: function(startValue) {
this.distance = this.getEndValue() - startValue;
},
updateEndValue: function(endValue) {
this.distance = endValue - this.getStartValue();
},
getValue: function() {
var deltaTime = Ext.Date.now() - this.getStartTime(),
duration = this.getDuration();
if (deltaTime > duration) {
this.isEnded = true;
return this.getEndValue();
}
else {
return this.getStartValue() + ((deltaTime / duration) * this.distance);
}
}
});
/**
* @private
*
* The abstract class. Sub-classes are expected, at the very least, to implement translation logics inside
* the 'translate' method
*/
Ext.define('Ext.util.translatable.Abstract', {
extend: Ext.Evented ,
config: {
useWrapper: null,
easing: null,
easingX: null,
easingY: null
},
/**
* @event animationstart
* Fires whenever the animation is started
* @param {Ext.util.translatable.Abstract} this
* @param {Number} x The current translation on the x axis
* @param {Number} y The current translation on the y axis
*/
/**
* @event animationframe
* Fires for each animation frame
* @param {Ext.util.translatable.Abstract} this
* @param {Number} x The new translation on the x axis
* @param {Number} y The new translation on the y axis
*/
/**
* @event animationend
* Fires whenever the animation is ended
* @param {Ext.util.translatable.Abstract} this
* @param {Number} x The current translation on the x axis
* @param {Number} y The current translation on the y axis
*/
x: 0,
y: 0,
activeEasingX: null,
activeEasingY: null,
isAnimating: false,
isTranslatable: true,
constructor: function(config) {
this.initConfig(config);
},
factoryEasing: function(easing) {
return Ext.factory(easing, Ext.fx.easing.Linear, null, 'easing');
},
applyEasing: function(easing) {
if (!this.getEasingX()) {
this.setEasingX(this.factoryEasing(easing));
}
if (!this.getEasingY()) {
this.setEasingY(this.factoryEasing(easing));
}
},
applyEasingX: function(easing) {
return this.factoryEasing(easing);
},
applyEasingY: function(easing) {
return this.factoryEasing(easing);
},
doTranslate: Ext.emptyFn,
translate: function(x, y, animation) {
if (animation) {
return this.translateAnimated(x, y, animation);
}
if (this.isAnimating) {
this.stopAnimation();
}
if (!isNaN(x) && typeof x == 'number') {
this.x = x;
}
if (!isNaN(y) && typeof y == 'number') {
this.y = y;
}
this.doTranslate(x, y);
},
translateAxis: function(axis, value, animation) {
var x, y;
if (axis == 'x') {
x = value;
}
else {
y = value;
}
return this.translate(x, y, animation);
},
animate: function(easingX, easingY) {
this.activeEasingX = easingX;
this.activeEasingY = easingY;
this.isAnimating = true;
this.lastX = null;
this.lastY = null;
Ext.AnimationQueue.start(this.doAnimationFrame, this);
this.fireEvent('animationstart', this, this.x, this.y);
return this;
},
translateAnimated: function(x, y, animation) {
if (!Ext.isObject(animation)) {
animation = {};
}
if (this.isAnimating) {
this.stopAnimation();
}
var now = Ext.Date.now(),
easing = animation.easing,
easingX = (typeof x == 'number') ? (animation.easingX || easing || this.getEasingX() || true) : null,
easingY = (typeof y == 'number') ? (animation.easingY || easing || this.getEasingY() || true) : null;
if (easingX) {
easingX = this.factoryEasing(easingX);
easingX.setStartTime(now);
easingX.setStartValue(this.x);
easingX.setEndValue(x);
if ('duration' in animation) {
easingX.setDuration(animation.duration);
}
}
if (easingY) {
easingY = this.factoryEasing(easingY);
easingY.setStartTime(now);
easingY.setStartValue(this.y);
easingY.setEndValue(y);
if ('duration' in animation) {
easingY.setDuration(animation.duration);
}
}
return this.animate(easingX, easingY);
},
doAnimationFrame: function() {
var me = this,
easingX = me.activeEasingX,
easingY = me.activeEasingY,
now = Date.now(),
x, y;
if (!me.isAnimating) {
return;
}
me.lastRun = now;
if (easingX === null && easingY === null) {
me.stopAnimation();
return;
}
if (easingX !== null) {
me.x = x = Math.round(easingX.getValue());
if (easingX.isEnded) {
me.activeEasingX = null;
me.fireEvent('axisanimationend', me, 'x', x);
}
}
else {
x = me.x;
}
if (easingY !== null) {
me.y = y = Math.round(easingY.getValue());
if (easingY.isEnded) {
me.activeEasingY = null;
me.fireEvent('axisanimationend', me, 'y', y);
}
}
else {
y = me.y;
}
if (me.lastX !== x || me.lastY !== y) {
me.doTranslate(x, y);
me.lastX = x;
me.lastY = y;
}
me.fireEvent('animationframe', me, x, y);
},
stopAnimation: function() {
if (!this.isAnimating) {
return;
}
this.activeEasingX = null;
this.activeEasingY = null;
this.isAnimating = false;
Ext.AnimationQueue.stop(this.doAnimationFrame, this);
this.fireEvent('animationend', this, this.x, this.y);
},
refresh: function() {
this.translate(this.x, this.y);
},
destroy: function() {
if (this.isAnimating) {
this.stopAnimation();
}
this.callParent(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.util.translatable.Dom', {
extend: Ext.util.translatable.Abstract ,
config: {
element: null
},
applyElement: function(element) {
if (!element) {
return;
}
return Ext.get(element);
},
updateElement: function() {
this.refresh();
}
});
/**
* @private
*
* CSS Transform implementation
*/
Ext.define('Ext.util.translatable.CssTransform', {
extend: Ext.util.translatable.Dom ,
doTranslate: function(x, y) {
var element = this.getElement();
if (!this.isDestroyed && !element.isDestroyed) {
element.translate(x, y);
}
},
destroy: function() {
var element = this.getElement();
if (element && !element.isDestroyed) {
element.dom.style.webkitTransform = null;
}
this.callSuper();
}
});
/**
* @private
*
* Scroll position implementation
*/
Ext.define('Ext.util.translatable.ScrollPosition', {
extend: Ext.util.translatable.Dom ,
type: 'scrollposition',
config: {
useWrapper: true
},
getWrapper: function() {
var wrapper = this.wrapper,
element = this.getElement(),
container;
if (!wrapper) {
container = element.getParent();
if (!container) {
return null;
}
if (container.hasCls(Ext.baseCSSPrefix + 'translatable-hboxfix')) {
container = container.getParent();
}
if (this.getUseWrapper()) {
wrapper = element.wrap();
}
else {
wrapper = container;
}
element.addCls('x-translatable');
wrapper.addCls('x-translatable-container');
this.wrapper = wrapper;
wrapper.on('painted', function() {
if (!this.isAnimating) {
this.refresh();
}
}, this);
this.refresh();
}
return wrapper;
},
doTranslate: function(x, y) {
var wrapper = this.getWrapper(),
dom;
if (wrapper) {
dom = wrapper.dom;
if (typeof x == 'number') {
dom.scrollLeft = 500000 - x;
}
if (typeof y == 'number') {
dom.scrollTop = 500000 - y;
}
}
},
destroy: function() {
var element = this.getElement(),
wrapper = this.wrapper;
if (wrapper) {
if (!element.isDestroyed) {
if (this.getUseWrapper()) {
wrapper.doReplaceWith(element);
}
element.removeCls('x-translatable');
}
if (!wrapper.isDestroyed) {
wrapper.removeCls('x-translatable-container');
wrapper.un('painted', 'refresh', this);
}
delete this.wrapper;
delete this._element;
}
this.callSuper();
}
});
/**
* @class Ext.util.translatable.CssPosition
* @private
*/
Ext.define('Ext.util.translatable.CssPosition', {
extend: Ext.util.translatable.Dom ,
doTranslate: function(x, y) {
var domStyle = this.getElement().dom.style;
if (typeof x == 'number') {
domStyle.left = x + 'px';
}
if (typeof y == 'number') {
domStyle.top = y + 'px';
}
},
destroy: function() {
var domStyle = this.getElement().dom.style;
domStyle.left = null;
domStyle.top = null;
this.callParent(arguments);
}
});
/**
* The utility class to abstract different implementations to have the best performance when applying 2D translation
* on any DOM element.
*
* @private
*/
Ext.define('Ext.util.Translatable', {
constructor: function(config) {
var namespace = Ext.util.translatable;
switch (Ext.browser.getPreferredTranslationMethod(config)) {
case 'scrollposition':
return new namespace.ScrollPosition(config);
case 'csstransform':
return new namespace.CssTransform(config);
case 'cssposition':
return new namespace.CssPosition(config);
}
}
});
/**
* @private
*/
Ext.define('Ext.behavior.Translatable', {
extend: Ext.behavior.Behavior ,
setConfig: function(config) {
var translatable = this.translatable,
component = this.component;
if (config) {
if (!translatable) {
this.translatable = translatable = new Ext.util.Translatable(config);
translatable.setElement(component.renderElement);
translatable.on('destroy', 'onTranslatableDestroy', this);
}
else if (Ext.isObject(config)) {
translatable.setConfig(config);
}
}
else if (translatable) {
translatable.destroy();
}
return this;
},
getTranslatable: function() {
return this.translatable;
},
onTranslatableDestroy: function() {
delete this.translatable;
},
onComponentDestroy: function() {
var translatable = this.translatable;
if (translatable) {
translatable.destroy();
}
}
});
/**
* A core util class to bring Draggable behavior to a Component. This class is specifically designed only for
* absolutely positioned elements starting from top: 0, left: 0. The initialOffset can then be set via configuration
* to have the elements in a different position.
*/
Ext.define('Ext.util.Draggable', {
isDraggable: true,
mixins: [
Ext.mixin.Observable
],
/**
* @event dragstart
* @preventable initDragStart
* Fires whenever the component starts to be dragged
* @param {Ext.util.Draggable} this
* @param {Ext.event.Event} e the event object
* @param {Number} offsetX The current offset value on the x axis
* @param {Number} offsetY The current offset value on the y axis
*/
/**
* @event drag
* Fires whenever the component is dragged
* @param {Ext.util.Draggable} this
* @param {Ext.event.Event} e the event object
* @param {Number} offsetX The new offset value on the x axis
* @param {Number} offsetY The new offset value on the y axis
*/
/**
* @event dragend
* Fires whenever the component is dragged
* @param {Ext.util.Draggable} this
* @param {Ext.event.Event} e the event object
* @param {Number} offsetX The current offset value on the x axis
* @param {Number} offsetY The current offset value on the y axis
*/
config: {
cls: Ext.baseCSSPrefix + 'draggable',
draggingCls: Ext.baseCSSPrefix + 'dragging',
element: null,
constraint: 'container',
disabled: null,
/**
* @cfg {String} direction
* Possible values: 'vertical', 'horizontal', or 'both'
* @accessor
*/
direction: 'both',
/**
* @cfg {Object/Number} initialOffset
* The initial draggable offset. When specified as Number,
* both x and y will be set to that value.
*/
initialOffset: {
x: 0,
y: 0
},
translatable: {}
},
DIRECTION_BOTH: 'both',
DIRECTION_VERTICAL: 'vertical',
DIRECTION_HORIZONTAL: 'horizontal',
defaultConstraint: {
min: { x: -Infinity, y: -Infinity },
max: { x: Infinity, y: Infinity }
},
containerWidth: 0,
containerHeight: 0,
width: 0,
height: 0,
/**
* Creates new Draggable.
* @param {Object} config The configuration object for this Draggable.
*/
constructor: function(config) {
var element;
this.extraConstraint = {};
this.initialConfig = config;
this.offset = {
x: 0,
y: 0
};
this.listeners = {
dragstart: 'onDragStart',
drag : 'onDrag',
dragend : 'onDragEnd',
resize : 'onElementResize',
touchstart : 'onPress',
touchend : 'onRelease',
scope: this
};
if (config && config.element) {
element = config.element;
delete config.element;
this.setElement(element);
}
return this;
},
applyElement: function(element) {
if (!element) {
return;
}
return Ext.get(element);
},
updateElement: function(element) {
element.on(this.listeners);
this.initConfig(this.initialConfig);
},
updateInitialOffset: function(initialOffset) {
if (typeof initialOffset == 'number') {
initialOffset = {
x: initialOffset,
y: initialOffset
};
}
var offset = this.offset,
x, y;
offset.x = x = initialOffset.x;
offset.y = y = initialOffset.y;
this.getTranslatable().translate(x, y);
},
updateCls: function(cls) {
this.getElement().addCls(cls);
},
applyTranslatable: function(translatable, currentInstance) {
translatable = Ext.factory(translatable, Ext.util.Translatable, currentInstance);
if (translatable) {
translatable.setElement(this.getElement());
}
return translatable;
},
setExtraConstraint: function(constraint) {
this.extraConstraint = constraint || {};
this.refreshConstraint();
return this;
},
addExtraConstraint: function(constraint) {
Ext.merge(this.extraConstraint, constraint);
this.refreshConstraint();
return this;
},
applyConstraint: function(newConstraint) {
this.currentConstraint = newConstraint;
if (!newConstraint) {
newConstraint = this.defaultConstraint;
}
if (newConstraint === 'container') {
return Ext.merge(this.getContainerConstraint(), this.extraConstraint);
}
return Ext.merge({}, this.extraConstraint, newConstraint);
},
updateConstraint: function() {
this.refreshOffset();
},
getContainerConstraint: function() {
var container = this.getContainer(),
element = this.getElement();
if (!container || !element.dom) {
return this.defaultConstraint;
}
return {
min: { x: 0, y: 0 },
max: { x: this.containerWidth - this.width, y: this.containerHeight - this.height }
};
},
getContainer: function() {
var container = this.container;
if (!container) {
container = this.getElement().getParent();
if (container) {
this.container = container;
container.on({
resize: 'onContainerResize',
destroy: 'onContainerDestroy',
scope: this
});
}
}
return container;
},
onElementResize: function(element, info) {
this.width = info.width;
this.height = info.height;
this.refresh();
},
onContainerResize: function(container, info) {
this.containerWidth = info.width;
this.containerHeight = info.height;
this.refresh();
},
onContainerDestroy: function() {
delete this.container;
delete this.containerSizeMonitor;
},
detachListeners: function() {
this.getElement().un(this.listeners);
},
isAxisEnabled: function(axis) {
var direction = this.getDirection();
if (axis === 'x') {
return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_HORIZONTAL);
}
return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_VERTICAL);
},
onPress: function(e) {
this.fireAction('touchstart', [this, e]);
},
onRelease: function(e) {
this.fireAction('touchend', [this, e]);
},
onDragStart: function(e) {
if (this.getDisabled()) {
return false;
}
var offset = this.offset;
this.fireAction('dragstart', [this, e, offset.x, offset.y], this.initDragStart);
},
initDragStart: function(me, e, offsetX, offsetY) {
this.dragStartOffset = {
x: offsetX,
y: offsetY
};
this.isDragging = true;
this.getElement().addCls(this.getDraggingCls());
},
onDrag: function(e) {
if (!this.isDragging) {
return;
}
var startOffset = this.dragStartOffset;
this.fireAction('drag', [this, e, startOffset.x + e.deltaX, startOffset.y + e.deltaY], this.doDrag);
},
doDrag: function(me, e, offsetX, offsetY) {
me.setOffset(offsetX, offsetY);
},
onDragEnd: function(e) {
if (!this.isDragging) {
return;
}
this.onDrag(e);
this.isDragging = false;
this.getElement().removeCls(this.getDraggingCls());
this.fireEvent('dragend', this, e, this.offset.x, this.offset.y);
},
setOffset: function(x, y, animation) {
var currentOffset = this.offset,
constraint = this.getConstraint(),
minOffset = constraint.min,
maxOffset = constraint.max,
min = Math.min,
max = Math.max;
if (this.isAxisEnabled('x') && typeof x == 'number') {
x = min(max(x, minOffset.x), maxOffset.x);
}
else {
x = currentOffset.x;
}
if (this.isAxisEnabled('y') && typeof y == 'number') {
y = min(max(y, minOffset.y), maxOffset.y);
}
else {
y = currentOffset.y;
}
currentOffset.x = x;
currentOffset.y = y;
this.getTranslatable().translate(x, y, animation);
},
getOffset: function() {
return this.offset;
},
refreshConstraint: function() {
this.setConstraint(this.currentConstraint);
},
refreshOffset: function() {
var offset = this.offset;
this.setOffset(offset.x, offset.y);
},
refresh: function() {
this.refreshConstraint();
this.getTranslatable().refresh();
this.refreshOffset();
},
/**
* Enable the Draggable.
* @return {Ext.util.Draggable} This Draggable instance
*/
enable: function() {
return this.setDisabled(false);
},
/**
* Disable the Draggable.
* @return {Ext.util.Draggable} This Draggable instance
*/
disable: function() {
return this.setDisabled(true);
},
destroy: function() {
var translatable = this.getTranslatable();
var element = this.getElement();
if (element && !element.isDestroyed) {
element.removeCls(this.getCls());
}
this.detachListeners();
if (translatable) {
translatable.destroy();
}
}
}, function() {
});
/**
* @private
*/
Ext.define('Ext.behavior.Draggable', {
extend: Ext.behavior.Behavior ,
setConfig: function(config) {
var draggable = this.draggable,
component = this.component;
if (config) {
if (!draggable) {
component.setTranslatable(config.translatable);
this.draggable = draggable = new Ext.util.Draggable(config);
draggable.setTranslatable(component.getTranslatable());
draggable.setElement(component.renderElement);
draggable.on('destroy', 'onDraggableDestroy', this);
component.on(this.listeners);
}
else if (Ext.isObject(config)) {
draggable.setConfig(config);
}
}
else if (draggable) {
draggable.destroy();
}
return this;
},
getDraggable: function() {
return this.draggable;
},
onDraggableDestroy: function() {
delete this.draggable;
},
onComponentDestroy: function() {
var draggable = this.draggable;
if (draggable) {
draggable.destroy();
}
}
});
(function(clsPrefix) {
/**
* Most of the visual classes you interact with in Sencha Touch are Components. Every Component in Sencha Touch is a
* subclass of Ext.Component, which means they can all:
*
* * Render themselves onto the page using a template
* * Show and hide themselves at any time
* * Center themselves on the screen
* * Enable and disable themselves
*
* They can also do a few more advanced things:
*
* * Float above other components (windows, message boxes and overlays)
* * Change size and position on the screen with animation
* * Dock other Components inside themselves (useful for toolbars)
* * Align to other components, allow themselves to be dragged around, make their content scrollable & more
*
* ## Available Components
*
* There are many components available in Sencha Touch, separated into 4 main groups:
*
* ### Navigation components
* * {@link Ext.Toolbar}
* * {@link Ext.Button}
* * {@link Ext.TitleBar}
* * {@link Ext.SegmentedButton}
* * {@link Ext.Title}
* * {@link Ext.Spacer}
*
* ### Store-bound components
* * {@link Ext.dataview.DataView}
* * {@link Ext.Carousel}
* * {@link Ext.List}
* * {@link Ext.NestedList}
*
* ### Form components
* * {@link Ext.form.Panel}
* * {@link Ext.form.FieldSet}
* * {@link Ext.field.Checkbox}
* * {@link Ext.field.Hidden}
* * {@link Ext.field.Slider}
* * {@link Ext.field.Text}
* * {@link Ext.picker.Picker}
* * {@link Ext.picker.Date}
*
* ### General components
* * {@link Ext.Panel}
* * {@link Ext.tab.Panel}
* * {@link Ext.Viewport Ext.Viewport}
* * {@link Ext.Img}
* * {@link Ext.Map}
* * {@link Ext.Audio}
* * {@link Ext.Video}
* * {@link Ext.Sheet}
* * {@link Ext.ActionSheet}
* * {@link Ext.MessageBox}
*
*
* ## Instantiating Components
*
* Components are created the same way as all other classes in Sencha Touch - using Ext.create. Here's how we can
* create a Text field:
*
* var panel = Ext.create('Ext.Panel', {
* html: 'This is my panel'
* });
*
* This will create a {@link Ext.Panel Panel} instance, configured with some basic HTML content. A Panel is just a
* simple Component that can render HTML and also contain other items. In this case we've created a Panel instance but
* it won't show up on the screen yet because items are not rendered immediately after being instantiated. This allows
* us to create some components and move them around before rendering and laying them out, which is a good deal faster
* than moving them after rendering.
*
* To show this panel on the screen now we can simply add it to the global Viewport:
*
* Ext.Viewport.add(panel);
*
* Panels are also Containers, which means they can contain other Components, arranged by a layout. Let's revisit the
* above example now, this time creating a panel with two child Components and a hbox layout:
*
* @example
* var panel = Ext.create('Ext.Panel', {
* layout: 'hbox',
*
* items: [
* {
* xtype: 'panel',
* flex: 1,
* html: 'Left Panel, 1/3rd of total size',
* style: 'background-color: #5E99CC;'
* },
* {
* xtype: 'panel',
* flex: 2,
* html: 'Right Panel, 2/3rds of total size',
* style: 'background-color: #759E60;'
* }
* ]
* });
*
* Ext.Viewport.add(panel);
*
* This time we created 3 Panels - the first one is created just as before but the inner two are declared inline using
* an xtype. Xtype is a convenient way of creating Components without having to go through the process of using
* Ext.create and specifying the full class name, instead you can just provide the xtype for the class inside an object
* and the framework will create the components for you.
*
* We also specified a layout for the top level panel - in this case hbox, which splits the horizontal width of the
* parent panel based on the 'flex' of each child. For example, if the parent Panel above is 300px wide then the first
* child will be flexed to 100px wide and the second to 200px because the first one was given `flex: 1` and the second
* `flex: 2`.
*
* ## Using xtype
*
* xtype is an easy way to create Components without using the full class name. This is especially useful when creating
* a {@link Ext.Container Container} that contains child Components. An xtype is simply a shorthand way of specifying a
* Component - for example you can use `xtype: 'panel'` instead of typing out Ext.panel.Panel.
*
* Sample usage:
*
* @example miniphone
* Ext.create('Ext.Container', {
* fullscreen: true,
* layout: 'fit',
*
* items: [
* {
* xtype: 'panel',
* html: 'This panel is created by xtype'
* },
* {
* xtype: 'toolbar',
* title: 'So is the toolbar',
* docked: 'top'
* }
* ]
* });
*
*
* ### Common xtypes
*
* These are the xtypes that are most commonly used. For an exhaustive list please see the
* [Components Guide](../../../core_concepts/components.html).
*
*
xtype Class
----------------- ---------------------
actionsheet Ext.ActionSheet
audio Ext.Audio
button Ext.Button
image Ext.Img
label Ext.Label
loadmask Ext.LoadMask
map Ext.Map
panel Ext.Panel
segmentedbutton Ext.SegmentedButton
sheet Ext.Sheet
spacer Ext.Spacer
titlebar Ext.TitleBar
toolbar Ext.Toolbar
video Ext.Video
carousel Ext.carousel.Carousel
navigationview Ext.navigation.View
datepicker Ext.picker.Date
picker Ext.picker.Picker
slider Ext.slider.Slider
thumb Ext.slider.Thumb
tabpanel Ext.tab.Panel
viewport Ext.viewport.Default
DataView Components
---------------------------------------------
dataview Ext.dataview.DataView
list Ext.dataview.List
nestedlist Ext.dataview.NestedList
Form Components
---------------------------------------------
checkboxfield Ext.field.Checkbox
datepickerfield Ext.field.DatePicker
emailfield Ext.field.Email
hiddenfield Ext.field.Hidden
numberfield Ext.field.Number
passwordfield Ext.field.Password
radiofield Ext.field.Radio
searchfield Ext.field.Search
selectfield Ext.field.Select
sliderfield Ext.field.Slider
spinnerfield Ext.field.Spinner
textfield Ext.field.Text
textareafield Ext.field.TextArea
togglefield Ext.field.Toggle
urlfield Ext.field.Url
fieldset Ext.form.FieldSet
formpanel Ext.form.Panel
*
*
* ## Configuring Components
*
* Whenever you create a new Component you can pass in configuration options. All of the configurations for a given
* Component are listed in the "Config options" section of its class docs page. You can pass in any number of
* configuration options when you instantiate the Component, and modify any of them at any point later. For example, we
* can easily modify the {@link Ext.Panel#html html content} of a Panel after creating it:
*
* @example miniphone
* // we can configure the HTML when we instantiate the Component
* var panel = Ext.create('Ext.Panel', {
* fullscreen: true,
* html: 'This is a Panel'
* });
*
* // we can update the HTML later using the setHtml method:
* panel.setHtml('Some new HTML');
*
* // we can retrieve the current HTML using the getHtml method:
* Ext.Msg.alert(panel.getHtml()); // displays "Some new HTML"
*
* Every config has a getter method and a setter method - these are automatically generated and always follow the same
* pattern. For example, a config called `html` will receive `getHtml` and `setHtml` methods, a config called `defaultType`
* will receive `getDefaultType` and `setDefaultType` methods, and so on.
*
* ## Further Reading
*
* See the [Component & Container Guide](../../../core_concepts/components.html) for more information, and check out the
* {@link Ext.Container} class docs also.
*
*/
Ext.define('Ext.Component', {
extend: Ext.AbstractComponent ,
alternateClassName: 'Ext.lib.Component',
mixins: [ Ext.mixin.Traversable ],
/**
* @cfg {String} xtype
* The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
* shortcut to the full component name. For example, the component `Ext.button.Button` has an xtype of `button`.
*
* You can define your own xtype on a custom {@link Ext.Component component} by specifying the
* {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
*
* Ext.define('PressMeButton', {
* extend: 'Ext.button.Button',
* alias: 'widget.pressmebutton',
* text: 'Press Me'
* });
*
* Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
* declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
* rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
* until they are actually needed. In complex, nested layouts containing many Components, this can make a
* noticeable improvement in performance.
*
* // Explicit creation of contained Components:
* var panel = new Ext.Panel({
* // ...
* items: [
* Ext.create('Ext.button.Button', {
* text: 'OK'
* })
* ]
* });
*
* // Implicit creation using xtype:
* var panel = new Ext.Panel({
* // ...
* items: [{
* xtype: 'button',
* text: 'OK'
* }]
* });
*
* In the first example, the button will always be created immediately during the panel's initialization. With
* many added Components, this approach could potentially slow the rendering of the page. In the second example,
* the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
* is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
* will never consume any resources whatsoever.
*/
xtype: 'component',
observableType: 'component',
cachedConfig: {
/**
* @cfg {String} baseCls
* The base CSS class to apply to this component's element. This will also be prepended to
* other elements within this component. To add specific styling for sub-classes, use the {@link #cls} config.
* @accessor
*/
baseCls: null,
/**
* @cfg {String/String[]} cls The CSS class to add to this component's element, in addition to the {@link #baseCls}
* @accessor
*/
cls: null,
/**
* @cfg {String} [floatingCls="x-floating"] The CSS class to add to this component when it is floatable.
* @accessor
*/
floatingCls: clsPrefix + 'floating',
/**
* @cfg {String} [hiddenCls="x-item-hidden"] The CSS class to add to the component when it is hidden
* @accessor
*/
hiddenCls: clsPrefix + 'item-hidden',
/**
* @cfg {String} ui The ui to be used on this Component
*/
ui: null,
/**
* @cfg {Number/String} margin The margin to use on this Component. Can be specified as a number (in which case
* all edges get the same margin) or a CSS string like '5 10 10 10'
* @accessor
*/
margin: null,
/**
* @cfg {Number/String} padding The padding to use on this Component. Can be specified as a number (in which
* case all edges get the same padding) or a CSS string like '5 10 10 10'
* @accessor
*/
padding: null,
/**
* @cfg {Number/String} border The border width to use on this Component. Can be specified as a number (in which
* case all edges get the same border width) or a CSS string like '5 10 10 10'.
*
* Please note that this will not add
* a `border-color` or `border-style` CSS property to the component; you must do that manually using either CSS or
* the {@link #style} configuration.
*
* ## Using {@link #style}:
*
* Ext.Viewport.add({
* centered: true,
* width: 100,
* height: 100,
*
* border: 3,
* style: 'border-color: blue; border-style: solid;'
* // ...
* });
*
* ## Using CSS:
*
* Ext.Viewport.add({
* centered: true,
* width: 100,
* height: 100,
*
* border: 3,
* cls: 'my-component'
* // ...
* });
*
* And your CSS file:
*
* .my-component {
* border-color: red;
* border-style: solid;
* }
*
* @accessor
*/
border: null,
/**
* @cfg {String} [styleHtmlCls="x-html"]
* The class that is added to the content target when you set `styleHtmlContent` to `true`.
* @accessor
*/
styleHtmlCls: clsPrefix + 'html',
/**
* @cfg {Boolean} [styleHtmlContent=false]
* `true` to automatically style the HTML inside the content target of this component (body for panels).
* @accessor
*/
styleHtmlContent: null
},
eventedConfig: {
/**
* @cfg {Number} flex
* The flex of this item *if* this item item is inside a {@link Ext.layout.HBox} or {@link Ext.layout.VBox}
* layout.
*
* You can also update the flex of a component dynamically using the {@link Ext.layout.FlexBox#setItemFlex}
* method.
*/
flex: null,
/**
* @cfg {Number/String} left
* The absolute left position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* Explicitly setting this value will make this Component become 'floating', which means its layout will no
* longer be affected by the Container that it resides in.
* @accessor
* @evented
*/
left: null,
/**
* @cfg {Number/String} top
* The absolute top position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* Explicitly setting this value will make this Component become 'floating', which means its layout will no
* longer be affected by the Container that it resides in.
* @accessor
* @evented
*/
top: null,
/**
* @cfg {Number/String} right
* The absolute right position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* Explicitly setting this value will make this Component become 'floating', which means its layout will no
* longer be affected by the Container that it resides in.
* @accessor
* @evented
*/
right: null,
/**
* @cfg {Number/String} bottom
* The absolute bottom position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* Explicitly setting this value will make this Component become 'floating', which means its layout will no
* longer be affected by the Container that it resides in.
* @accessor
* @evented
*/
bottom: null,
/**
* @cfg {Number/String} width
* The width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* By default, if this is not explicitly set, this Component's element will simply have its own natural size.
* If set to `auto`, it will set the width to `null` meaning it will have its own natural size.
* @accessor
* @evented
*/
width: null,
/**
* @cfg {Number/String} height
* The height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* By default, if this is not explicitly set, this Component's element will simply have its own natural size.
* If set to `auto`, it will set the width to `null` meaning it will have its own natural size.
* @accessor
* @evented
*/
height: null,
/**
* @cfg {Number/String} minWidth
* The minimum width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* If set to `auto`, it will set the width to `null` meaning it will have its own natural size.
* @accessor
* @evented
*/
minWidth: null,
/**
* @cfg {Number/String} minHeight
* The minimum height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* If set to `auto`, it will set the width to `null` meaning it will have its own natural size.
* @accessor
* @evented
*/
minHeight: null,
/**
* @cfg {Number/String} maxWidth
* The maximum width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* If set to `auto`, it will set the width to `null` meaning it will have its own natural size.
* Note that this config will not apply if the Component is 'floating' (absolutely positioned or centered)
* @accessor
* @evented
*/
maxWidth: null,
/**
* @cfg {Number/String} maxHeight
* The maximum height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
* If set to `auto`, it will set the width to `null` meaning it will have its own natural size.
* Note that this config will not apply if the Component is 'floating' (absolutely positioned or centered)
* @accessor
* @evented
*/
maxHeight: null,
/**
* @cfg {String} docked
* The dock position of this component in its container. Can be `left`, `top`, `right` or `bottom`.
*
* __Notes__
*
* You must use a HTML5 doctype for {@link #docked} `bottom` to work. To do this, simply add the following code to the HTML file:
*
*
*
* So your index.html file should look a little like this:
*
*
*
*
* MY application title
* ...
*
* @accessor
* @evented
*/
docked: null,
/**
* @cfg {Boolean} centered
* Whether or not this Component is absolutely centered inside its Container
* @accessor
* @evented
*/
centered: null,
/**
* @cfg {Boolean} hidden
* Whether or not this Component is hidden (its CSS `display` property is set to `none`)
* @accessor
* @evented
*/
hidden: null,
/**
* @cfg {Boolean} disabled
* Whether or not this component is disabled
* @accessor
* @evented
*/
disabled: null
},
config: {
/**
* @cfg {String/Object} style Optional CSS styles that will be rendered into an inline style attribute when the
* Component is rendered.
*
* You can pass either a string syntax:
*
* style: 'background:red'
*
* Or by using an object:
*
* style: {
* background: 'red'
* }
*
* When using the object syntax, you can define CSS Properties by using a string:
*
* style: {
* 'border-left': '1px solid red'
* }
*
* Although the object syntax is much easier to read, we suggest you to use the string syntax for better performance.
*
* @accessor
*/
style: null,
/**
* @cfg {String/Ext.Element/HTMLElement} html Optional HTML content to render inside this Component, or a reference
* to an existing element on the page.
* @accessor
*/
html: null,
/**
* @cfg {Object} draggable Configuration options to make this Component draggable
* @accessor
*/
draggable: null,
/**
* @cfg {Object} translatable
* @private
* @accessor
*/
translatable: null,
/**
* @cfg {Ext.Element} renderTo Optional element to render this Component to. Usually this is not needed because
* a Component is normally full screen or automatically rendered inside another {@link Ext.Container Container}
* @accessor
*/
renderTo: null,
/**
* @cfg {Number} zIndex The z-index to give this Component when it is rendered
* @accessor
*/
zIndex: null,
/**
* @cfg {String/String[]/Ext.Template/Ext.XTemplate[]} tpl
* A {@link String}, {@link Ext.Template}, {@link Ext.XTemplate} or an {@link Array} of strings to form an {@link Ext.XTemplate}.
* Used in conjunction with the {@link #data} and {@link #tplWriteMode} configurations.
*
* __Note__
* The {@link #data} configuration _must_ be set for any content to be shown in the component when using this configuration.
* @accessor
*/
tpl: null,
/**
* @cfg {String/Mixed} enterAnimation
* Animation effect to apply when the Component is being shown. Typically you want to use an
* inbound animation type such as 'fadeIn' or 'slideIn'.
* @deprecated 2.0.0 Please use {@link #showAnimation} instead.
* @accessor
*/
enterAnimation: null,
/**
* @cfg {String/Mixed} exitAnimation
* Animation effect to apply when the Component is being hidden.
* @deprecated 2.0.0 Please use {@link #hideAnimation} instead. Typically you want to use an
* outbound animation type such as 'fadeOut' or 'slideOut'.
* @accessor
*/
exitAnimation: null,
/**
* @cfg {String/Mixed} showAnimation
* Animation effect to apply when the Component is being shown. Typically you want to use an
* inbound animation type such as 'fadeIn' or 'slideIn'. For more animations, check the {@link Ext.fx.Animation#type} config.
* @accessor
*/
showAnimation: null,
/**
* @cfg {String/Mixed} hideAnimation
* Animation effect to apply when the Component is being hidden. Typically you want to use an
* outbound animation type such as 'fadeOut' or 'slideOut'. For more animations, check the {@link Ext.fx.Animation#type} config.
* @accessor
*/
hideAnimation: null,
/**
* @cfg {String} tplWriteMode The Ext.(X)Template method to use when
* updating the content area of the Component.
* Valid modes are:
*
* - append
* - insertAfter
* - insertBefore
* - insertFirst
* - overwrite
* @accessor
*/
tplWriteMode: 'overwrite',
/**
* @cfg {Object} data
* The initial set of data to apply to the `{@link #tpl}` to
* update the content area of the Component.
* @accessor
*/
data: null,
/**
* @cfg {String} [disabledCls="x-item-disabled"] The CSS class to add to the component when it is disabled
* @accessor
*/
disabledCls: clsPrefix + 'item-disabled',
/**
* @cfg {Ext.Element/HTMLElement/String} contentEl The configured element will automatically be
* added as the content of this component. When you pass a string, we expect it to be an element id.
* If the content element is hidden, we will automatically show it.
* @accessor
*/
contentEl: null,
/**
* @cfg {String} id
* The **unique id of this component instance.**
*
* It should not be necessary to use this configuration except for singleton objects in your application. Components
* created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
*
* Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
* which provides selector-based searching for Sencha Components analogous to DOM querying. The
* {@link Ext.Container} class contains {@link Ext.Container#down shortcut methods} to query
* its descendant Components by selector.
*
* Note that this id will also be used as the element id for the containing HTML element that is rendered to the
* page for this component. This allows you to write id-based CSS rules to style the specific instance of this
* component uniquely, and also to select sub-elements using this component's id as the parent.
*
* **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
*
* Defaults to an auto-assigned id.
*/
/**
* @cfg {String} itemId
* An itemId can be used as an alternative way to get a reference to a component when no object reference is
* available. Instead of using an `{@link #id}` with {@link Ext#getCmp}, use `itemId` with
* {@link Ext.Container#getComponent} which will retrieve `itemId`'s or {@link #id}'s. Since `itemId`'s are an
* index to the container's internal MixedCollection, the `itemId` is scoped locally to the container - avoiding
* potential conflicts with {@link Ext.ComponentManager} which requires a **unique** `{@link #id}`.
*
* Also see {@link #id}, {@link Ext.Container#query}, {@link Ext.Container#down} and {@link Ext.Container#child}.
*
* @accessor
*/
itemId: undefined,
/**
* @cfg {Ext.data.Model} record A model instance which updates the Component's html based on it's tpl. Similar to the data
* configuration, but tied to to a record to make allow dynamic updates. This must be a model
* instance and not a configuration of one.
* @accessor
*/
record: null,
/**
* @cfg {Object/Array} plugins
* @accessor
* An object or array of objects that will provide custom functionality for this component. The only
* requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
*
* When a component is created, if any plugins are available, the component will call the init method on each
* plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
* component as needed to provide its functionality.
*
* For examples of plugins, see Ext.plugin.PullRefresh and Ext.plugin.ListPaging
*
* ## Example code
*
* A plugin by alias:
*
* Ext.create('Ext.dataview.List', {
* config: {
* plugins: 'listpaging',
* itemTpl: '{title}
',
* store: 'Items'
* }
* });
*
* Multiple plugins by alias:
*
* Ext.create('Ext.dataview.List', {
* config: {
* plugins: ['listpaging', 'pullrefresh'],
* itemTpl: '{title}
',
* store: 'Items'
* }
* });
*
* Single plugin by class name with config options:
*
* Ext.create('Ext.dataview.List', {
* config: {
* plugins: {
* xclass: 'Ext.plugin.ListPaging', // Reference plugin by class
* autoPaging: true
* },
*
* itemTpl: '{title}
',
* store: 'Items'
* }
* });
*
* Multiple plugins by class name with config options:
*
* Ext.create('Ext.dataview.List', {
* config: {
* plugins: [
* {
* xclass: 'Ext.plugin.PullRefresh',
* pullRefreshText: 'Pull to refresh...'
* },
* {
* xclass: 'Ext.plugin.ListPaging',
* autoPaging: true
* }
* ],
*
* itemTpl: '{title}
',
* store: 'Items'
* }
* });
*
*/
plugins: null
},
/**
* @event show
* Fires whenever the Component is shown
* @param {Ext.Component} this The component instance
*/
/**
* @event hide
* Fires whenever the Component is hidden
* @param {Ext.Component} this The component instance
*/
/**
* @event fullscreen
* Fires whenever a Component with the fullscreen config is instantiated
* @param {Ext.Component} this The component instance
*/
/**
* @event floatingchange
* Fires whenever there is a change in the floating status of a component
* @param {Ext.Component} this The component instance
* @param {Boolean} floating The component's new floating state
*/
/**
* @event destroy
* Fires when the component is destroyed
*/
/**
* @event beforeorientationchange
* Fires before orientation changes.
* @removed 2.0.0 This event is now only available `onBefore` the Viewport's {@link Ext.Viewport#orientationchange}
*/
/**
* @event orientationchange
* Fires when orientation changes.
* @removed 2.0.0 This event is now only available on the Viewport's {@link Ext.Viewport#orientationchange}
*/
/**
* @event initialize
* Fires when the component has been initialized
* @param {Ext.Component} this The component instance
*/
/**
* @event painted
* @inheritdoc Ext.dom.Element#painted
* @param {Ext.Element} element The component's outer element (this.element)
*/
/**
* @event erased
* Fires when the component is no longer displayed in the DOM. Listening to this event will
* degrade performance not recommend for general use.
* @param {Ext.Component} this The component instance
*/
/**
* @event resize
* @inheritdoc Ext.dom.Element#resize
* @param {Ext.Element} element The component's outer element (this.element)
*/
/**
* @private
*/
listenerOptionsRegex: /^(?:delegate|single|delay|buffer|args|prepend|element)$/,
/**
* @private
*/
alignmentRegex: /^([a-z]+)-([a-z]+)(\?)?$/,
/**
* @private
*/
isComponent: true,
/**
* @private
*/
floating: false,
/**
* @private
*/
rendered: false,
/**
* @private
*/
isInner: true,
/**
* @private
*/
activeAnimation: null,
/**
* @readonly
* @private
*/
dockPositions: {
top: true,
right: true,
bottom: true,
left: true
},
innerElement: null,
element: null,
template: [],
widthLayoutSized: false,
heightLayoutSized: false,
layoutStretched: false,
sizeState: false,
sizeFlags: 0x0,
LAYOUT_WIDTH: 0x1,
LAYOUT_HEIGHT: 0x2,
LAYOUT_BOTH: 0x3,
LAYOUT_STRETCHED: 0x4,
/**
* Creates new Component.
* @param {Object} config The standard configuration object.
*/
constructor: function(config) {
var me = this,
currentConfig = me.config,
id;
me.onInitializedListeners = [];
me.initialConfig = config;
if (config !== undefined && 'id' in config) {
id = config.id;
}
else if ('id' in currentConfig) {
id = currentConfig.id;
}
else {
id = me.getId();
}
me.id = id;
me.setId(id);
Ext.ComponentManager.register(me);
me.initElement();
me.initConfig(me.initialConfig);
me.refreshSizeState = me.doRefreshSizeState;
me.refreshFloating = me.doRefreshFloating;
if (me.refreshSizeStateOnInitialized) {
me.refreshSizeState();
}
if (me.refreshFloatingOnInitialized) {
me.refreshFloating();
}
me.initialize();
me.triggerInitialized();
/**
* Force the component to take up 100% width and height available, by adding it to {@link Ext.Viewport}.
* @cfg {Boolean} fullscreen
*/
if (me.config.fullscreen) {
me.fireEvent('fullscreen', me);
}
me.fireEvent('initialize', me);
},
beforeInitConfig: function(config) {
this.beforeInitialize.apply(this, arguments);
},
/**
* @private
*/
beforeInitialize: Ext.emptyFn,
/**
* Allows addition of behavior to the rendering phase.
* @protected
* @template
*/
initialize: Ext.emptyFn,
getTemplate: function() {
return this.template;
},
/**
* @private
* @return {Object}
* @return {String} return.reference
* @return {Array} return.classList
* @return {Object} return.children
*/
getElementConfig: function() {
return {
reference: 'element',
classList: ['x-unsized'],
children: this.getTemplate()
};
},
/**
* @private
*/
triggerInitialized: function() {
var listeners = this.onInitializedListeners,
ln = listeners.length,
listener, fn, scope, args, i;
if (!this.initialized) {
this.initialized = true;
if (ln > 0) {
for (i = 0; i < ln; i++) {
listener = listeners[i];
fn = listener.fn;
scope = listener.scope;
args = listener.args;
if (typeof fn == 'string') {
scope[fn].apply(scope, args);
}
else {
fn.apply(scope, args);
}
}
listeners.length = 0;
}
}
},
/**
* @private
*/
onInitialized: function(fn, scope, args) {
var listeners = this.onInitializedListeners;
if (!scope) {
scope = this;
}
if (this.initialized) {
if (typeof fn == 'string') {
scope[fn].apply(scope, args);
}
else {
fn.apply(scope, args);
}
}
else {
listeners.push({
fn: fn,
scope: scope,
args: args
});
}
},
renderTo: function(container, insertBeforeElement) {
var dom = this.renderElement.dom,
containerDom = Ext.getDom(container),
insertBeforeChildDom = Ext.getDom(insertBeforeElement);
if (containerDom) {
if (insertBeforeChildDom) {
containerDom.insertBefore(dom, insertBeforeChildDom);
}
else {
containerDom.appendChild(dom);
}
this.setRendered(Boolean(dom.offsetParent));
}
},
/**
* @private
* @chainable
*/
setParent: function(parent) {
var currentParent = this.parent;
if (parent && currentParent && currentParent !== parent) {
currentParent.remove(this, false);
}
this.parent = parent;
return this;
},
applyPlugins: function(config) {
var ln, i, configObj;
if (!config) {
return config;
}
config = [].concat(config);
for (i = 0, ln = config.length; i < ln; i++) {
configObj = config[i];
config[i] = Ext.factory(configObj, 'Ext.plugin.Plugin', null, 'plugin');
}
return config;
},
updatePlugins: function(newPlugins, oldPlugins) {
var ln, i;
if (newPlugins) {
for (i = 0, ln = newPlugins.length; i < ln; i++) {
newPlugins[i].init(this);
}
}
if (oldPlugins) {
for (i = 0, ln = oldPlugins.length; i < ln; i++) {
Ext.destroy(oldPlugins[i]);
}
}
},
updateRenderTo: function(newContainer) {
this.renderTo(newContainer);
},
updateStyle: function(style) {
this.element.applyStyles(style);
},
updateBorder: function(border) {
this.element.setBorder(border);
},
updatePadding: function(padding) {
this.innerElement.setPadding(padding);
},
updateMargin: function(margin) {
this.element.setMargin(margin);
},
updateUi: function(newUi, oldUi) {
var baseCls = this.getBaseCls(),
element = this.element,
currentUi = this.currentUi;
if (baseCls) {
if (oldUi) {
if (currentUi) {
element.removeCls(currentUi);
}
else {
element.removeCls(baseCls + '-' + oldUi);
}
}
if (newUi) {
element.addCls(newUi, baseCls);
this.currentUi = baseCls + '-' + newUi;
// The first instance gets stored on the proptotype
if (!this.self.prototype.currentUi) {
this.self.prototype.currentUi = this.currentUi;
}
}
}
},
applyBaseCls: function(baseCls) {
return baseCls || clsPrefix + this.xtype;
},
updateBaseCls: function(newBaseCls, oldBaseCls) {
var me = this,
ui = me.getUi();
if (oldBaseCls) {
this.element.removeCls(oldBaseCls);
if (ui) {
this.element.removeCls(this.currentUi);
}
}
if (newBaseCls) {
this.element.addCls(newBaseCls);
if (ui) {
this.element.addCls(newBaseCls, null, ui);
this.currentUi = newBaseCls + '-' + ui;
}
}
},
/**
* Adds a CSS class (or classes) to this Component's rendered element.
* @param {String} cls The CSS class to add.
* @param {String} [prefix=""] Optional prefix to add to each class.
* @param {String} [suffix=""] Optional suffix to add to each class.
*/
addCls: function(cls, prefix, suffix) {
var oldCls = this.getCls(),
newCls = (oldCls) ? oldCls.slice() : [],
ln, i, cachedCls;
prefix = prefix || '';
suffix = suffix || '';
if (typeof cls == "string") {
cls = [cls];
}
ln = cls.length;
//check if there is currently nothing in the array and we don't need to add a prefix or a suffix.
//if true, we can just set the newCls value to the cls property, because that is what the value will be
//if false, we need to loop through each and add them to the newCls array
if (!newCls.length && prefix === '' && suffix === '') {
newCls = cls;
} else {
for (i = 0; i < ln; i++) {
cachedCls = prefix + cls[i] + suffix;
if (newCls.indexOf(cachedCls) == -1) {
newCls.push(cachedCls);
}
}
}
this.setCls(newCls);
},
/**
* Removes the given CSS class(es) from this Component's rendered element.
* @param {String} cls The class(es) to remove.
* @param {String} [prefix=""] Optional prefix to prepend before each class.
* @param {String} [suffix=""] Optional suffix to append to each class.
*/
removeCls: function(cls, prefix, suffix) {
var oldCls = this.getCls(),
newCls = (oldCls) ? oldCls.slice() : [],
ln, i;
prefix = prefix || '';
suffix = suffix || '';
if (typeof cls == "string") {
newCls = Ext.Array.remove(newCls, prefix + cls + suffix);
} else {
ln = cls.length;
for (i = 0; i < ln; i++) {
newCls = Ext.Array.remove(newCls, prefix + cls[i] + suffix);
}
}
this.setCls(newCls);
},
/**
* Replaces specified classes with the newly specified classes.
* It uses the {@link #addCls} and {@link #removeCls} methods, so if the class(es) you are removing don't exist, it will
* still add the new classes.
* @param {String} oldCls The class(es) to remove.
* @param {String} newCls The class(es) to add.
* @param {String} [prefix=""] Optional prefix to prepend before each class.
* @param {String} [suffix=""] Optional suffix to append to each class.
*/
replaceCls: function(oldCls, newCls, prefix, suffix) {
// We could have just called {@link #removeCls} and {@link #addCls}, but that would mean {@link #updateCls}
// would get called twice, which would have performance implications because it will update the dom.
var cls = this.getCls(),
array = (cls) ? cls.slice() : [],
ln, i, cachedCls;
prefix = prefix || '';
suffix = suffix || '';
//remove all oldCls
if (typeof oldCls == "string") {
array = Ext.Array.remove(array, prefix + oldCls + suffix);
} else if (oldCls) {
ln = oldCls.length;
for (i = 0; i < ln; i++) {
array = Ext.Array.remove(array, prefix + oldCls[i] + suffix);
}
}
//add all newCls
if (typeof newCls == "string") {
array.push(prefix + newCls + suffix);
} else if (newCls) {
ln = newCls.length;
//check if there is currently nothing in the array and we don't need to add a prefix or a suffix.
//if true, we can just set the array value to the newCls property, because that is what the value will be
//if false, we need to loop through each and add them to the array
if (!array.length && prefix === '' && suffix === '') {
array = newCls;
} else {
for (i = 0; i < ln; i++) {
cachedCls = prefix + newCls[i] + suffix;
if (array.indexOf(cachedCls) == -1) {
array.push(cachedCls);
}
}
}
}
this.setCls(array);
},
/**
* Add or removes a class based on if the class is already added to the Component.
*
* @param {String} className The class to toggle.
* @chainable
*/
toggleCls: function(className, /* private */ force) {
var oldCls = this.getCls(),
newCls = (oldCls) ? oldCls.slice() : [];
if (force || newCls.indexOf(className) == -1) {
newCls.push(className);
} else {
Ext.Array.remove(newCls, className);
}
this.setCls(newCls);
return this;
},
/**
* @private
* Checks if the `cls` is a string. If it is, changed it into an array.
* @param {String/Array} cls
* @return {Array/null}
*/
applyCls: function(cls) {
if (typeof cls == "string") {
cls = [cls];
}
//reset it back to null if there is nothing.
if (!cls || !cls.length) {
cls = null;
}
return cls;
},
/**
* @private
* All cls methods directly report to the {@link #cls} configuration, so anytime it changes, {@link #updateCls} will be called
*/
updateCls: function (newCls, oldCls) {
if (this.element && ((newCls && !oldCls) || (!newCls && oldCls) || newCls.length != oldCls.length || Ext.Array.difference(newCls,
oldCls).length > 0)) {
this.element.replaceCls(oldCls, newCls);
}
},
/**
* Updates the {@link #styleHtmlCls} configuration
*/
updateStyleHtmlCls: function(newHtmlCls, oldHtmlCls) {
var innerHtmlElement = this.innerHtmlElement,
innerElement = this.innerElement;
if (this.getStyleHtmlContent() && oldHtmlCls) {
if (innerHtmlElement) {
innerHtmlElement.replaceCls(oldHtmlCls, newHtmlCls);
} else {
innerElement.replaceCls(oldHtmlCls, newHtmlCls);
}
}
},
applyStyleHtmlContent: function(config) {
return Boolean(config);
},
updateStyleHtmlContent: function(styleHtmlContent) {
var htmlCls = this.getStyleHtmlCls(),
innerElement = this.innerElement,
innerHtmlElement = this.innerHtmlElement;
if (styleHtmlContent) {
if (innerHtmlElement) {
innerHtmlElement.addCls(htmlCls);
} else {
innerElement.addCls(htmlCls);
}
} else {
if (innerHtmlElement) {
innerHtmlElement.removeCls(htmlCls);
} else {
innerElement.addCls(htmlCls);
}
}
},
applyContentEl: function(contentEl) {
if (contentEl) {
return Ext.get(contentEl);
}
},
updateContentEl: function(newContentEl, oldContentEl) {
if (oldContentEl) {
oldContentEl.hide();
Ext.getBody().append(oldContentEl);
}
if (newContentEl) {
this.setHtml(newContentEl.dom);
newContentEl.show();
}
},
/**
* Returns the height and width of the Component.
* @return {Object} The current `height` and `width` of the Component.
* @return {Number} return.width
* @return {Number} return.height
*/
getSize: function() {
return {
width: this.getWidth(),
height: this.getHeight()
};
},
/**
* @private
* @return {Boolean}
*/
isCentered: function() {
return Boolean(this.getCentered());
},
isFloating: function() {
return this.floating;
},
isDocked: function() {
return Boolean(this.getDocked());
},
isInnerItem: function() {
return this.isInner;
},
setIsInner: function(isInner) {
if (isInner !== this.isInner) {
this.isInner = isInner;
if (this.initialized) {
this.fireEvent('innerstatechange', this, isInner);
}
}
},
filterLengthValue: function(value) {
if (value === 'auto' || (!value && value !== 0)) {
return null;
}
return value;
},
applyTop: function(top) {
return this.filterLengthValue(top);
},
applyRight: function(right) {
return this.filterLengthValue(right);
},
applyBottom: function(bottom) {
return this.filterLengthValue(bottom);
},
applyLeft: function(left) {
return this.filterLengthValue(left);
},
applyWidth: function(width) {
return this.filterLengthValue(width);
},
applyHeight: function(height) {
return this.filterLengthValue(height);
},
applyMinWidth: function(width) {
return this.filterLengthValue(width);
},
applyMinHeight: function(height) {
return this.filterLengthValue(height);
},
applyMaxWidth: function(width) {
return this.filterLengthValue(width);
},
applyMaxHeight: function(height) {
return this.filterLengthValue(height);
},
doSetTop: function(top) {
this.element.setTop(top);
this.refreshFloating();
},
doSetRight: function(right) {
this.element.setRight(right);
this.refreshFloating();
},
doSetBottom: function(bottom) {
this.element.setBottom(bottom);
this.refreshFloating();
},
doSetLeft: function(left) {
this.element.setLeft(left);
this.refreshFloating();
},
doSetWidth: function(width) {
this.element.setWidth(width);
this.refreshSizeState();
},
doSetHeight: function(height) {
this.element.setHeight(height);
this.refreshSizeState();
},
applyFlex: function(flex) {
if (flex) {
flex = Number(flex);
if (isNaN(flex)) {
flex = null;
}
}
else {
flex = null
}
return flex;
},
doSetFlex: Ext.emptyFn,
refreshSizeState: function() {
this.refreshSizeStateOnInitialized = true;
},
doRefreshSizeState: function() {
var hasWidth = this.getWidth() !== null || this.widthLayoutSized || (this.getLeft() !== null && this.getRight() !== null),
hasHeight = this.getHeight() !== null || this.heightLayoutSized || (this.getTop() !== null && this.getBottom() !== null),
stretched = this.layoutStretched || this.hasCSSMinHeight || (!hasHeight && this.getMinHeight() !== null),
state = hasWidth && hasHeight,
flags = (hasWidth && this.LAYOUT_WIDTH) | (hasHeight && this.LAYOUT_HEIGHT) | (stretched && this.LAYOUT_STRETCHED);
if (!state && stretched) {
state = null;
}
this.setSizeState(state);
this.setSizeFlags(flags);
},
setLayoutSizeFlags: function(flags) {
this.layoutStretched = !!(flags & this.LAYOUT_STRETCHED);
this.widthLayoutSized = !!(flags & this.LAYOUT_WIDTH);
this.heightLayoutSized = !!(flags & this.LAYOUT_HEIGHT);
this.refreshSizeState();
},
setSizeFlags: function(flags) {
if (flags !== this.sizeFlags) {
this.sizeFlags = flags;
var hasWidth = !!(flags & this.LAYOUT_WIDTH),
hasHeight = !!(flags & this.LAYOUT_HEIGHT),
stretched = !!(flags & this.LAYOUT_STRETCHED);
if (hasWidth && !stretched && !hasHeight) {
this.element.addCls('x-has-width');
}
else {
this.element.removeCls('x-has-width');
}
if (hasHeight && !stretched && !hasWidth) {
this.element.addCls('x-has-height');
}
else {
this.element.removeCls('x-has-height');
}
if (this.initialized) {
this.fireEvent('sizeflagschange', this, flags);
}
}
},
getSizeFlags: function() {
if (!this.initialized) {
this.doRefreshSizeState();
}
return this.sizeFlags;
},
setSizeState: function(state) {
if (state !== this.sizeState) {
this.sizeState = state;
this.element.setSizeState(state);
if (this.initialized) {
this.fireEvent('sizestatechange', this, state);
}
}
},
getSizeState: function() {
if (!this.initialized) {
this.doRefreshSizeState();
}
return this.sizeState;
},
doSetMinWidth: function(width) {
this.element.setMinWidth(width);
},
doSetMinHeight: function(height) {
this.element.setMinHeight(height);
this.refreshSizeState();
},
doSetMaxWidth: function(width) {
this.element.setMaxWidth(width);
},
doSetMaxHeight: function(height) {
this.element.setMaxHeight(height);
},
/**
* @private
* @param {Boolean} centered
* @return {Boolean}
*/
applyCentered: function(centered) {
centered = Boolean(centered);
if (centered) {
this.refreshInnerState = Ext.emptyFn;
if (this.isFloating()) {
this.resetFloating();
}
if (this.isDocked()) {
this.setDocked(false);
}
this.setIsInner(false);
delete this.refreshInnerState;
}
return centered;
},
doSetCentered: function(centered) {
this.toggleCls(this.getFloatingCls(), centered);
if (!centered) {
this.refreshInnerState();
}
},
applyDocked: function(docked) {
if (!docked) {
return null;
}
//
if (!/^(top|right|bottom|left)$/.test(docked)) {
Ext.Logger.error("Invalid docking position of '" + docked.position + "', must be either 'top', 'right', 'bottom', " +
"'left' or `null` (for no docking)", this);
return;
}
//
this.refreshInnerState = Ext.emptyFn;
if (this.isFloating()) {
this.resetFloating();
}
if (this.isCentered()) {
this.setCentered(false);
}
this.setIsInner(false);
delete this.refreshInnerState;
return docked;
},
doSetDocked: function(docked, oldDocked) {
this.fireEvent('afterdockedchange', this, docked, oldDocked);
if (!docked) {
this.refreshInnerState();
}
},
/**
* Resets {@link #top}, {@link #right}, {@link #bottom} and {@link #left} configurations to `null`, which
* will un-float this component.
*/
resetFloating: function() {
this.setTop(null);
this.setRight(null);
this.setBottom(null);
this.setLeft(null);
},
refreshInnerState: function() {
this.setIsInner(!this.isCentered() && !this.isFloating() && !this.isDocked());
},
refreshFloating: function() {
this.refreshFloatingOnInitialized = true;
},
doRefreshFloating: function() {
var floating = true,
floatingCls = this.getFloatingCls();
if (this.getTop() === null && this.getBottom() === null &&
this.getRight() === null && this.getLeft() === null) {
floating = false;
}
else {
this.refreshSizeState();
}
if (floating !== this.floating) {
this.floating = floating;
if (floating) {
this.refreshInnerState = Ext.emptyFn;
if (this.isCentered()) {
this.setCentered(false);
}
if (this.isDocked()) {
this.setDocked(false);
}
this.setIsInner(false);
delete this.refreshInnerState;
}
this.element.toggleCls(floatingCls, floating);
if (this.initialized) {
this.fireEvent('floatingchange', this, floating);
}
if (!floating) {
this.refreshInnerState();
}
}
},
/**
* Updates the floatingCls if the component is currently floating
* @private
*/
updateFloatingCls: function(newFloatingCls, oldFloatingCls) {
if (this.isFloating()) {
this.replaceCls(oldFloatingCls, newFloatingCls);
}
},
applyDisabled: function(disabled) {
return Boolean(disabled);
},
doSetDisabled: function(disabled) {
this.element[disabled ? 'addCls' : 'removeCls'](this.getDisabledCls());
},
updateDisabledCls: function(newDisabledCls, oldDisabledCls) {
if (this.isDisabled()) {
this.element.replaceCls(oldDisabledCls, newDisabledCls);
}
},
/**
* Disables this Component
*/
disable: function() {
this.setDisabled(true);
},
/**
* Enables this Component
*/
enable: function() {
this.setDisabled(false);
},
/**
* Returns `true` if this Component is currently disabled.
* @return {Boolean} `true` if currently disabled.
*/
isDisabled: function() {
return this.getDisabled();
},
applyZIndex: function(zIndex) {
if (!zIndex && zIndex !== 0) {
zIndex = null;
}
if (zIndex !== null) {
zIndex = Number(zIndex);
if (isNaN(zIndex)) {
zIndex = null;
}
}
return zIndex;
},
updateZIndex: function(zIndex) {
var element = this.element,
domStyle;
if (element && !element.isDestroyed) {
domStyle = element.dom.style;
if (zIndex !== null) {
domStyle.setProperty('z-index', zIndex, 'important');
}
else {
domStyle.removeProperty('z-index');
}
}
},
getInnerHtmlElement: function() {
var innerHtmlElement = this.innerHtmlElement,
styleHtmlCls;
if (!innerHtmlElement || !innerHtmlElement.dom || !innerHtmlElement.dom.parentNode) {
this.innerHtmlElement = innerHtmlElement = Ext.Element.create({ cls: 'x-innerhtml' });
if (this.getStyleHtmlContent()) {
styleHtmlCls = this.getStyleHtmlCls();
this.innerHtmlElement.addCls(styleHtmlCls);
this.innerElement.removeCls(styleHtmlCls);
}
this.innerElement.appendChild(innerHtmlElement);
}
return innerHtmlElement;
},
updateHtml: function(html) {
if (!this.isDestroyed) {
var innerHtmlElement = this.getInnerHtmlElement();
if (Ext.isElement(html)){
innerHtmlElement.setHtml('');
innerHtmlElement.append(html);
}
else {
innerHtmlElement.setHtml(html);
}
}
},
applyHidden: function(hidden) {
return Boolean(hidden);
},
doSetHidden: function(hidden) {
var element = this.renderElement;
if (element.isDestroyed) {
return;
}
if (hidden) {
element.hide();
}
else {
element.show();
}
if (this.element) {
this.element[hidden ? 'addCls' : 'removeCls'](this.getHiddenCls());
}
this.fireEvent(hidden ? 'hide' : 'show', this);
},
updateHiddenCls: function(newHiddenCls, oldHiddenCls) {
if (this.isHidden()) {
this.element.replaceCls(oldHiddenCls, newHiddenCls);
}
},
/**
* Returns `true` if this Component is currently hidden.
* @return {Boolean} `true` if currently hidden.
*/
isHidden: function() {
return this.getHidden();
},
/**
* Hides this Component optionally using an animation.
* @param {Object/Boolean} [animation] You can specify an animation here or a bool to use the {@link #hideAnimation} config.
* @return {Ext.Component}
* @chainable
*/
hide: function(animation) {
this.setCurrentAlignmentInfo(null);
if(this.activeAnimation) {
this.activeAnimation.on({
animationend: function(){
this.hide(animation);
},
scope: this,
single: true
});
return this;
}
if (!this.getHidden()) {
if (animation === undefined || (animation && animation.isComponent)) {
animation = this.getHideAnimation();
}
if (animation) {
if (animation === true) {
animation = 'fadeOut';
}
this.onBefore({
hiddenchange: 'animateFn',
scope: this,
single: true,
args: [animation]
});
}
this.setHidden(true);
}
return this;
},
/**
* Shows this component optionally using an animation.
* @param {Object/Boolean} [animation] You can specify an animation here or a bool to use the {@link #showAnimation} config.
* @return {Ext.Component}
* @chainable
*/
show: function(animation) {
if(this.activeAnimation) {
this.activeAnimation.on({
animationend: function(){
this.show(animation);
},
scope: this,
single: true
});
return this;
}
var hidden = this.getHidden();
if (hidden || hidden === null) {
if (animation === true) {
animation = 'fadeIn';
}
else if (animation === undefined || (animation && animation.isComponent)) {
animation = this.getShowAnimation();
}
if (animation) {
this.beforeShowAnimation();
this.onBefore({
hiddenchange: 'animateFn',
scope: this,
single: true,
args: [animation]
});
}
this.setHidden(false);
}
return this;
},
beforeShowAnimation: function() {
if (this.element) {
this.renderElement.show();
this.element.removeCls(this.getHiddenCls());
}
},
animateFn: function(animation, component, newState, oldState, options, controller) {
var me = this;
if (animation && (!newState || (newState && this.isPainted()))) {
this.activeAnimation = new Ext.fx.Animation(animation);
this.activeAnimation.setElement(component.element);
if (!Ext.isEmpty(newState)) {
this.activeAnimation.setOnEnd(function() {
me.activeAnimation = null;
controller.resume();
});
controller.pause();
}
Ext.Animator.run(me.activeAnimation);
}
},
/**
* @private
*/
setVisibility: function(isVisible) {
this.renderElement.setVisibility(isVisible);
},
/**
* @private
*/
isRendered: function() {
return this.rendered;
},
/**
* @private
*/
isPainted: function() {
return this.renderElement.isPainted();
},
/**
* @private
*/
applyTpl: function(config) {
return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
},
applyData: function(data) {
if (Ext.isObject(data)) {
return Ext.apply({}, data);
} else if (!data) {
data = {};
}
return data;
},
/**
* @private
*/
updateData: function(newData) {
var me = this;
if (newData) {
var tpl = me.getTpl(),
tplWriteMode = me.getTplWriteMode();
if (tpl) {
tpl[tplWriteMode](me.getInnerHtmlElement(), newData);
}
/**
* @event updatedata
* Fires whenever the data of the component is updated
* @param {Ext.Component} this The component instance
* @param {Object} newData The new data
*/
this.fireEvent('updatedata', me, newData);
}
},
applyRecord: function(config) {
if (config && Ext.isObject(config) && config.isModel) {
return config;
}
return null;
},
updateRecord: function(newRecord, oldRecord) {
var me = this;
if (oldRecord) {
oldRecord.unjoin(me);
}
if (!newRecord) {
me.updateData('');
}
else {
newRecord.join(me);
me.updateData(newRecord.getData(true));
}
},
// @private Used to handle joining of a record to a tpl
afterEdit: function() {
this.updateRecord(this.getRecord());
},
// @private Used to handle joining of a record to a tpl
afterErase: function() {
this.setRecord(null);
},
applyItemId: function(itemId) {
return itemId || this.getId();
},
/**
* Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
* from the xtype (default) or whether it is directly of the xtype specified (`shallow = true`).
* __If using your own subclasses, be aware that a Component must register its own xtype
* to participate in determination of inherited xtypes.__
*
* For a list of all available xtypes, see the {@link Ext.Component} header.
*
* Example usage:
*
* var t = new Ext.field.Text();
* var isText = t.isXType('textfield'); // true
* var isBoxSubclass = t.isXType('field'); // true, descended from Ext.field.Field
* var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.field.Field instance
*
* @param {String} xtype The xtype to check for this Component.
* @param {Boolean} shallow (optional) `false` to check whether this Component is descended from the xtype (this is
* the default), or `true` to check whether this Component is directly of the specified xtype.
* @return {Boolean} `true` if this component descends from the specified xtype, `false` otherwise.
*/
isXType: function(xtype, shallow) {
if (shallow) {
return this.xtypes.indexOf(xtype) != -1;
}
return Boolean(this.xtypesMap[xtype]);
},
/**
* Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
* available xtypes, see the {@link Ext.Component} header.
*
* __Note:__ If using your own subclasses, be aware that a Component must register its own xtype
* to participate in determination of inherited xtypes.
*
* Example usage:
*
* var t = new Ext.field.Text();
* alert(t.getXTypes()); // alerts 'component/field/textfield'
*
* @return {String} The xtype hierarchy string.
*/
getXTypes: function() {
return this.xtypesChain.join('/');
},
getDraggableBehavior: function() {
var behavior = this.draggableBehavior;
if (!behavior) {
behavior = this.draggableBehavior = new Ext.behavior.Draggable(this);
}
return behavior;
},
applyDraggable: function(config) {
this.getDraggableBehavior().setConfig(config);
},
getDraggable: function() {
return this.getDraggableBehavior().getDraggable();
},
getTranslatableBehavior: function() {
var behavior = this.translatableBehavior;
if (!behavior) {
behavior = this.translatableBehavior = new Ext.behavior.Translatable(this);
}
return behavior;
},
applyTranslatable: function(config) {
this.getTranslatableBehavior().setConfig(config);
},
getTranslatable: function() {
return this.getTranslatableBehavior().getTranslatable();
},
translateAxis: function(axis, value, animation) {
var x, y;
if (axis === 'x') {
x = value;
}
else {
y = value;
}
return this.translate(x, y, animation);
},
translate: function() {
var translatable = this.getTranslatable();
if (!translatable) {
this.setTranslatable(true);
translatable = this.getTranslatable();
}
translatable.translate.apply(translatable, arguments);
},
/**
* @private
* @param {Boolean} rendered
*/
setRendered: function(rendered) {
var wasRendered = this.rendered;
if (rendered !== wasRendered) {
this.rendered = rendered;
return true;
}
return false;
},
/**
* Sets the size of the Component.
* @param {Number} width The new width for the Component.
* @param {Number} height The new height for the Component.
*/
setSize: function(width, height) {
if (width != undefined) {
this.setWidth(width);
}
if (height != undefined) {
this.setHeight(height);
}
},
//@private
doAddListener: function(name, fn, scope, options, order) {
if (options && 'element' in options) {
//
if (this.referenceList.indexOf(options.element) === -1) {
Ext.Logger.error("Adding event listener with an invalid element reference of '" + options.element +
"' for this component. Available values are: '" + this.referenceList.join("', '") + "'", this);
}
//
// The default scope is this component
return this[options.element].doAddListener(name, fn, scope || this, options, order);
}
if (name == 'painted' || name == 'resize') {
return this.element.doAddListener(name, fn, scope || this, options, order);
}
return this.callParent(arguments);
},
//@private
doRemoveListener: function(name, fn, scope, options, order) {
if (options && 'element' in options) {
//
if (this.referenceList.indexOf(options.element) === -1) {
Ext.Logger.error("Removing event listener with an invalid element reference of '" + options.element +
"' for this component. Available values are: '" + this.referenceList.join('", "') + "'", this);
}
//
// The default scope is this component
this[options.element].doRemoveListener(name, fn, scope || this, options, order);
}
return this.callParent(arguments);
},
/**
* Shows this component by another component. If you specify no alignment, it will automatically
* position this component relative to the reference component.
*
* For example, say we are aligning a Panel next to a Button, the alignment string would look like this:
*
* [panel-vertical (t/b/c)][panel-horizontal (l/r/c)]-[button-vertical (t/b/c)][button-horizontal (l/r/c)]
*
* where t = top, b = bottom, c = center, l = left, r = right.
*
* ## Examples
*
* - `tl-tr` means top-left corner of the Panel to the top-right corner of the Button
* - `tc-bc` means top-center of the Panel to the bottom-center of the Button
*
* You can put a '?' at the end of the alignment string to constrain the floating element to the
* {@link Ext.Viewport Viewport}
*
* // show `panel` by `button` using the default positioning (auto fit)
* panel.showBy(button);
*
* // align the top left corner of `panel` with the top right corner of `button` (constrained to viewport)
* panel.showBy(button, "tl-tr?");
*
* // align the bottom right corner of `panel` with the center left edge of `button` (not constrained by viewport)
* panel.showBy(button, "br-cl");
*
* @param {Ext.Component} component The target component to show this component by.
* @param {String} alignment (optional) The specific alignment.
*/
showBy: function(component, alignment) {
var me = this,
viewport = Ext.Viewport,
parent = me.getParent();
me.setVisibility(false);
if (parent !== viewport) {
viewport.add(me);
}
me.show();
me.on({
hide: 'onShowByErased',
destroy: 'onShowByErased',
single: true,
scope: me
});
viewport.on('resize', 'alignTo', me, { args: [component, alignment] });
me.alignTo(component, alignment);
me.setVisibility(true);
},
/**
* @private
* @param {Ext.Component} component
*/
onShowByErased: function() {
Ext.Viewport.un('resize', 'alignTo', this);
},
/**
* Prepares information on aligning this to component using alignment.
* Also checks to see if this is already aligned to component according to alignment.
* @protected
*/
getAlignmentInfo: function (component, alignment){
var alignToElement = component.isComponent ? component.renderElement : component,
alignToBox = alignToElement.getPageBox(),
element = this.renderElement,
box = element.getPageBox(),
stats = {
alignToBox: alignToBox,
alignment: alignment,
top: alignToBox.top,
left: alignToBox.left,
alignToWidth: alignToBox.width,
alignToHeight: alignToBox.height,
width: box.width,
height: box.height
},
currentAlignmentInfo = this.getCurrentAlignmentInfo(),
isAligned = true;
if (!Ext.isEmpty(currentAlignmentInfo)) {
Ext.Object.each(stats, function(key, value) {
if (!Ext.isObject(value) && currentAlignmentInfo[key] != value) {
isAligned = false;
return false;
}
return true;
});
} else {
isAligned = false;
}
return {isAligned: isAligned, stats: stats};
},
/**
* Current Alignment information from the last alignTo call
* @private
*/
getCurrentAlignmentInfo: function() {
return this.$currentAlignmentInfo;
},
/**
* Sets the current Alignment information, called by alignTo
* @private
*/
setCurrentAlignmentInfo: function(alignmentInfo) {
this.$currentAlignmentInfo = Ext.isEmpty(alignmentInfo) ? null : Ext.merge({}, alignmentInfo.stats ? alignmentInfo.stats : alignmentInfo);
},
/**
* @private
*/
alignTo: function(component, alignment) {
var alignmentInfo = this.getAlignmentInfo(component, alignment);
if(alignmentInfo.isAligned) return;
var alignToBox = alignmentInfo.stats.alignToBox,
constrainBox = this.getParent().element.getPageBox(),
alignToHeight = alignmentInfo.stats.alignToHeight,
alignToWidth = alignmentInfo.stats.alignToWidth,
height = alignmentInfo.stats.height,
width = alignmentInfo.stats.width;
// Keep off the sides...
constrainBox.bottom -= 5;
constrainBox.height -= 10;
constrainBox.left += 5;
constrainBox.right -= 5;
constrainBox.top += 5;
constrainBox.width -= 10;
if (!alignment || alignment === 'auto') {
if (constrainBox.bottom - alignToBox.bottom < height) {
if (alignToBox.top - constrainBox.top < height) {
if (alignToBox.left - constrainBox.left < width) {
alignment = 'cl-cr?';
}
else {
alignment = 'cr-cl?';
}
}
else {
alignment = 'bc-tc?';
}
}
else {
alignment = 'tc-bc?';
}
}
var matches = alignment.match(this.alignmentRegex);
//
if (!matches) {
Ext.Logger.error("Invalid alignment value of '" + alignment + "'");
}
//
var from = matches[1].split(''),
to = matches[2].split(''),
constrained = (matches[3] === '?'),
fromVertical = from[0],
fromHorizontal = from[1] || fromVertical,
toVertical = to[0],
toHorizontal = to[1] || toVertical,
top = alignToBox.top,
left = alignToBox.left,
halfAlignHeight = alignToHeight / 2,
halfAlignWidth = alignToWidth / 2,
halfWidth = width / 2,
halfHeight = height / 2,
maxLeft, maxTop;
switch (fromVertical) {
case 't':
switch (toVertical) {
case 'c':
top += halfAlignHeight;
break;
case 'b':
top += alignToHeight;
}
break;
case 'b':
switch (toVertical) {
case 'c':
top -= (height - halfAlignHeight);
break;
case 't':
top -= height;
break;
case 'b':
top -= height - alignToHeight;
}
break;
case 'c':
switch (toVertical) {
case 't':
top -= halfHeight;
break;
case 'c':
top -= (halfHeight - halfAlignHeight);
break;
case 'b':
top -= (halfHeight - alignToHeight);
}
break;
}
switch (fromHorizontal) {
case 'l':
switch (toHorizontal) {
case 'c':
left += halfAlignHeight;
break;
case 'r':
left += alignToWidth;
}
break;
case 'r':
switch (toHorizontal) {
case 'r':
left -= (width - alignToWidth);
break;
case 'c':
left -= (width - halfWidth);
break;
case 'l':
left -= width;
}
break;
case 'c':
switch (toHorizontal) {
case 'l':
left -= halfWidth;
break;
case 'c':
left -= (halfWidth - halfAlignWidth);
break;
case 'r':
left -= (halfWidth - alignToWidth);
}
break;
}
if (constrained) {
maxLeft = (constrainBox.left + constrainBox.width) - width;
maxTop = (constrainBox.top + constrainBox.height) - height;
left = Math.max(constrainBox.left, Math.min(maxLeft, left));
top = Math.max(constrainBox.top, Math.min(maxTop, top));
}
this.setLeft(left);
this.setTop(top);
this.setCurrentAlignmentInfo(alignmentInfo);
},
/**
* Walks up the `ownerCt` axis looking for an ancestor Container which matches
* the passed simple selector.
*
* Example:
*
* var owningTabPanel = grid.up('tabpanel');
*
* @param {String} selector (optional) The simple selector to test.
* @return {Ext.Container} The matching ancestor Container (or `undefined` if no match was found).
*/
up: function(selector) {
var result = this.parent;
if (selector) {
for (; result; result = result.parent) {
if (Ext.ComponentQuery.is(result, selector)) {
return result;
}
}
}
return result;
},
getBubbleTarget: function() {
return this.getParent();
},
/**
* Destroys this Component. If it is currently added to a Container it will first be removed from that Container.
* All Ext.Element references are also deleted and the Component is de-registered from Ext.ComponentManager
*/
destroy: function() {
this.destroy = Ext.emptyFn;
var parent = this.getParent(),
referenceList = this.referenceList,
i, ln, reference;
this.isDestroying = true;
Ext.destroy(this.getTranslatable(), this.getPlugins());
// Remove this component itself from the container if it's currently contained
if (parent) {
parent.remove(this, false);
}
// Destroy all element references
for (i = 0, ln = referenceList.length; i < ln; i++) {
reference = referenceList[i];
this[reference].destroy();
delete this[reference];
}
Ext.destroy(this.innerHtmlElement);
this.setRecord(null);
this.callSuper();
Ext.ComponentManager.unregister(this);
}
// Convert old properties in data into a config object
}, function() {
});
})(Ext.baseCSSPrefix);
/**
*
*/
Ext.define('Ext.layout.wrapper.Inner', {
config: {
sizeState: null,
container: null
},
constructor: function(config) {
this.initConfig(config);
},
getElement: function() {
return this.getContainer().bodyElement;
},
setInnerWrapper: Ext.emptyFn,
getInnerWrapper: Ext.emptyFn
});
/**
*
*/
Ext.define('Ext.layout.Abstract', {
mixins: [ Ext.mixin.Observable ],
isLayout: true,
constructor: function(config) {
this.initialConfig = config;
},
setContainer: function(container) {
this.container = container;
this.initConfig(this.initialConfig);
return this;
},
onItemAdd: function() {},
onItemRemove: function() {},
onItemMove: function() {},
onItemCenteredChange: function() {},
onItemFloatingChange: function() {},
onItemDockedChange: function() {},
onItemInnerStateChange: function() {}
});
/**
*
*/
Ext.define('Ext.mixin.Bindable', {
extend: Ext.mixin.Mixin ,
mixinConfig: {
id: 'bindable'
},
bind: function(instance, boundMethod, bindingMethod, preventDefault, extraArgs) {
if (!bindingMethod) {
bindingMethod = boundMethod;
}
var boundFn = instance[boundMethod],
fn, binding;
if (boundFn && boundFn.hasOwnProperty('$binding')) {
binding = boundFn.$binding;
if (binding.bindingMethod === bindingMethod && binding.bindingScope === this) {
return this;
}
}
instance[boundMethod] = fn = function() {
var binding = fn.$binding,
scope = binding.bindingScope,
args = Array.prototype.slice.call(arguments);
args.push(arguments);
if (extraArgs) {
args.push.apply(args, extraArgs);
}
if (!binding.preventDefault && scope[binding.bindingMethod].apply(scope, args) !== false) {
return binding.boundFn.apply(this, arguments);
}
};
fn.$binding = {
preventDefault: !!preventDefault,
boundFn: boundFn,
bindingMethod: bindingMethod,
bindingScope: this
};
return this;
},
unbind: function(instance, boundMethod, bindingMethod) {
if (!bindingMethod) {
bindingMethod = boundMethod;
}
var fn = instance[boundMethod],
binding = fn.$binding,
boundFn, currentBinding;
while (binding) {
boundFn = binding.boundFn;
if (binding.bindingMethod === bindingMethod && binding.bindingScope === this) {
if (currentBinding) {
currentBinding.boundFn = boundFn;
}
else {
instance[boundMethod] = boundFn;
}
return this;
}
currentBinding = binding;
binding = boundFn.$binding;
}
return this;
}
});
/**
*
*/
Ext.define('Ext.util.Wrapper', {
mixins: [ Ext.mixin.Bindable ],
constructor: function(elementConfig, wrappedElement) {
var element = this.link('element', Ext.Element.create(elementConfig));
if (wrappedElement) {
element.insertBefore(wrappedElement);
this.wrap(wrappedElement);
}
},
bindSize: function(sizeName) {
var wrappedElement = this.wrappedElement,
boundMethodName;
this.boundSizeName = sizeName;
this.boundMethodName = boundMethodName = sizeName === 'width' ? 'setWidth' : 'setHeight';
this.bind(wrappedElement, boundMethodName, 'onBoundSizeChange');
wrappedElement[boundMethodName].call(wrappedElement, wrappedElement.getStyleValue(sizeName));
},
onBoundSizeChange: function(size, args) {
var element = this.element;
if (typeof size === 'string' && size.substr(-1) === '%') {
args[0] = '100%';
}
else {
size = '';
}
element[this.boundMethodName].call(element, size);
},
wrap: function(wrappedElement) {
var element = this.element,
innerDom;
this.wrappedElement = wrappedElement;
innerDom = element.dom;
while (innerDom.firstElementChild !== null) {
innerDom = innerDom.firstElementChild;
}
innerDom.appendChild(wrappedElement.dom);
},
destroy: function() {
var element = this.element,
dom = element.dom,
wrappedElement = this.wrappedElement,
boundMethodName = this.boundMethodName,
parentNode = dom.parentNode,
size;
if (boundMethodName) {
this.unbind(wrappedElement, boundMethodName, 'onBoundSizeChange');
size = element.getStyle(this.boundSizeName);
if (size) {
wrappedElement[boundMethodName].call(wrappedElement, size);
}
}
if (parentNode) {
if (!wrappedElement.isDestroyed) {
parentNode.replaceChild(dom.firstElementChild, dom);
}
delete this.wrappedElement;
}
this.callSuper();
}
});
/**
*
*/
Ext.define('Ext.layout.wrapper.BoxDock', {
config: {
direction: 'horizontal',
element: {
className: 'x-dock'
},
bodyElement: {
className: 'x-dock-body'
},
innerWrapper: null,
sizeState: false,
container: null
},
positionMap: {
top: 'start',
left: 'start',
bottom: 'end',
right: 'end'
},
constructor: function(config) {
this.items = {
start: [],
end: []
};
this.itemsCount = 0;
this.initConfig(config);
},
addItems: function(items) {
var i, ln, item;
for (i = 0, ln = items.length; i < ln; i++) {
item = items[i];
this.addItem(item);
}
},
addItem: function(item) {
var docked = item.getDocked(),
position = this.positionMap[docked],
wrapper = item.$dockWrapper,
container = this.getContainer(),
index = container.indexOf(item),
element = item.element,
items = this.items,
sideItems = items[position],
i, ln, sibling, referenceElement, siblingIndex;
if (wrapper) {
wrapper.removeItem(item);
}
item.$dockWrapper = this;
item.addCls('x-dock-item');
item.addCls('x-docked-' + docked);
for (i = 0, ln = sideItems.length; i < ln; i++) {
sibling = sideItems[i];
siblingIndex = container.indexOf(sibling);
if (siblingIndex > index) {
referenceElement = sibling.element;
sideItems.splice(i, 0, item);
break;
}
}
if (!referenceElement) {
sideItems.push(item);
referenceElement = this.getBodyElement();
}
this.itemsCount++;
if (position === 'start') {
element.insertBefore(referenceElement);
}
else {
element.insertAfter(referenceElement);
}
},
removeItem: function(item) {
var position = item.getDocked(),
items = this.items[this.positionMap[position]];
Ext.Array.remove(items, item);
item.element.detach();
delete item.$dockWrapper;
item.removeCls('x-dock-item');
item.removeCls('x-docked-' + position);
if (--this.itemsCount === 0) {
this.destroy();
}
},
getItemsSlice: function(index) {
var container = this.getContainer(),
items = this.items,
slice = [],
sideItems, i, ln, item;
for (sideItems = items.start, i = 0, ln = sideItems.length; i < ln; i++) {
item = sideItems[i];
if (container.indexOf(item) > index) {
slice.push(item);
}
}
for (sideItems = items.end, i = 0, ln = sideItems.length; i < ln; i++) {
item = sideItems[i];
if (container.indexOf(item) > index) {
slice.push(item);
}
}
return slice;
},
applyElement: function(element) {
return Ext.Element.create(element);
},
updateElement: function(element) {
element.addCls('x-dock-' + this.getDirection());
},
applyBodyElement: function(bodyElement) {
return Ext.Element.create(bodyElement);
},
updateBodyElement: function(bodyElement) {
this.getElement().append(bodyElement);
},
updateInnerWrapper: function(innerWrapper, oldInnerWrapper) {
var bodyElement = this.getBodyElement();
if (oldInnerWrapper && oldInnerWrapper.$outerWrapper === this) {
oldInnerWrapper.getElement().detach();
delete oldInnerWrapper.$outerWrapper;
}
if (innerWrapper) {
innerWrapper.setSizeState(this.getSizeState());
innerWrapper.$outerWrapper = this;
bodyElement.append(innerWrapper.getElement());
}
},
updateSizeState: function(state) {
var innerWrapper = this.getInnerWrapper();
this.getElement().setSizeState(state);
if (innerWrapper) {
innerWrapper.setSizeState(state);
}
},
destroy: function() {
var innerWrapper = this.getInnerWrapper(),
outerWrapper = this.$outerWrapper,
innerWrapperElement;
if (innerWrapper) {
if (outerWrapper) {
outerWrapper.setInnerWrapper(innerWrapper);
}
else {
innerWrapperElement = innerWrapper.getElement();
if (!innerWrapperElement.isDestroyed) {
innerWrapperElement.replace(this.getElement());
}
delete innerWrapper.$outerWrapper;
}
}
delete this.$outerWrapper;
this.setInnerWrapper(null);
this.unlink('_bodyElement', '_element');
this.callSuper();
}
});
/**
*
*/
Ext.define('Ext.layout.Default', {
extend: Ext.layout.Abstract ,
isAuto: true,
alias: ['layout.default', 'layout.auto'],
config: {
/**
* @cfg {Ext.fx.layout.Card} animation Layout animation configuration
* Controls how layout transitions are animated. Currently only available for
* Card Layouts.
*
* Possible values are:
*
* - cover
* - cube
* - fade
* - flip
* - pop
* - reveal
* - scroll
* - slide
* @accessor
*/
animation: null
},
centerWrapperClass: 'x-center',
dockWrapperClass: 'x-dock',
positionMap: {
top: 'start',
left: 'start',
middle: 'center',
bottom: 'end',
right: 'end'
},
positionDirectionMap: {
top: 'vertical',
bottom: 'vertical',
left: 'horizontal',
right: 'horizontal'
},
setContainer: function(container) {
var options = {
delegate: '> component'
};
this.dockedItems = [];
this.callSuper(arguments);
container.on('centeredchange', 'onItemCenteredChange', this, options, 'before')
.on('floatingchange', 'onItemFloatingChange', this, options, 'before')
.on('dockedchange', 'onBeforeItemDockedChange', this, options, 'before')
.on('afterdockedchange', 'onAfterItemDockedChange', this, options);
},
monitorSizeStateChange: function() {
this.monitorSizeStateChange = Ext.emptyFn;
this.container.on('sizestatechange', 'onContainerSizeStateChange', this);
},
monitorSizeFlagsChange: function() {
this.monitorSizeFlagsChange = Ext.emptyFn;
this.container.on('sizeflagschange', 'onContainerSizeFlagsChange', this);
},
onItemAdd: function(item) {
var docked = item.getDocked();
if (docked !== null) {
this.dockItem(item);
}
else if (item.isCentered()) {
this.onItemCenteredChange(item, true);
}
else if (item.isFloating()) {
this.onItemFloatingChange(item, true);
}
else {
this.onItemInnerStateChange(item, true);
}
},
/**
* @param {Ext.Component} item
* @param {Boolean} isInner
* @param {Boolean} [destroying]
*/
onItemInnerStateChange: function(item, isInner, destroying) {
if (isInner) {
this.insertInnerItem(item, this.container.innerIndexOf(item));
}
else {
this.removeInnerItem(item);
}
},
insertInnerItem: function(item, index) {
var container = this.container,
containerDom = container.innerElement.dom,
itemDom = item.element.dom,
nextSibling = index !== -1 ? container.getInnerAt(index + 1) : null,
nextSiblingDom = null,
translatable;
if (nextSibling) {
translatable = nextSibling.getTranslatable();
if (translatable && translatable.getUseWrapper()) {
nextSiblingDom = translatable.getWrapper().dom;
}
else {
nextSiblingDom = nextSibling ? nextSibling.element.dom : null;
}
}
containerDom.insertBefore(itemDom, nextSiblingDom);
return this;
},
insertBodyItem: function(item) {
var container = this.container.setUseBodyElement(true),
bodyDom = container.bodyElement.dom;
if (item.getZIndex() === null) {
item.setZIndex((container.indexOf(item) + 1) * 2);
}
bodyDom.insertBefore(item.element.dom, bodyDom.firstChild);
return this;
},
removeInnerItem: function(item) {
item.element.detach();
},
removeBodyItem: function(item) {
item.setZIndex(null);
item.element.detach();
},
onItemRemove: function(item, index, destroying) {
var docked = item.getDocked();
if (docked) {
this.undockItem(item);
}
else if (item.isCentered()) {
this.onItemCenteredChange(item, false);
}
else if (item.isFloating()) {
this.onItemFloatingChange(item, false);
}
else {
this.onItemInnerStateChange(item, false, destroying);
}
},
onItemMove: function(item, toIndex, fromIndex) {
if (item.isCentered() || item.isFloating()) {
item.setZIndex((toIndex + 1) * 2);
}
else if (item.isInnerItem()) {
this.insertInnerItem(item, this.container.innerIndexOf(item));
}
else {
this.undockItem(item);
this.dockItem(item);
}
},
onItemCenteredChange: function(item, centered) {
var wrapperName = '$centerWrapper';
if (centered) {
this.insertBodyItem(item);
item.link(wrapperName, new Ext.util.Wrapper({
className: this.centerWrapperClass
}, item.element));
}
else {
item.unlink(wrapperName);
this.removeBodyItem(item);
}
},
onItemFloatingChange: function(item, floating) {
if (floating) {
this.insertBodyItem(item);
}
else {
this.removeBodyItem(item);
}
},
onBeforeItemDockedChange: function(item, docked, oldDocked) {
if (oldDocked) {
this.undockItem(item);
}
},
onAfterItemDockedChange: function(item, docked, oldDocked) {
if (docked) {
this.dockItem(item);
}
},
onContainerSizeStateChange: function() {
var dockWrapper = this.getDockWrapper();
if (dockWrapper) {
dockWrapper.setSizeState(this.container.getSizeState());
}
},
onContainerSizeFlagsChange: function() {
var items = this.dockedItems,
i, ln, item;
for (i = 0, ln = items.length; i < ln; i++) {
item = items[i];
this.refreshDockedItemLayoutSizeFlags(item);
}
},
refreshDockedItemLayoutSizeFlags: function(item) {
var container = this.container,
dockedDirection = this.positionDirectionMap[item.getDocked()],
binaryMask = (dockedDirection === 'horizontal') ? container.LAYOUT_HEIGHT : container.LAYOUT_WIDTH,
flags = (container.getSizeFlags() & binaryMask);
item.setLayoutSizeFlags(flags);
},
dockItem: function(item) {
var DockClass = Ext.layout.wrapper.BoxDock,
dockedItems = this.dockedItems,
ln = dockedItems.length,
container = this.container,
itemIndex = container.indexOf(item),
positionDirectionMap = this.positionDirectionMap,
direction = positionDirectionMap[item.getDocked()],
dockInnerWrapper = this.dockInnerWrapper,
referenceDirection, i, dockedItem, index, previousItem, slice,
referenceItem, referenceDocked, referenceWrapper, newWrapper, nestedWrapper, oldInnerWrapper;
this.monitorSizeStateChange();
this.monitorSizeFlagsChange();
if (!dockInnerWrapper) {
dockInnerWrapper = this.link('dockInnerWrapper', new Ext.layout.wrapper.Inner({
container: this.container
}));
}
if (ln === 0) {
dockedItems.push(item);
newWrapper = new DockClass({
container: this.container,
direction: direction
});
newWrapper.addItem(item);
newWrapper.getElement().replace(dockInnerWrapper.getElement());
newWrapper.setInnerWrapper(dockInnerWrapper);
container.onInitialized('onContainerSizeStateChange', this);
}
else {
for (i = 0; i < ln; i++) {
dockedItem = dockedItems[i];
index = container.indexOf(dockedItem);
if (index > itemIndex) {
referenceItem = previousItem || dockedItems[0];
dockedItems.splice(i, 0, item);
break;
}
previousItem = dockedItem;
}
if (!referenceItem) {
referenceItem = dockedItems[ln - 1];
dockedItems.push(item);
}
referenceDocked = referenceItem.getDocked();
referenceWrapper = referenceItem.$dockWrapper;
referenceDirection = positionDirectionMap[referenceDocked];
if (direction === referenceDirection) {
referenceWrapper.addItem(item);
}
else {
slice = referenceWrapper.getItemsSlice(itemIndex);
newWrapper = new DockClass({
container: this.container,
direction: direction
});
if (slice.length > 0) {
if (slice.length === referenceWrapper.itemsCount) {
nestedWrapper = referenceWrapper;
newWrapper.setSizeState(nestedWrapper.getSizeState());
newWrapper.getElement().replace(nestedWrapper.getElement());
}
else {
nestedWrapper = new DockClass({
container: this.container,
direction: referenceDirection
});
nestedWrapper.setInnerWrapper(referenceWrapper.getInnerWrapper());
nestedWrapper.addItems(slice);
referenceWrapper.setInnerWrapper(newWrapper);
}
newWrapper.setInnerWrapper(nestedWrapper);
}
else {
oldInnerWrapper = referenceWrapper.getInnerWrapper();
referenceWrapper.setInnerWrapper(null);
newWrapper.setInnerWrapper(oldInnerWrapper);
referenceWrapper.setInnerWrapper(newWrapper);
}
newWrapper.addItem(item);
}
}
container.onInitialized('refreshDockedItemLayoutSizeFlags', this, [item]);
},
getDockWrapper: function() {
var dockedItems = this.dockedItems;
if (dockedItems.length > 0) {
return dockedItems[0].$dockWrapper;
}
return null;
},
undockItem: function(item) {
var dockedItems = this.dockedItems;
if (item.$dockWrapper) {
item.$dockWrapper.removeItem(item);
}
Ext.Array.remove(dockedItems, item);
item.setLayoutSizeFlags(0);
},
destroy: function() {
this.dockedItems.length = 0;
delete this.dockedItems;
this.callSuper();
}
});
/**
* Box is a superclass for the two box layouts:
*
* * {@link Ext.layout.HBox hbox}
* * {@link Ext.layout.VBox vbox}
*
* Box itself is never used directly, but its subclasses provide flexible arrangement of child components
* inside a {@link Ext.Container Container}.
*
* ## Horizontal Box
*
* HBox allows you to easily lay out child components horizontally. It can size items based on a fixed width or a
* fraction of the total width available, enabling you to achieve flexible layouts that expand or contract to fill the
* space available.
*
* {@img ../guides/layouts/hbox.jpg}
*
* See the {@link Ext.layout.HBox HBox layout docs} for more information on using hboxes.
*
* ## Vertical Box
*
* VBox allows you to easily lay out child components vertically. It can size items based on a fixed height or a
* fraction of the total height available, enabling you to achieve flexible layouts that expand or contract to fill the
* space available.
*
* {@img ../guides/layouts/vbox.jpg}
*
* See the {@link Ext.layout.VBox VBox layout docs} for more information on using vboxes.
*
* For a more detailed overview of Sencha Touch 2 layouts, check out the
* [Layout Guide](../../../core_concepts/layouts.html).
*/
Ext.define('Ext.layout.Box', {
extend: Ext.layout.Default ,
config: {
orient: 'horizontal',
/**
* @cfg {String} align
* Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
*
* - ** start ** : child items are packed together at left side of container
* - ** center ** : child items are packed together at mid-width of container
* - ** end ** : child items are packed together at right side of container
* - **stretch** : child items are stretched vertically to fill the height of the container
*
* Please see the 'Pack and Align' section of the [Events Guide](../../../core_concepts/events.html) for a detailed example and
* explanation.
* @accessor
*/
align: 'start',
/**
* @cfg {String} pack
* Controls how the child items of the container are packed together. Acceptable configuration values
* for this property are:
*
* - ** start ** : child items are packed together at left side of container
* - ** center ** : child items are packed together at mid-width of container
* - ** end ** : child items are packed together at right side of container
* - ** justify ** : child items are packed evenly across the container. Uses the 'justify-content: space-between' css property
*
* Please see the 'Pack and Align' section of the [Events Guide](../../../core_concepts/events.html) for a detailed example and
* explanation.
* @accessor
*/
pack: 'start'
},
alias: 'layout.tablebox',
layoutBaseClass: 'x-layout-tablebox',
itemClass: 'x-layout-tablebox-item',
setContainer: function(container) {
this.callSuper(arguments);
container.innerElement.addCls(this.layoutBaseClass);
container.on('flexchange', 'onItemFlexChange', this, {
delegate: '> component'
});
},
onItemInnerStateChange: function(item, isInner) {
this.callSuper(arguments);
item.toggleCls(this.itemClass, isInner);
},
onItemFlexChange: function() {
}
});
/**
* AbstractBox is a superclass for the two box layouts:
*
* * {@link Ext.layout.HBox hbox}
* * {@link Ext.layout.VBox vbox}
*
* FlexBox itself is never used directly, but its subclasses provide flexible arrangement of child components
* inside a {@link Ext.Container Container}.
*
* ## Horizontal Box
*
* HBox allows you to easily lay out child components horizontally. It can size items based on a fixed width or a
* fraction of the total width available, enabling you to achieve flexible layouts that expand or contract to fill the
* space available.
*
* {@img ../guides/layouts/hbox.jpg}
*
* See the {@link Ext.layout.HBox HBox layout docs} for more information on using hboxes.
*
* ## Vertical Box
*
* VBox allows you to easily lay out child components vertically. It can size items based on a fixed height or a
* fraction of the total height available, enabling you to achieve flexible layouts that expand or contract to fill the
* space available.
*
* {@img ../guides/layouts/vbox.jpg}
*
* See the {@link Ext.layout.VBox VBox layout docs} for more information on using vboxes.
*
* For a more detailed overview of Sencha Touch 2 layouts, check out the
* [Layout Guide](../../../core_concepts/layouts.html).
*/
Ext.define('Ext.layout.FlexBox', {
extend: Ext.layout.Box ,
alias: 'layout.box',
config: {
align: 'stretch'
},
layoutBaseClass: 'x-layout-box',
itemClass: 'x-layout-box-item',
setContainer: function(container) {
this.callSuper(arguments);
this.monitorSizeFlagsChange();
},
applyOrient: function(orient) {
//
if (orient !== 'horizontal' && orient !== 'vertical') {
Ext.Logger.error("Invalid box orient of: '" + orient + "', must be either 'horizontal' or 'vertical'");
}
//
return orient;
},
updateOrient: function(orient, oldOrient) {
var container = this.container,
delegation = {
delegate: '> component'
};
if (orient === 'horizontal') {
this.sizePropertyName = 'width';
}
else {
this.sizePropertyName = 'height';
}
container.innerElement.swapCls('x-' + orient, 'x-' + oldOrient);
if (oldOrient) {
container.un(oldOrient === 'horizontal' ? 'widthchange' : 'heightchange', 'onItemSizeChange', this, delegation);
this.redrawContainer();
}
container.on(orient === 'horizontal' ? 'widthchange' : 'heightchange', 'onItemSizeChange', this, delegation);
},
onItemInnerStateChange: function(item, isInner) {
this.callSuper(arguments);
var flex, size;
item.toggleCls(this.itemClass, isInner);
if (isInner) {
flex = item.getFlex();
size = item.get(this.sizePropertyName);
if (flex) {
this.doItemFlexChange(item, flex);
}
else if (size) {
this.doItemSizeChange(item, size);
}
}
this.refreshItemSizeState(item);
},
refreshItemSizeState: function(item) {
var isInner = item.isInnerItem(),
container = this.container,
LAYOUT_HEIGHT = container.LAYOUT_HEIGHT,
LAYOUT_WIDTH = container.LAYOUT_WIDTH,
dimension = this.sizePropertyName,
layoutSizeFlags = 0,
containerSizeFlags = container.getSizeFlags();
if (isInner) {
layoutSizeFlags |= container.LAYOUT_STRETCHED;
if (this.getAlign() === 'stretch') {
layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_HEIGHT : LAYOUT_WIDTH);
}
if (item.getFlex()) {
layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_WIDTH : LAYOUT_HEIGHT);
}
}
item.setLayoutSizeFlags(layoutSizeFlags);
},
refreshAllItemSizedStates: function() {
var innerItems = this.container.innerItems,
i, ln, item;
for (i = 0,ln = innerItems.length; i < ln; i++) {
item = innerItems[i];
this.refreshItemSizeState(item);
}
},
onContainerSizeFlagsChange: function() {
this.refreshAllItemSizedStates();
this.callSuper(arguments);
},
onItemSizeChange: function(item, size) {
if (item.isInnerItem()) {
this.doItemSizeChange(item, size);
}
},
doItemSizeChange: function(item, size) {
if (size) {
item.setFlex(null);
this.redrawContainer();
}
},
onItemFlexChange: function(item, flex) {
if (item.isInnerItem()) {
this.doItemFlexChange(item, flex);
this.refreshItemSizeState(item);
}
},
doItemFlexChange: function(item, flex) {
this.setItemFlex(item, flex);
if (flex) {
item.set(this.sizePropertyName, null);
}
else {
this.redrawContainer();
}
},
redrawContainer: function() {
var container = this.container,
renderedTo = container.element.dom.parentNode;
if (renderedTo && renderedTo.nodeType !== 11) {
container.innerElement.redraw();
}
},
/**
* Sets the flex of an item in this box layout.
* @param {Ext.Component} item The item of this layout which you want to update the flex of.
* @param {Number} flex The flex to set on this method
*/
setItemFlex: function(item, flex) {
var element = item.element;
element.toggleCls('x-flexed', !!flex);
if (!flex) {
flex = '';
}
else {
flex = String(flex);
}
if (Ext.browser.is.WebKit) {
element.dom.style.setProperty('-webkit-box-flex', flex, null);
}
else if (Ext.browser.is.IE) {
element.dom.style.setProperty('-ms-flex', flex + ' 0 0px', null);
}
else {
element.dom.style.setProperty('flex', flex + ' 0 0px', null);
}
},
convertPosition: function(position) {
var positionMap = this.positionMap;
if (positionMap.hasOwnProperty(position)) {
return positionMap[position];
}
return position;
},
applyAlign: function(align) {
return this.convertPosition(align);
},
updateAlign: function(align, oldAlign) {
var container = this.container;
container.innerElement.swapCls(align, oldAlign, true, 'x-align');
if (oldAlign !== undefined) {
this.refreshAllItemSizedStates();
}
},
applyPack: function(pack) {
return this.convertPosition(pack);
},
updatePack: function(pack, oldPack) {
this.container.innerElement.swapCls(pack, oldPack, true, 'x-pack');
}
});
/**
* The HBox (short for horizontal box) layout makes it easy to position items horizontally in a
* {@link Ext.Container Container}. It can size items based on a fixed width or a fraction of the total width
* available.
*
* For example, an email client might have a list of messages pinned to the left, taking say one third of the available
* width, and a message viewing panel in the rest of the screen. We can achieve this with hbox layout's *flex* config:
*
* @example
* Ext.create('Ext.Container', {
* fullscreen: true,
* layout: 'hbox',
* items: [
* {
* html: 'message list',
* style: 'background-color: #5E99CC;',
* flex: 1
* },
* {
* html: 'message preview',
* style: 'background-color: #759E60;',
* flex: 2
* }
* ]
* });
*
* This will give us two boxes - one that's one third of the available width, the other being two thirds of the
* available width:
*
* {@img ../guides/layouts/hbox.jpg}
*
* We can also specify fixed widths for child items, or mix fixed widths and flexes. For example, here we have 3 items
* - one on each side with flex: 1, and one in the center with a fixed width of 100px:
*
* @example
* Ext.create('Ext.Container', {
* fullscreen: true,
* layout: 'hbox',
* items: [
* {
* html: 'Left item',
* style: 'background-color: #759E60;',
* flex: 1
* },
* {
* html: 'Center item',
* width: 100
* },
* {
* html: 'Right item',
* style: 'background-color: #5E99CC;',
* flex: 1
* }
* ]
* });
*
* Which gives us an effect like this:
*
* {@img ../guides/layouts/hboxfixed.jpg}
*
* For a more detailed overview of Sencha Touch 2 layouts, check out the
* [Layout Guide](../../../core_concepts/layouts.html).
*/
Ext.define('Ext.layout.HBox', {
extend: Ext.layout.FlexBox ,
alias: 'layout.hbox'
});
/**
*
*/
Ext.define('Ext.layout.Fit', {
extend: Ext.layout.Default ,
isFit: true,
alias: 'layout.fit',
layoutClass: 'x-layout-fit',
itemClass: 'x-layout-fit-item',
setContainer: function(container) {
this.callSuper(arguments);
container.innerElement.addCls(this.layoutClass);
this.onContainerSizeFlagsChange();
this.monitorSizeFlagsChange();
},
onContainerSizeFlagsChange: function() {
var container = this.container,
sizeFlags = container.getSizeFlags(),
stretched = Boolean(sizeFlags & container.LAYOUT_STRETCHED),
innerItems = container.innerItems,
i, ln, item;
this.callSuper();
for (i = 0,ln = innerItems.length; i < ln; i++) {
item = innerItems[i];
item.setLayoutSizeFlags(sizeFlags);
}
container.innerElement.toggleCls('x-stretched', stretched);
},
onItemInnerStateChange: function(item, isInner) {
this.callSuper(arguments);
item.toggleCls(this.itemClass, isInner);
item.setLayoutSizeFlags(isInner ? this.container.getSizeFlags() : 0);
}
});
/**
*
*/
Ext.define('Ext.layout.Float', {
extend: Ext.layout.Default ,
alias: 'layout.float',
config: {
direction: 'left'
},
layoutClass: 'layout-float',
itemClass: 'layout-float-item',
setContainer: function(container) {
this.callSuper(arguments);
container.innerElement.addCls(this.layoutClass);
},
onItemInnerStateChange: function(item, isInner) {
this.callSuper(arguments);
item.toggleCls(this.itemClass, isInner);
},
updateDirection: function(direction, oldDirection) {
var prefix = 'direction-';
this.container.innerElement.swapCls(prefix + direction, prefix + oldDirection);
}
});
/**
*
*/
Ext.define('Ext.layout.wrapper.Dock', {
config: {
direction: 'horizontal',
element: {
className: 'x-dock'
},
bodyElement: {
className: 'x-dock-body'
},
innerWrapper: null,
sizeState: false,
container: null
},
positionMap: {
top: 'start',
left: 'start',
bottom: 'end',
right: 'end'
},
constructor: function(config) {
this.items = {
start: [],
end: []
};
this.itemsCount = 0;
this.initConfig(config);
},
addItems: function(items) {
var i, ln, item;
for (i = 0, ln = items.length; i < ln; i++) {
item = items[i];
this.addItem(item);
}
},
addItem: function(item) {
var docked = item.getDocked(),
position = this.positionMap[docked],
wrapper = item.$dockWrapper,
container = this.getContainer(),
index = container.indexOf(item),
items = this.items,
sideItems = items[position],
itemWrapper, element, i, ln, sibling, referenceElement, siblingIndex;
if (wrapper) {
wrapper.removeItem(item);
}
item.$dockWrapper = this;
itemWrapper = item.link('$dockItemWrapper', new Ext.util.Wrapper({
className: 'x-dock-item'
}));
item.addCls('x-docked-' + docked);
element = itemWrapper.element;
for (i = 0, ln = sideItems.length; i < ln; i++) {
sibling = sideItems[i];
siblingIndex = container.indexOf(sibling);
if (siblingIndex > index) {
referenceElement = sibling.element;
sideItems.splice(i, 0, item);
break;
}
}
if (!referenceElement) {
sideItems.push(item);
referenceElement = this.getBodyElement();
}
this.itemsCount++;
if (position === 'start') {
element.insertBefore(referenceElement);
}
else {
element.insertAfter(referenceElement);
}
itemWrapper.wrap(item.element);
itemWrapper.bindSize(this.getDirection() === 'horizontal' ? 'width' : 'height');
},
removeItem: function(item) {
var position = item.getDocked(),
items = this.items[this.positionMap[position]];
item.removeCls('x-docked-' + position);
Ext.Array.remove(items, item);
item.unlink('$dockItemWrapper');
item.element.detach();
delete item.$dockWrapper;
if (--this.itemsCount === 0) {
this.destroy();
}
},
getItemsSlice: function(index) {
var container = this.getContainer(),
items = this.items,
slice = [],
sideItems, i, ln, item;
for (sideItems = items.start, i = 0, ln = sideItems.length; i < ln; i++) {
item = sideItems[i];
if (container.indexOf(item) > index) {
slice.push(item);
}
}
for (sideItems = items.end, i = 0, ln = sideItems.length; i < ln; i++) {
item = sideItems[i];
if (container.indexOf(item) > index) {
slice.push(item);
}
}
return slice;
},
applyElement: function(element) {
return Ext.Element.create(element);
},
updateElement: function(element) {
element.addCls('x-dock-' + this.getDirection());
},
applyBodyElement: function(bodyElement) {
return Ext.Element.create(bodyElement);
},
updateBodyElement: function(bodyElement) {
this.getElement().append(bodyElement);
},
updateInnerWrapper: function(innerWrapper, oldInnerWrapper) {
var innerElement = this.getBodyElement();
if (oldInnerWrapper && oldInnerWrapper.$outerWrapper === this) {
innerElement.remove(oldInnerWrapper.getElement());
delete oldInnerWrapper.$outerWrapper;
}
if (innerWrapper) {
innerWrapper.setSizeState(this.getSizeState());
innerWrapper.$outerWrapper = this;
innerElement.append(innerWrapper.getElement());
}
},
updateSizeState: function(state) {
var innerWrapper = this.getInnerWrapper();
this.getElement().setSizeState(state);
if (innerWrapper) {
innerWrapper.setSizeState(state);
}
},
destroy: function() {
var innerWrapper = this.getInnerWrapper(),
outerWrapper = this.$outerWrapper;
if (innerWrapper) {
if (outerWrapper) {
outerWrapper.setInnerWrapper(innerWrapper);
}
else {
innerWrapper.getElement().replace(this.getElement());
delete innerWrapper.$outerWrapper;
}
}
delete this.$outerWrapper;
this.setInnerWrapper(null);
this.unlink('_bodyElement', '_element');
this.callSuper();
}
});
/**
* The VBox (short for vertical box) layout makes it easy to position items horizontally in a
* {@link Ext.Container Container}. It can size items based on a fixed height or a fraction of the total height
* available.
*
* For example, let's say we want a banner to take one third of the available height, and an information panel in the
* rest of the screen. We can achieve this with vbox layout's *flex* config:
*
* @example
* Ext.create('Ext.Container', {
* fullscreen: true,
* layout: 'vbox',
* items: [
* {
* html: 'Awesome banner',
* style: 'background-color: #759E60;',
* flex: 1
* },
* {
* html: 'Some wonderful information',
* style: 'background-color: #5E99CC;',
* flex: 2
* }
* ]
* });
*
* This will give us two boxes - one that's one third of the available height, the other being two thirds of the
* available height:
*
* {@img ../guides/layouts/vbox.jpg}
*
* We can also specify fixed heights for child items, or mix fixed heights and flexes. For example, here we have 3
* items - one at the top and bottom with flex: 1, and one in the center with a fixed width of 100px:
*
* @example preview portrait
* Ext.create('Ext.Container', {
* fullscreen: true,
* layout: 'vbox',
* items: [
* {
* html: 'Top item',
* style: 'background-color: #5E99CC;',
* flex: 1
* },
* {
* html: 'Center item',
* height: 100
* },
* {
* html: 'Bottom item',
* style: 'background-color: #759E60;',
* flex: 1
* }
* ]
* });
*
* Which gives us an effect like this:
*
* {@img ../guides/layouts/vboxfixed.jpg}
*
* For a more detailed overview of Sencha Touch 2 layouts, check out the
* [Layout Guide](../../../core_concepts/layouts.html).
*
*/
Ext.define('Ext.layout.VBox', {
extend: Ext.layout.FlexBox ,
alias: 'layout.vbox',
config: {
orient: 'vertical'
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Abstract', {
extend: Ext.Evented ,
isAnimation: true,
config: {
direction: 'left',
duration: null,
reverse: null,
layout: null
},
updateLayout: function() {
this.enable();
},
enable: function() {
var layout = this.getLayout();
if (layout) {
layout.onBefore('activeitemchange', 'onActiveItemChange', this);
}
},
disable: function() {
var layout = this.getLayout();
if (this.isAnimating) {
this.stopAnimation();
}
if (layout) {
layout.unBefore('activeitemchange', 'onActiveItemChange', this);
}
},
onActiveItemChange: Ext.emptyFn,
destroy: function() {
var layout = this.getLayout();
if (this.isAnimating) {
this.stopAnimation();
}
if (layout) {
layout.unBefore('activeitemchange', 'onActiveItemChange', this);
}
this.setLayout(null);
if (this.observableId) {
this.fireEvent('destroy', this);
this.clearListeners();
this.clearManagedListeners();
}
// this.callSuper(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.fx.State', {
isAnimatable: {
'background-color' : true,
'background-image' : true,
'background-position': true,
'border-bottom-color': true,
'border-bottom-width': true,
'border-color' : true,
'border-left-color' : true,
'border-left-width' : true,
'border-right-color' : true,
'border-right-width' : true,
'border-spacing' : true,
'border-top-color' : true,
'border-top-width' : true,
'border-width' : true,
'bottom' : true,
'color' : true,
'crop' : true,
'font-size' : true,
'font-weight' : true,
'height' : true,
'left' : true,
'letter-spacing' : true,
'line-height' : true,
'margin-bottom' : true,
'margin-left' : true,
'margin-right' : true,
'margin-top' : true,
'max-height' : true,
'max-width' : true,
'min-height' : true,
'min-width' : true,
'opacity' : true,
'outline-color' : true,
'outline-offset' : true,
'outline-width' : true,
'padding-bottom' : true,
'padding-left' : true,
'padding-right' : true,
'padding-top' : true,
'right' : true,
'text-indent' : true,
'text-shadow' : true,
'top' : true,
'vertical-align' : true,
'visibility' : true,
'width' : true,
'word-spacing' : true,
'z-index' : true,
'zoom' : true,
'transform' : true
},
constructor: function(data) {
this.data = {};
this.set(data);
},
setConfig: function(data) {
this.set(data);
return this;
},
setRaw: function(data) {
this.data = data;
return this;
},
clear: function() {
return this.setRaw({});
},
setTransform: function(name, value) {
var data = this.data,
isArray = Ext.isArray(value),
transform = data.transform,
ln, key;
if (!transform) {
transform = data.transform = {
translateX: 0,
translateY: 0,
translateZ: 0,
scaleX: 1,
scaleY: 1,
scaleZ: 1,
rotate: 0,
rotateX: 0,
rotateY: 0,
rotateZ: 0,
skewX: 0,
skewY: 0
};
}
if (typeof name == 'string') {
switch (name) {
case 'translate':
if (isArray) {
ln = value.length;
if (ln == 0) { break; }
transform.translateX = value[0];
if (ln == 1) { break; }
transform.translateY = value[1];
if (ln == 2) { break; }
transform.translateZ = value[2];
}
else {
transform.translateX = value;
}
break;
case 'rotate':
if (isArray) {
ln = value.length;
if (ln == 0) { break; }
transform.rotateX = value[0];
if (ln == 1) { break; }
transform.rotateY = value[1];
if (ln == 2) { break; }
transform.rotateZ = value[2];
}
else {
transform.rotate = value;
}
break;
case 'scale':
if (isArray) {
ln = value.length;
if (ln == 0) { break; }
transform.scaleX = value[0];
if (ln == 1) { break; }
transform.scaleY = value[1];
if (ln == 2) { break; }
transform.scaleZ = value[2];
}
else {
transform.scaleX = value;
transform.scaleY = value;
}
break;
case 'skew':
if (isArray) {
ln = value.length;
if (ln == 0) { break; }
transform.skewX = value[0];
if (ln == 1) { break; }
transform.skewY = value[1];
}
else {
transform.skewX = value;
}
break;
default:
transform[name] = value;
}
}
else {
for (key in name) {
if (name.hasOwnProperty(key)) {
value = name[key];
this.setTransform(key, value);
}
}
}
},
set: function(name, value) {
var data = this.data,
key;
if (typeof name != 'string') {
for (key in name) {
value = name[key];
if (key === 'transform') {
this.setTransform(value);
}
else {
data[key] = value;
}
}
}
else {
if (name === 'transform') {
this.setTransform(value);
}
else {
data[name] = value;
}
}
return this;
},
unset: function(name) {
var data = this.data;
if (data.hasOwnProperty(name)) {
delete data[name];
}
return this;
},
getData: function() {
return this.data;
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.Abstract', {
extend: Ext.Evented ,
isAnimation: true,
config: {
name: '',
element: null,
/**
* @cfg
* Before configuration.
*/
before: null,
from: {},
to: {},
after: null,
states: {},
duration: 300,
/**
* @cfg
* Easing type.
*/
easing: 'linear',
iteration: 1,
direction: 'normal',
delay: 0,
onBeforeStart: null,
onEnd: null,
onBeforeEnd: null,
scope: null,
reverse: null,
preserveEndState: false,
replacePrevious: true
},
STATE_FROM: '0%',
STATE_TO: '100%',
DIRECTION_UP: 'up',
DIRECTION_DOWN: 'down',
DIRECTION_LEFT: 'left',
DIRECTION_RIGHT: 'right',
stateNameRegex: /^(?:[\d\.]+)%$/,
constructor: function() {
this.states = {};
this.callParent(arguments);
return this;
},
applyElement: function(element) {
return Ext.get(element);
},
applyBefore: function(before, current) {
if (before) {
return Ext.factory(before, Ext.fx.State, current);
}
},
applyAfter: function(after, current) {
if (after) {
return Ext.factory(after, Ext.fx.State, current);
}
},
setFrom: function(from) {
return this.setState(this.STATE_FROM, from);
},
setTo: function(to) {
return this.setState(this.STATE_TO, to);
},
getFrom: function() {
return this.getState(this.STATE_FROM);
},
getTo: function() {
return this.getState(this.STATE_TO);
},
setStates: function(states) {
var validNameRegex = this.stateNameRegex,
name;
for (name in states) {
if (validNameRegex.test(name)) {
this.setState(name, states[name]);
}
}
return this;
},
getStates: function() {
return this.states;
},
stop: function() {
this.fireEvent('stop', this);
},
destroy: function() {
this.stop();
this.callParent();
},
setState: function(name, state) {
var states = this.getStates(),
stateInstance;
stateInstance = Ext.factory(state, Ext.fx.State, states[name]);
if (stateInstance) {
states[name] = stateInstance;
}
//
else if (name === this.STATE_TO) {
Ext.Logger.error("Setting and invalid '100%' / 'to' state of: " + state);
}
//
return this;
},
getState: function(name) {
return this.getStates()[name];
},
getData: function() {
var states = this.getStates(),
statesData = {},
before = this.getBefore(),
after = this.getAfter(),
from = states[this.STATE_FROM],
to = states[this.STATE_TO],
fromData = from.getData(),
toData = to.getData(),
data, name, state;
for (name in states) {
if (states.hasOwnProperty(name)) {
state = states[name];
data = state.getData();
statesData[name] = data;
}
}
if (Ext.browser.is.AndroidStock2) {
statesData['0.0001%'] = fromData;
}
return {
before: before ? before.getData() : {},
after: after ? after.getData() : {},
states: statesData,
from: fromData,
to: toData,
duration: this.getDuration(),
iteration: this.getIteration(),
direction: this.getDirection(),
easing: this.getEasing(),
delay: this.getDelay(),
onEnd: this.getOnEnd(),
onBeforeEnd: this.getOnBeforeEnd(),
onBeforeStart: this.getOnBeforeStart(),
scope: this.getScope(),
preserveEndState: this.getPreserveEndState(),
replacePrevious: this.getReplacePrevious()
};
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.Slide', {
extend: Ext.fx.animation.Abstract ,
alternateClassName: 'Ext.fx.animation.SlideIn',
alias: ['animation.slide', 'animation.slideIn'],
config: {
/**
* @cfg {String} direction The direction of which the slide animates
* @accessor
*/
direction: 'left',
/**
* @cfg {Boolean} out True if you want to make this animation slide out, instead of slide in.
* @accessor
*/
out: false,
/**
* @cfg {Number} offset The offset that the animation should go offscreen before entering (or when exiting)
* @accessor
*/
offset: 0,
/**
* @cfg
* @inheritdoc
*/
easing: 'auto',
containerBox: 'auto',
elementBox: 'auto',
isElementBoxFit: true,
useCssTransform: true
},
reverseDirectionMap: {
up: 'down',
down: 'up',
left: 'right',
right: 'left'
},
applyEasing: function(easing) {
if (easing === 'auto') {
return 'ease-' + ((this.getOut()) ? 'in' : 'out');
}
return easing;
},
getContainerBox: function() {
var box = this._containerBox;
if (box === 'auto') {
box = this.getElement().getParent().getPageBox();
}
return box;
},
getElementBox: function() {
var box = this._elementBox;
if (this.getIsElementBoxFit()) {
return this.getContainerBox();
}
if (box === 'auto') {
box = this.getElement().getPageBox();
}
return box;
},
getData: function() {
var elementBox = this.getElementBox(),
containerBox = this.getContainerBox(),
box = elementBox ? elementBox : containerBox,
from = this.getFrom(),
to = this.getTo(),
out = this.getOut(),
offset = this.getOffset(),
direction = this.getDirection(),
useCssTransform = this.getUseCssTransform(),
reverse = this.getReverse(),
translateX = 0,
translateY = 0,
fromX, fromY, toX, toY;
if (reverse) {
direction = this.reverseDirectionMap[direction];
}
switch (direction) {
case this.DIRECTION_UP:
if (out) {
translateY = containerBox.top - box.top - box.height - offset;
}
else {
translateY = containerBox.bottom - box.bottom + box.height + offset;
}
break;
case this.DIRECTION_DOWN:
if (out) {
translateY = containerBox.bottom - box.bottom + box.height + offset;
}
else {
translateY = containerBox.top - box.height - box.top - offset;
}
break;
case this.DIRECTION_RIGHT:
if (out) {
translateX = containerBox.right - box.right + box.width + offset;
}
else {
translateX = containerBox.left - box.left - box.width - offset;
}
break;
case this.DIRECTION_LEFT:
if (out) {
translateX = containerBox.left - box.left - box.width - offset;
}
else {
translateX = containerBox.right - box.right + box.width + offset;
}
break;
}
fromX = (out) ? 0 : translateX;
fromY = (out) ? 0 : translateY;
if (useCssTransform) {
from.setTransform({
translateX: fromX,
translateY: fromY
});
}
else {
from.set('left', fromX);
from.set('top', fromY);
}
toX = (out) ? translateX : 0;
toY = (out) ? translateY : 0;
if (useCssTransform) {
to.setTransform({
translateX: toX,
translateY: toY
});
}
else {
to.set('left', toX);
to.set('top', toY);
}
return this.callParent(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.SlideOut', {
extend: Ext.fx.animation.Slide ,
alias: ['animation.slideOut'],
config: {
// @hide
out: true
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.Fade', {
extend: Ext.fx.animation.Abstract ,
alternateClassName: 'Ext.fx.animation.FadeIn',
alias: ['animation.fade', 'animation.fadeIn'],
config: {
/**
* @cfg {Boolean} out True if you want to make this animation fade out, instead of fade in.
* @accessor
*/
out: false,
before: {
display: null,
opacity: 0
},
after: {
opacity: null
},
reverse: null
},
updateOut: function(newOut) {
var to = this.getTo(),
from = this.getFrom();
if (newOut) {
from.set('opacity', 1);
to.set('opacity', 0);
} else {
from.set('opacity', 0);
to.set('opacity', 1);
}
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.FadeOut', {
extend: Ext.fx.animation.Fade ,
alias: 'animation.fadeOut',
config: {
// @hide
out: true,
before: {}
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.Flip', {
extend: Ext.fx.animation.Abstract ,
alias: 'animation.flip',
config: {
easing: 'ease-in',
/**
* @cfg {String} direction The direction of which the slide animates
* @accessor
*/
direction: 'right',
half: false,
out: null
},
getData: function() {
var from = this.getFrom(),
to = this.getTo(),
direction = this.getDirection(),
out = this.getOut(),
half = this.getHalf(),
rotate = (half) ? 90 : 180,
fromScale = 1,
toScale = 1,
fromRotateX = 0,
fromRotateY = 0,
toRotateX = 0,
toRotateY = 0;
if (out) {
toScale = 0.8;
}
else {
fromScale = 0.8;
}
switch (direction) {
case this.DIRECTION_UP:
if (out) {
toRotateX = rotate;
}
else {
fromRotateX = -rotate;
}
break;
case this.DIRECTION_DOWN:
if (out) {
toRotateX = -rotate;
}
else {
fromRotateX = rotate;
}
break;
case this.DIRECTION_RIGHT:
if (out) {
toRotateY = rotate;
}
else {
fromRotateY = -rotate;
}
break;
case this.DIRECTION_LEFT:
if (out) {
toRotateY = -rotate;
}
else {
fromRotateY = rotate;
}
break;
}
from.setTransform({
rotateX: fromRotateX,
rotateY: fromRotateY,
scale: fromScale
});
to.setTransform({
rotateX: toRotateX,
rotateY: toRotateY,
scale: toScale
});
return this.callParent(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.Pop', {
extend: Ext.fx.animation.Abstract ,
alias: ['animation.pop', 'animation.popIn'],
alternateClassName: 'Ext.fx.animation.PopIn',
config: {
/**
* @cfg {Boolean} out True if you want to make this animation pop out, instead of pop in.
* @accessor
*/
out: false,
before: {
display: null,
opacity: 0
},
after: {
opacity: null
}
},
getData: function() {
var to = this.getTo(),
from = this.getFrom(),
out = this.getOut();
if (out) {
from.set('opacity', 1);
from.setTransform({
scale: 1
});
to.set('opacity', 0);
to.setTransform({
scale: 0
});
}
else {
from.set('opacity', 0);
from.setTransform({
scale: 0
});
to.set('opacity', 1);
to.setTransform({
scale: 1
});
}
return this.callParent(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.fx.animation.PopOut', {
extend: Ext.fx.animation.Pop ,
alias: 'animation.popOut',
config: {
// @hide
out: true,
before: {}
}
});
/**
* @private
* @author Jacky Nguyen
*
* This class is a factory class that will create and return an animation class based on the {@link #type} configuration.
*/
Ext.define('Ext.fx.Animation', {
/**
* @cfg {String} type The type of animation to use. The possible values are:
*
* - `fade` - {@link Ext.fx.animation.Fade}
* - `fadeOut` - {@link Ext.fx.animation.FadeOut}
* - `flip` - {@link Ext.fx.animation.Flip}
* - `pop` - {@link Ext.fx.animation.Pop}
* - `popOut` - {@link Ext.fx.animation.PopOut}
* - `slide` - {@link Ext.fx.animation.Slide}
* - `slideOut` - {@link Ext.fx.animation.SlideOut}
*/
constructor: function(config) {
var defaultClass = Ext.fx.animation.Abstract,
type;
if (typeof config == 'string') {
type = config;
config = {};
}
else if (config && config.type) {
type = config.type;
}
if (type) {
if (Ext.browser.is.AndroidStock2) {
if (type == 'pop') {
type = 'fade';
}
if (type == 'popIn') {
type = 'fadeIn';
}
if (type == 'popOut') {
type = 'fadeOut';
}
}
defaultClass = Ext.ClassManager.getByAlias('animation.' + type);
//
if (!defaultClass) {
Ext.Logger.error("Invalid animation type of: '" + type + "'");
}
//
}
return Ext.factory(config, defaultClass);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Style', {
extend: Ext.fx.layout.card.Abstract ,
config: {
inAnimation: {
before: {
visibility: null
},
preserveEndState: false,
replacePrevious: true
},
outAnimation: {
preserveEndState: false,
replacePrevious: true
}
},
constructor: function(config) {
var inAnimation, outAnimation;
this.initConfig(config);
this.endAnimationCounter = 0;
inAnimation = this.getInAnimation();
outAnimation = this.getOutAnimation();
inAnimation.on('animationend', 'incrementEnd', this);
outAnimation.on('animationend', 'incrementEnd', this);
},
updateDirection: function(direction) {
this.getInAnimation().setDirection(direction);
this.getOutAnimation().setDirection(direction);
},
updateDuration: function(duration) {
this.getInAnimation().setDuration(duration);
this.getOutAnimation().setDuration(duration);
},
updateReverse: function(reverse) {
this.getInAnimation().setReverse(reverse);
this.getOutAnimation().setReverse(reverse);
},
incrementEnd: function() {
this.endAnimationCounter++;
if (this.endAnimationCounter > 1) {
this.endAnimationCounter = 0;
this.fireEvent('animationend', this);
}
},
applyInAnimation: function(animation, inAnimation) {
return Ext.factory(animation, Ext.fx.Animation, inAnimation);
},
applyOutAnimation: function(animation, outAnimation) {
return Ext.factory(animation, Ext.fx.Animation, outAnimation);
},
updateInAnimation: function(animation) {
animation.setScope(this);
},
updateOutAnimation: function(animation) {
animation.setScope(this);
},
onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
var inAnimation = this.getInAnimation(),
outAnimation = this.getOutAnimation(),
inElement, outElement;
if (newItem && oldItem && oldItem.isPainted()) {
inElement = newItem.renderElement;
outElement = oldItem.renderElement;
inAnimation.setElement(inElement);
outAnimation.setElement(outElement);
outAnimation.setOnBeforeEnd(function(element, interrupted) {
if (interrupted || Ext.Animator.hasRunningAnimations(element)) {
controller.firingArguments[1] = null;
controller.firingArguments[2] = null;
}
});
outAnimation.setOnEnd(function() {
controller.resume();
});
inElement.dom.style.setProperty('visibility', 'hidden', 'important');
newItem.show();
Ext.Animator.run([outAnimation, inAnimation]);
controller.pause();
}
},
destroy: function () {
Ext.destroy(this.getInAnimation(), this.getOutAnimation());
this.callParent(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Slide', {
extend: Ext.fx.layout.card.Style ,
alias: 'fx.layout.card.slide',
config: {
inAnimation: {
type: 'slide',
easing: 'ease-out'
},
outAnimation: {
type: 'slide',
easing: 'ease-out',
out: true
}
},
updateReverse: function(reverse) {
this.getInAnimation().setReverse(reverse);
this.getOutAnimation().setReverse(reverse);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Cover', {
extend: Ext.fx.layout.card.Style ,
alias: 'fx.layout.card.cover',
config: {
reverse: null,
inAnimation: {
before: {
'z-index': 100
},
after: {
'z-index': 0
},
type: 'slide',
easing: 'ease-out'
},
outAnimation: {
easing: 'ease-out',
from: {
opacity: 0.99
},
to: {
opacity: 1
},
out: true
}
},
updateReverse: function(reverse) {
this.getInAnimation().setReverse(reverse);
this.getOutAnimation().setReverse(reverse);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Reveal', {
extend: Ext.fx.layout.card.Style ,
alias: 'fx.layout.card.reveal',
config: {
inAnimation: {
easing: 'ease-out',
from: {
opacity: 0.99
},
to: {
opacity: 1
}
},
outAnimation: {
before: {
'z-index': 100
},
after: {
'z-index': 0
},
type: 'slide',
easing: 'ease-out',
out: true
}
},
updateReverse: function(reverse) {
this.getInAnimation().setReverse(reverse);
this.getOutAnimation().setReverse(reverse);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Fade', {
extend: Ext.fx.layout.card.Style ,
alias: 'fx.layout.card.fade',
config: {
reverse: null,
inAnimation: {
type: 'fade',
easing: 'ease-out'
},
outAnimation: {
type: 'fade',
easing: 'ease-out',
out: true
}
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Flip', {
extend: Ext.fx.layout.card.Style ,
alias: 'fx.layout.card.flip',
config: {
duration: 500,
inAnimation: {
type: 'flip',
half: true,
easing: 'ease-out',
before: {
'backface-visibility': 'hidden'
},
after: {
'backface-visibility': null
}
},
outAnimation: {
type: 'flip',
half: true,
easing: 'ease-in',
before: {
'backface-visibility': 'hidden'
},
after: {
'backface-visibility': null
},
out: true
}
},
onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
var parent = newItem.element.getParent();
parent.addCls('x-layout-card-perspective');
this.on('animationend', function() {
parent.removeCls('x-layout-card-perspective');
}, this, {single: true});
this.callParent(arguments);
},
updateDuration: function(duration) {
var halfDuration = duration / 2,
inAnimation = this.getInAnimation(),
outAnimation = this.getOutAnimation();
inAnimation.setDelay(halfDuration);
inAnimation.setDuration(halfDuration);
outAnimation.setDuration(halfDuration);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Pop', {
extend: Ext.fx.layout.card.Style ,
alias: 'fx.layout.card.pop',
config: {
duration: 500,
inAnimation: {
type: 'pop',
easing: 'ease-out'
},
outAnimation: {
type: 'pop',
easing: 'ease-in',
out: true
}
},
updateDuration: function(duration) {
var halfDuration = duration / 2,
inAnimation = this.getInAnimation(),
outAnimation = this.getOutAnimation();
inAnimation.setDelay(halfDuration);
inAnimation.setDuration(halfDuration);
outAnimation.setDuration(halfDuration);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.card.Scroll', {
extend: Ext.fx.layout.card.Abstract ,
alias: 'fx.layout.card.scroll',
config: {
duration: 150
},
constructor: function(config) {
this.initConfig(config);
},
getEasing: function() {
var easing = this.easing;
if (!easing) {
this.easing = easing = new Ext.fx.easing.Linear();
}
return easing;
},
updateDuration: function(duration) {
this.getEasing().setDuration(duration);
},
onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
var direction = this.getDirection(),
easing = this.getEasing(),
containerElement, inElement, outElement, containerWidth, containerHeight, reverse;
if (newItem && oldItem) {
if (this.isAnimating) {
this.stopAnimation();
}
newItem.setWidth('100%');
newItem.setHeight('100%');
containerElement = this.getLayout().container.innerElement;
containerWidth = containerElement.getWidth();
containerHeight = containerElement.getHeight();
inElement = newItem.renderElement;
outElement = oldItem.renderElement;
this.oldItem = oldItem;
this.newItem = newItem;
this.currentEventController = controller;
this.containerElement = containerElement;
this.isReverse = reverse = this.getReverse();
newItem.show();
if (direction == 'right') {
direction = 'left';
this.isReverse = reverse = !reverse;
}
else if (direction == 'down') {
direction = 'up';
this.isReverse = reverse = !reverse;
}
if (direction == 'left') {
if (reverse) {
easing.setConfig({
startValue: containerWidth,
endValue: 0
});
containerElement.dom.scrollLeft = containerWidth;
outElement.setLeft(containerWidth);
}
else {
easing.setConfig({
startValue: 0,
endValue: containerWidth
});
inElement.setLeft(containerWidth);
}
}
else {
if (reverse) {
easing.setConfig({
startValue: containerHeight,
endValue: 0
});
containerElement.dom.scrollTop = containerHeight;
outElement.setTop(containerHeight);
}
else {
easing.setConfig({
startValue: 0,
endValue: containerHeight
});
inElement.setTop(containerHeight);
}
}
this.startAnimation();
controller.pause();
}
},
startAnimation: function() {
this.isAnimating = true;
this.getEasing().setStartTime(Date.now());
Ext.AnimationQueue.start(this.doAnimationFrame, this);
},
doAnimationFrame: function() {
var easing = this.getEasing(),
direction = this.getDirection(),
scroll = 'scrollTop',
value;
if (direction == 'left' || direction == 'right') {
scroll = 'scrollLeft';
}
if (easing.isEnded) {
this.stopAnimation();
}
else {
value = easing.getValue();
this.containerElement.dom[scroll] = value;
}
},
stopAnimation: function() {
var me = this,
direction = me.getDirection(),
scroll = 'setTop',
oldItem = me.oldItem,
newItem = me.newItem;
if (direction == 'left' || direction == 'right') {
scroll = 'setLeft';
}
me.currentEventController.resume();
if (me.isReverse && oldItem && oldItem.renderElement && oldItem.renderElement.dom) {
oldItem.renderElement[scroll](null);
}
else if (newItem && newItem.renderElement && newItem.renderElement.dom) {
newItem.renderElement[scroll](null);
}
Ext.AnimationQueue.stop(this.doAnimationFrame, this);
me.isAnimating = false;
me.fireEvent('animationend', me);
}
});
/**
* @private
*/
Ext.define('Ext.fx.layout.Card', {
constructor: function(config) {
var defaultClass = Ext.fx.layout.card.Abstract,
type;
if (!config) {
return null;
}
if (typeof config == 'string') {
type = config;
config = {};
}
else if (config.type) {
type = config.type;
}
config.elementBox = false;
if (type) {
if (Ext.browser.is.AndroidStock2) {
// In Android 2 we only support scroll and fade. Otherwise force it to slide.
if (type != 'fade') {
type = 'scroll';
}
}
defaultClass = Ext.ClassManager.getByAlias('fx.layout.card.' + type);
//
if (!defaultClass) {
Ext.Logger.error("Unknown card animation type: '" + type + "'");
}
//
}
return Ext.factory(config, defaultClass);
}
});
/**
* Sometimes you want to show several screens worth of information but you've only got a small screen to work with.
* TabPanels and Carousels both enable you to see one screen of many at a time, and underneath they both use a Card
* Layout.
*
* Card Layout takes the size of the Container it is applied to and sizes the currently active item to fill the
* Container completely. It then hides the rest of the items, allowing you to change which one is currently visible but
* only showing one at once:
*
* {@img ../guides/layouts/card.jpg}
*
*
* Here the gray box is our Container, and the blue box inside it is the currently active card. The three other cards
* are hidden from view, but can be swapped in later. While it's not too common to create Card layouts directly, you
* can do so like this:
*
* var panel = Ext.create('Ext.Panel', {
* layout: 'card',
* items: [
* {
* html: "First Item"
* },
* {
* html: "Second Item"
* },
* {
* html: "Third Item"
* },
* {
* html: "Fourth Item"
* }
* ]
* });
*
* panel.{@link Ext.Container#setActiveItem setActiveItem}(1);
*
* Here we create a Panel with a Card Layout and later set the second item active (the active item index is zero-based,
* so 1 corresponds to the second item). Normally you're better off using a {@link Ext.tab.Panel tab panel} or a
* {@link Ext.carousel.Carousel carousel}.
*
* For a more detailed overview of Sencha Touch 2 layouts, check out the
* [Layout Guide](../../../core_concepts/layouts.html).
*/
Ext.define('Ext.layout.Card', {
extend: Ext.layout.Default ,
alias: 'layout.card',
isCard: true,
/**
* @event activeitemchange
* @preventable doActiveItemChange
* Fires when an card is made active
* @param {Ext.layout.Card} this The layout instance
* @param {Mixed} newActiveItem The new active item
* @param {Mixed} oldActiveItem The old active item
*/
layoutClass: 'x-layout-card',
itemClass: 'x-layout-card-item',
/**
* @private
*/
applyAnimation: function(animation) {
return new Ext.fx.layout.Card(animation);
},
/**
* @private
*/
updateAnimation: function(animation, oldAnimation) {
if (animation && animation.isAnimation) {
animation.setLayout(this);
}
if (oldAnimation) {
oldAnimation.destroy();
}
},
setContainer: function(container) {
this.callSuper(arguments);
container.innerElement.addCls(this.layoutClass);
container.onInitialized('onContainerInitialized', this);
},
onContainerInitialized: function() {
var container = this.container,
firstItem = container.getInnerAt(0),
activeItem = container.getActiveItem();
if (activeItem) {
activeItem.show();
if(firstItem && firstItem !== activeItem) {
firstItem.hide();
}
}
container.on('activeitemchange', 'onContainerActiveItemChange', this);
},
/**
* @private
*/
onContainerActiveItemChange: function(container) {
this.relayEvent(arguments, 'doActiveItemChange');
},
onItemInnerStateChange: function(item, isInner, destroying) {
this.callSuper(arguments);
var container = this.container,
activeItem = container.getActiveItem();
item.toggleCls(this.itemClass, isInner);
item.setLayoutSizeFlags(isInner ? container.LAYOUT_BOTH : 0);
if (isInner) {
if (activeItem !== container.innerIndexOf(item) && activeItem !== item && item !== container.pendingActiveItem) {
item.hide();
}
}
else {
if (!destroying && !item.isDestroyed && item.isDestroying !== true) {
item.show();
}
}
},
/**
* @private
*/
doActiveItemChange: function(me, newActiveItem, oldActiveItem) {
if (oldActiveItem) {
oldActiveItem.hide();
}
if (newActiveItem) {
newActiveItem.show();
}
},
destroy: function () {
this.callParent(arguments);
Ext.destroy(this.getAnimation());
}
});
/**
* Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
* filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
* context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
* on their records. Example usage:
*
* // Set up a fictional MixedCollection containing a few people to filter on
* var allNames = new Ext.util.MixedCollection();
* allNames.addAll([
* { id: 1, name: 'Ed', age: 25 },
* { id: 2, name: 'Jamie', age: 37 },
* { id: 3, name: 'Abe', age: 32 },
* { id: 4, name: 'Aaron', age: 26 },
* { id: 5, name: 'David', age: 32 }
* ]);
*
* var ageFilter = new Ext.util.Filter({
* property: 'age',
* value : 32
* });
*
* var longNameFilter = new Ext.util.Filter({
* filterFn: function(item) {
* return item.name.length > 4;
* }
* });
*
* // a new MixedCollection with the 3 names longer than 4 characters
* var longNames = allNames.filter(longNameFilter);
*
* // a new MixedCollection with the 2 people of age 32:
* var youngFolk = allNames.filter(ageFilter);
*/
Ext.define('Ext.util.Filter', {
isFilter: true,
config: {
/**
* @cfg {String} [property=null]
* The property to filter on. Required unless a `filter` is passed
*/
property: null,
/**
* @cfg {RegExp/Mixed} [value=null]
* The value you want to match against. Can be a regular expression which will be used as matcher or any other
* value. Mixed can be an object or an array of objects.
*/
value: null,
/**
* @cfg {Function} filterFn
* A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should
* return true to accept each item or false to reject it
*/
filterFn: Ext.emptyFn,
/**
* @cfg {Boolean} [anyMatch=false]
* True to allow any match - no regex start/end line anchors will be added.
*/
anyMatch: false,
/**
* @cfg {Boolean} [exactMatch=false]
* True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
*/
exactMatch: false,
/**
* @cfg {Boolean} [caseSensitive=false]
* True to make the regex case sensitive (adds 'i' switch to regex).
*/
caseSensitive: false,
/**
* @cfg {String} [root=null]
* Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data'
* to make the filter pull the {@link #property} out of the data object of each item
*/
root: null,
/**
* @cfg {String} id
* An optional id this filter can be keyed by in Collections. If no id is specified it will generate an id by
* first trying a combination of property-value, and if none if these were specified (like when having a
* filterFn) it will generate a random id.
*/
id: undefined,
/**
* @cfg {Object} [scope=null]
* The scope in which to run the filterFn
*/
scope: null
},
applyId: function(id) {
if (!id) {
if (this.getProperty()) {
id = this.getProperty() + '-' + String(this.getValue());
}
if (!id) {
id = Ext.id(null, 'ext-filter-');
}
}
return id;
},
/**
* Creates new Filter.
* @param {Object} config Config object
*/
constructor: function(config) {
this.initConfig(config);
},
applyFilterFn: function(filterFn) {
if (filterFn === Ext.emptyFn) {
filterFn = this.getInitialConfig('filter');
if (filterFn) {
return filterFn;
}
var value = this.getValue();
if (!this.getProperty() && !value && value !== 0) {
//
Ext.Logger.error('A Filter requires either a property and value, or a filterFn to be set');
//
return Ext.emptyFn;
}
else {
return this.createFilterFn();
}
}
return filterFn;
},
/**
* @private
* Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
*/
createFilterFn: function() {
var me = this,
matcher = me.createValueMatcher();
return function(item) {
var root = me.getRoot(),
property = me.getProperty();
if (root) {
item = item[root];
}
return matcher.test(item[property]);
};
},
/**
* @private
* Returns a regular expression based on the given value and matching options
*/
createValueMatcher: function() {
var me = this,
value = me.getValue(),
anyMatch = me.getAnyMatch(),
exactMatch = me.getExactMatch(),
caseSensitive = me.getCaseSensitive(),
escapeRe = Ext.String.escapeRegex;
if (value === null || value === undefined || !value.exec) { // not a regex
value = String(value);
if (anyMatch === true) {
value = escapeRe(value);
} else {
value = '^' + escapeRe(value);
if (exactMatch === true) {
value += '$';
}
}
value = new RegExp(value, caseSensitive ? '' : 'i');
}
return value;
}
});
/**
* @private
*/
Ext.define('Ext.util.AbstractMixedCollection', {
mixins: {
observable: Ext.mixin.Observable
},
/**
* @event clear
* Fires when the collection is cleared.
*/
/**
* @event add
* Fires when an item is added to the collection.
* @param {Number} index The index at which the item was added.
* @param {Object} o The item added.
* @param {String} key The key associated with the added item.
*/
/**
* @event replace
* Fires when an item is replaced in the collection.
* @param {String} key he key associated with the new added.
* @param {Object} old The item being replaced.
* @param {Object} new The new item.
*/
/**
* @event remove
* Fires when an item is removed from the collection.
* @param {Object} o The item being removed.
* @param {String} key (optional) The key associated with the removed item.
*/
/**
* Creates new MixedCollection.
* @param {Boolean} [allowFunctions=false] Specify `true` if the {@link #addAll}
* function should add function references to the collection.
* @param {Function} [keyFn] A function that can accept an item of the type(s) stored in this MixedCollection
* and return the key value for that item. This is used when available to look up the key on items that
* were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
* equivalent to providing an implementation for the {@link #getKey} method.
*/
constructor: function(allowFunctions, keyFn) {
var me = this;
me.items = [];
me.map = {};
me.keys = [];
me.length = 0;
me.allowFunctions = allowFunctions === true;
if (keyFn) {
me.getKey = keyFn;
}
me.mixins.observable.constructor.call(me);
},
/**
* @cfg {Boolean} allowFunctions Specify `true` if the {@link #addAll}
* function should add function references to the collection.
*/
allowFunctions : false,
/**
* Adds an item to the collection. Fires the {@link #event-add} event when complete.
* @param {String} key The key to associate with the item, or the new item.
*
* If a {@link #getKey} implementation was specified for this MixedCollection,
* or if the key of the stored items is in a property called `id`,
* the MixedCollection will be able to _derive_ the key for the new item.
* In this case just pass the new item in this parameter.
* @param {Object} obj The item to add.
* @return {Object} The item added.
*/
add: function(key, obj){
var me = this,
myObj = obj,
myKey = key,
old;
if (arguments.length == 1) {
myObj = myKey;
myKey = me.getKey(myObj);
}
if (typeof myKey != 'undefined' && myKey !== null) {
old = me.map[myKey];
if (typeof old != 'undefined') {
return me.replace(myKey, myObj);
}
me.map[myKey] = myObj;
}
me.length++;
me.items.push(myObj);
me.keys.push(myKey);
me.fireEvent('add', me.length - 1, myObj, myKey);
return myObj;
},
/**
* MixedCollection has a generic way to fetch keys if you implement `getKey`. The default implementation
* simply returns `item.id` but you can provide your own implementation
* to return a different value as in the following examples:
*
* // normal way
* var mc = new Ext.util.MixedCollection();
* mc.add(someEl.dom.id, someEl);
* mc.add(otherEl.dom.id, otherEl);
* //and so on
*
* // using getKey
* var mc = new Ext.util.MixedCollection();
* mc.getKey = function(el) {
* return el.dom.id;
* };
* mc.add(someEl);
* mc.add(otherEl);
*
* // or via the constructor
* var mc = new Ext.util.MixedCollection(false, function(el) {
* return el.dom.id;
* });
* mc.add(someEl);
* mc.add(otherEl);
*
* @param {Object} item The item for which to find the key.
* @return {Object} The key for the passed item.
*/
getKey: function(o){
return o.id;
},
/**
* Replaces an item in the collection. Fires the {@link #event-replace} event when complete.
* @param {String} key The key associated with the item to replace, or the replacement item.
*
* If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
* of your stored items is in a property called `id`, then the MixedCollection
* will be able to _derive_ the key of the replacement item. If you want to replace an item
* with one having the same key value, then just pass the replacement item in this parameter.
* @param {Object} o (optional) If the first parameter passed was a key, the item to associate
* with that key.
* @return {Object} The new item.
*/
replace: function(key, o){
var me = this,
old,
index;
if (arguments.length == 1) {
o = arguments[0];
key = me.getKey(o);
}
old = me.map[key];
if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
return me.add(key, o);
}
index = me.indexOfKey(key);
me.items[index] = o;
me.map[key] = o;
me.fireEvent('replace', key, old, o);
return o;
},
/**
* Adds all elements of an Array or an Object to the collection.
* @param {Object/Array} objs An Object containing properties which will be added
* to the collection, or an Array of values, each of which are added to the collection.
* Functions references will be added to the collection if `{@link #allowFunctions}`
* has been set to `true`.
*/
addAll: function(objs){
var me = this,
i = 0,
args,
len,
key;
if (arguments.length > 1 || Ext.isArray(objs)) {
args = arguments.length > 1 ? arguments : objs;
for (len = args.length; i < len; i++) {
me.add(args[i]);
}
} else {
for (key in objs) {
if (objs.hasOwnProperty(key)) {
if (me.allowFunctions || typeof objs[key] != 'function') {
me.add(key, objs[key]);
}
}
}
}
},
/**
* Executes the specified function once for every item in the collection.
*
* @param {Function} fn The function to execute for each item.
* @param {Mixed} fn.item The collection item.
* @param {Number} fn.index The item's index.
* @param {Number} fn.length The total number of items in the collection.
* @param {Boolean} fn.return Returning `false` will stop the iteration.
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
* Defaults to the current item in the iteration.
*/
each: function(fn, scope){
var items = [].concat(this.items), // each safe for removal
i = 0,
len = items.length,
item;
for (; i < len; i++) {
item = items[i];
if (fn.call(scope || item, item, i, len) === false) {
break;
}
}
},
/**
* Executes the specified function once for every key in the collection, passing each
* key, and its associated item as the first two parameters.
* @param {Function} fn The function to execute for each item.
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the browser window.
*/
eachKey: function(fn, scope){
var keys = this.keys,
items = this.items,
i = 0,
len = keys.length;
for (; i < len; i++) {
fn.call(scope || window, keys[i], items[i], i, len);
}
},
/**
* Returns the first item in the collection which elicits a `true` return value from the
* passed selection function.
* @param {Function} fn The selection function to execute for each item.
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the browser window.
* @return {Object} The first item in the collection which returned `true` from the selection function.
*/
findBy: function(fn, scope) {
var keys = this.keys,
items = this.items,
i = 0,
len = items.length;
for (; i < len; i++) {
if (fn.call(scope || window, items[i], keys[i])) {
return items[i];
}
}
return null;
},
/**
* Inserts an item at the specified index in the collection. Fires the `{@link #event-add}` event when complete.
* @param {Number} index The index to insert the item at.
* @param {String} key The key to associate with the new item, or the item itself.
* @param {Object} [obj] If the second parameter was a key, the new item.
* @return {Object} The item inserted.
*/
insert: function(index, key, obj){
var me = this,
myKey = key,
myObj = obj;
if (arguments.length == 2) {
myObj = myKey;
myKey = me.getKey(myObj);
}
if (me.containsKey(myKey)) {
me.suspendEvents();
me.removeAtKey(myKey);
me.resumeEvents();
}
if (index >= me.length) {
return me.add(myKey, myObj);
}
me.length++;
Ext.Array.splice(me.items, index, 0, myObj);
if (typeof myKey != 'undefined' && myKey !== null) {
me.map[myKey] = myObj;
}
Ext.Array.splice(me.keys, index, 0, myKey);
me.fireEvent('add', index, myObj, myKey);
return myObj;
},
/**
* Remove an item from the collection.
* @param {Object} o The item to remove.
* @return {Object} The item removed or `false` if no item was removed.
*/
remove: function(o){
return this.removeAt(this.indexOf(o));
},
/**
* Remove all items in the passed array from the collection.
* @param {Array} items An array of items to be removed.
* @return {Ext.util.MixedCollection} this object
*/
removeAll: function(items){
Ext.each(items || [], function(item) {
this.remove(item);
}, this);
return this;
},
/**
* Remove an item from a specified index in the collection. Fires the `{@link #event-remove}` event when complete.
* @param {Number} index The index within the collection of the item to remove.
* @return {Object/Boolean} The item removed or `false` if no item was removed.
*/
removeAt: function(index){
var me = this,
o,
key;
if (index < me.length && index >= 0) {
me.length--;
o = me.items[index];
Ext.Array.erase(me.items, index, 1);
key = me.keys[index];
if (typeof key != 'undefined') {
delete me.map[key];
}
Ext.Array.erase(me.keys, index, 1);
me.fireEvent('remove', o, key);
return o;
}
return false;
},
/**
* Removed an item associated with the passed key from the collection.
* @param {String} key The key of the item to remove.
* @return {Object/Boolean} The item removed or `false` if no item was removed.
*/
removeAtKey: function(key){
return this.removeAt(this.indexOfKey(key));
},
/**
* Returns the number of items in the collection.
* @return {Number} the number of items in the collection.
*/
getCount: function(){
return this.length;
},
/**
* Returns index within the collection of the passed Object.
* @param {Object} o The item to find the index of.
* @return {Number} index of the item. Returns -1 if not found.
*/
indexOf: function(o){
return Ext.Array.indexOf(this.items, o);
},
/**
* Returns index within the collection of the passed key.
* @param {String} key The key to find the index of.
* @return {Number} The index of the key.
*/
indexOfKey: function(key){
return Ext.Array.indexOf(this.keys, key);
},
/**
* Returns the item associated with the passed key OR index.
* Key has priority over index. This is the equivalent
* of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
* @param {String/Number} key The key or index of the item.
* @return {Object} If the item is found, returns the item. If the item was not found, returns `undefined`.
* If an item was found, but is a Class, returns `null`.
*/
get: function(key) {
var me = this,
mk = me.map[key],
item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
},
/**
* Returns the item at the specified index.
* @param {Number} index The index of the item.
* @return {Object} The item at the specified index.
*/
getAt: function(index) {
return this.items[index];
},
/**
* Returns the item associated with the passed key.
* @param {String/Number} key The key of the item.
* @return {Object} The item associated with the passed key.
*/
getByKey: function(key) {
return this.map[key];
},
/**
* Returns `true` if the collection contains the passed Object as an item.
* @param {Object} o The Object to look for in the collection.
* @return {Boolean} `true` if the collection contains the Object as an item.
*/
contains: function(o){
return Ext.Array.contains(this.items, o);
},
/**
* Returns `true` if the collection contains the passed Object as a key.
* @param {String} key The key to look for in the collection.
* @return {Boolean} `true` if the collection contains the Object as a key.
*/
containsKey: function(key){
return typeof this.map[key] != 'undefined';
},
/**
* Removes all items from the collection. Fires the `{@link #event-clear}` event when complete.
*/
clear: function(){
var me = this;
me.length = 0;
me.items = [];
me.keys = [];
me.map = {};
me.fireEvent('clear');
},
/**
* Returns the first item in the collection.
* @return {Object} the first item in the collection..
*/
first: function() {
return this.items[0];
},
/**
* Returns the last item in the collection.
* @return {Object} the last item in the collection..
*/
last: function() {
return this.items[this.length - 1];
},
/**
* Collects all of the values of the given property and returns their sum.
* @param {String} property The property to sum by.
* @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
* summing fields in records, where the fields are all stored inside the `data` object
* @param {Number} [start=0] (optional) The record index to start at.
* @param {Number} [end=-1] (optional) The record index to end at.
* @return {Number} The total
*/
sum: function(property, root, start, end) {
var values = this.extractValues(property, root),
length = values.length,
sum = 0,
i;
start = start || 0;
end = (end || end === 0) ? end : length - 1;
for (i = start; i <= end; i++) {
sum += values[i];
}
return sum;
},
/**
* Collects unique values of a particular property in this MixedCollection.
* @param {String} property The property to collect on.
* @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
* summing fields in records, where the fields are all stored inside the `data` object.
* @param {Boolean} [allowNull] Pass `true` to allow `null`, `undefined`, or empty string values.
* @return {Array} The unique values.
*/
collect: function(property, root, allowNull) {
var values = this.extractValues(property, root),
length = values.length,
hits = {},
unique = [],
value, strValue, i;
for (i = 0; i < length; i++) {
value = values[i];
strValue = String(value);
if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
hits[strValue] = true;
unique.push(value);
}
}
return unique;
},
/**
* @private
* Extracts all of the given property values from the items in the MixedCollection. Mainly used as a supporting method for
* functions like `sum()` and `collect()`.
* @param {String} property The property to extract.
* @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
* extracting field data from Model instances, where the fields are stored inside the `data` object.
* @return {Array} The extracted values.
*/
extractValues: function(property, root) {
var values = this.items;
if (root) {
values = Ext.Array.pluck(values, root);
}
return Ext.Array.pluck(values, property);
},
/**
* Returns a range of items in this collection.
* @param {Number} [start=0] The starting index.
* @param {Number} [end=-1] The ending index.
* @return {Array} An array of items
*/
getRange: function(start, end){
var me = this,
items = me.items,
range = [],
i;
if (items.length < 1) {
return range;
}
start = start || 0;
end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
if (start <= end) {
for (i = start; i <= end; i++) {
range[range.length] = items[i];
}
} else {
for (i = start; i >= end; i--) {
range[range.length] = items[i];
}
}
return range;
},
/**
* Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
* property/value pair with optional parameters for substring matching and case sensitivity. See
* {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
* MixedCollection can be easily filtered by property like this:
*
* // create a simple store with a few people defined
* var people = new Ext.util.MixedCollection();
* people.addAll([
* {id: 1, age: 25, name: 'Ed'},
* {id: 2, age: 24, name: 'Tommy'},
* {id: 3, age: 24, name: 'Arne'},
* {id: 4, age: 26, name: 'Aaron'}
* ]);
*
* // a new MixedCollection containing only the items where age == 24
* var middleAged = people.filter('age', 24);
*
* @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
* @param {String/RegExp} value Either string that the property values
* should start with or a RegExp to test against the property.
* @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning
* @param {Boolean} [caseSensitive=false] (optional) `true` for case sensitive comparison.
* @return {Ext.util.MixedCollection} The new filtered collection
*/
filter: function(property, value, anyMatch, caseSensitive) {
var filters = [],
filterFn;
//support for the simple case of filtering by property/value
if (Ext.isString(property)) {
filters.push(Ext.create('Ext.util.Filter', {
property : property,
value : value,
anyMatch : anyMatch,
caseSensitive: caseSensitive
}));
} else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
filters = filters.concat(property);
}
//at this point we have an array of zero or more Ext.util.Filter objects to filter with,
//so here we construct a function that combines these filters by ANDing them together
filterFn = function(record) {
var isMatch = true,
length = filters.length,
i;
for (i = 0; i < length; i++) {
var filter = filters[i],
fn = filter.getFilterFn(),
scope = filter.getScope();
isMatch = isMatch && fn.call(scope, record);
}
return isMatch;
};
return this.filterBy(filterFn);
},
/**
* Filter by a function. Returns a _new_ collection that has been filtered.
* The passed function will be called with each object in the collection.
* If the function returns `true`, the value is included otherwise it is filtered.
* @param {Function} fn The function to be called, it will receive the args `o` (the object), `k` (the key)
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this MixedCollection.
* @return {Ext.util.MixedCollection} The new filtered collection.
*/
filterBy: function(fn, scope) {
var me = this,
newMC = new this.self(),
keys = me.keys,
items = me.items,
length = items.length,
i;
newMC.getKey = me.getKey;
for (i = 0; i < length; i++) {
if (fn.call(scope || me, items[i], keys[i])) {
newMC.add(keys[i], items[i]);
}
}
return newMC;
},
/**
* Finds the index of the first matching object in this collection by a specific property/value.
* @param {String} property The name of a property on your objects.
* @param {String/RegExp} value A string that the property values.
* should start with or a RegExp to test against the property.
* @param {Number} [start=0] (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.
* @return {Number} The matched index or -1.
*/
findIndex: function(property, value, start, anyMatch, caseSensitive){
if(Ext.isEmpty(value, false)){
return -1;
}
value = this.createValueMatcher(value, anyMatch, caseSensitive);
return this.findIndexBy(function(o){
return o && value.test(o[property]);
}, null, start);
},
/**
* Find the index of the first matching object in this collection by a function.
* If the function returns `true` it is considered a match.
* @param {Function} fn The function to be called, it will receive the args `o` (the object), `k` (the key).
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this MixedCollection.
* @param {Number} [start=0] (optional) The index to start searching at.
* @return {Number} The matched index or -1.
*/
findIndexBy: function(fn, scope, start){
var me = this,
keys = me.keys,
items = me.items,
i = start || 0,
len = items.length;
for (; i < len; i++) {
if (fn.call(scope || me, items[i], keys[i])) {
return i;
}
}
return -1;
},
/**
* Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
* and by Ext.data.Store#filter
* @private
* @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
* @param {Boolean} [anyMatch=false] `true` to allow any match - no regex start/end line anchors will be added.
* @param {Boolean} [caseSensitive=false] `true` to make the regex case sensitive (adds 'i' switch to regex).
* @param {Boolean} [exactMatch=false] `true` to force exact match (^ and $ characters added to the regex). Ignored if `anyMatch` is `true`.
*/
createValueMatcher: function(value, anyMatch, caseSensitive, exactMatch) {
if (!value.exec) { // not a regex
var er = Ext.String.escapeRegex;
value = String(value);
if (anyMatch === true) {
value = er(value);
} else {
value = '^' + er(value);
if (exactMatch === true) {
value += '$';
}
}
value = new RegExp(value, caseSensitive ? '' : 'i');
}
return value;
},
/**
* Creates a shallow copy of this collection.
* @return {Ext.util.MixedCollection}
*/
clone: function() {
var me = this,
copy = new this.self(),
keys = me.keys,
items = me.items,
i = 0,
len = items.length;
for(; i < len; i++){
copy.add(keys[i], items[i]);
}
copy.getKey = me.getKey;
return copy;
}
});
/**
* Represents a single sorter that can be used as part of the sorters configuration in Ext.mixin.Sortable.
*
* A common place for Sorters to be used are {@link Ext.data.Store Stores}. For example:
*
* @example miniphone
* var store = Ext.create('Ext.data.Store', {
* fields: ['firstName', 'lastName'],
* sorters: 'lastName',
*
* data: [
* { firstName: 'Tommy', lastName: 'Maintz' },
* { firstName: 'Rob', lastName: 'Dougan' },
* { firstName: 'Ed', lastName: 'Spencer'},
* { firstName: 'Jamie', lastName: 'Avins' },
* { firstName: 'Nick', lastName: 'Poulden'}
* ]
* });
*
* Ext.create('Ext.List', {
* fullscreen: true,
* itemTpl: '{firstName} {lastName}
',
* store: store
* });
*
* In the next example, we specify a custom sorter function:
*
* @example miniphone
* var store = Ext.create('Ext.data.Store', {
* fields: ['person'],
* sorters: [
* {
* // Sort by first letter of last name, in descending order
* sorterFn: function(record1, record2) {
* var name1 = record1.data.person.name.split('-')[1].substr(0, 1),
* name2 = record2.data.person.name.split('-')[1].substr(0, 1);
*
* return name1 > name2 ? 1 : (name1 === name2 ? 0 : -1);
* },
* direction: 'DESC'
* }
* ],
* data: [
* { person: { name: 'Tommy-Maintz' } },
* { person: { name: 'Rob-Dougan' } },
* { person: { name: 'Ed-Spencer' } },
* { person: { name: 'Nick-Poulden' } },
* { person: { name: 'Jamie-Avins' } }
* ]
* });
*
* Ext.create('Ext.List', {
* fullscreen: true,
* itemTpl: '{person.name}',
* store: store
* });
*/
Ext.define('Ext.util.Sorter', {
isSorter: true,
config: {
/**
* @cfg {String} property The property to sort by. Required unless `sorterFn` is provided
*/
property: null,
/**
* @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property}.
* This function should compare the two passed arguments, returning -1, 0 or 1 depending on if item 1 should be
* sorted before, at the same level, or after item 2.
*
* sorterFn: function(person1, person2) {
* return (person1.age > person2.age) ? 1 : (person1.age === person2.age ? 0 : -1);
* }
*/
sorterFn: null,
/**
* @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the
* root to 'data' to make the filter pull the {@link #property} out of the data object of each item
*/
root: null,
/**
* @cfg {Function} transform A function that will be run on each value before
* it is compared in the sorter. The function will receive a single argument,
* the value.
*/
transform: null,
/**
* @cfg {String} direction The direction to sort by. Valid values are "ASC", and "DESC".
*/
direction: "ASC",
/**
* @cfg {Mixed} id An optional id this sorter can be keyed by in Collections. If
* no id is specified it will use the property name used in this Sorter. If no
* property is specified, e.g. when adding a custom sorter function we will generate
* a random id.
*/
id: undefined
},
constructor: function(config) {
this.initConfig(config);
},
//
applySorterFn: function(sorterFn) {
if (!sorterFn && !this.getProperty()) {
Ext.Logger.error("A Sorter requires either a property or a sorterFn.");
}
return sorterFn;
},
applyProperty: function(property) {
if (!property && !this.getSorterFn()) {
Ext.Logger.error("A Sorter requires either a property or a sorterFn.");
}
return property;
},
//
applyId: function(id) {
if (!id) {
id = this.getProperty();
if (!id) {
id = Ext.id(null, 'ext-sorter-');
}
}
return id;
},
/**
* @private
* Creates and returns a function which sorts an array by the given property and direction
* @return {Function} A function which sorts by the property/direction combination provided
*/
createSortFunction: function(sorterFn) {
var me = this,
modifier = me.getDirection().toUpperCase() == "DESC" ? -1 : 1;
//create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
//-1 if object 2 is greater or 0 if they are equal
return function(o1, o2) {
return modifier * sorterFn.call(me, o1, o2);
};
},
/**
* @private
* Basic default sorter function that just compares the defined property of each object
*/
defaultSortFn: function(item1, item2) {
var me = this,
transform = me._transform,
root = me._root,
value1, value2,
property = me._property;
if (root !== null && root !== undefined) {
item1 = item1[root];
item2 = item2[root];
}
value1 = item1[property];
value2 = item2[property];
if (transform) {
value1 = transform(value1);
value2 = transform(value2);
}
return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0);
},
updateDirection: function() {
this.updateSortFn();
},
updateSortFn: function() {
this.sort = this.createSortFunction(this.getSorterFn() || this.defaultSortFn);
},
/**
* Toggles the direction of this Sorter. Note that when you call this function,
* the Collection this Sorter is part of does not get refreshed automatically.
*/
toggle: function() {
this.setDirection(Ext.String.toggle(this.getDirection(), "ASC", "DESC"));
}
});
/**
* @docauthor Tommy Maintz
*
* A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
*
* __Note:__ This mixin is mainly for internal library use and most users should not need to use it directly. It
* is more likely you will want to use one of the component classes that import this mixin, such as
* {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
*/
Ext.define("Ext.util.Sortable", {
extend: Ext.mixin.Mixin ,
/**
* @property {Boolean} isSortable
* Flag denoting that this object is sortable. Always `true`.
* @readonly
*/
isSortable: true,
mixinConfig: {
hooks: {
destroy: 'destroy'
}
},
/**
* @property {String} defaultSortDirection
* The default sort direction to use if one is not specified.
*/
defaultSortDirection: "ASC",
/**
* @property {String} sortRoot
* The property in each item that contains the data to sort.
*/
/**
* Performs initialization of this mixin. Component classes using this mixin should call this method during their
* own initialization.
*/
initSortable: function() {
var me = this,
sorters = me.sorters;
/**
* @property {Ext.util.MixedCollection} sorters
* The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
*/
me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
return item.id || item.property;
});
if (sorters) {
me.sorters.addAll(me.decodeSorters(sorters));
}
},
/**
* Sorts the data in the Store by one or more of its properties. Example usage:
*
* //sort by a single field
* myStore.sort('myField', 'DESC');
*
* //sorting by multiple fields
* myStore.sort([
* {
* property : 'age',
* direction: 'ASC'
* },
* {
* property : 'name',
* direction: 'DESC'
* }
* ]);
*
* Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
* the actual sorting to its internal {@link Ext.util.MixedCollection}.
*
* When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
*
* store.sort('myField');
* store.sort('myField');
*
* Is equivalent to this code, because Store handles the toggling automatically:
*
* store.sort('myField', 'ASC');
* store.sort('myField', 'DESC');
*
* @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
* {@link Ext.data.Model Model}, or an array of sorter configurations.
* @param {String} [direction="ASC"] The overall direction to sort the data by.
* @param {String} [where]
* @param {Boolean} [doSort]
* @return {Ext.util.Sorter[]}
*/
sort: function(sorters, direction, where, doSort) {
var me = this,
sorter, sorterFn,
newSorters;
if (Ext.isArray(sorters)) {
doSort = where;
where = direction;
newSorters = sorters;
}
else if (Ext.isObject(sorters)) {
doSort = where;
where = direction;
newSorters = [sorters];
}
else if (Ext.isString(sorters)) {
sorter = me.sorters.get(sorters);
if (!sorter) {
sorter = {
property : sorters,
direction: direction
};
newSorters = [sorter];
}
else if (direction === undefined) {
sorter.toggle();
}
else {
sorter.setDirection(direction);
}
}
if (newSorters && newSorters.length) {
newSorters = me.decodeSorters(newSorters);
if (Ext.isString(where)) {
if (where === 'prepend') {
sorters = me.sorters.clone().items;
me.sorters.clear();
me.sorters.addAll(newSorters);
me.sorters.addAll(sorters);
}
else {
me.sorters.addAll(newSorters);
}
}
else {
me.sorters.clear();
me.sorters.addAll(newSorters);
}
if (doSort !== false) {
me.onBeforeSort(newSorters);
}
}
if (doSort !== false) {
sorters = me.sorters.items;
if (sorters.length) {
//construct an amalgamated sorter function which combines all of the Sorters passed
sorterFn = function(r1, r2) {
var result = sorters[0].sort(r1, r2),
length = sorters.length,
i;
//if we have more than one sorter, OR any additional sorter functions together
for (i = 1; i < length; i++) {
result = result || sorters[i].sort.call(this, r1, r2);
}
return result;
};
me.doSort(sorterFn);
}
}
return sorters;
},
onBeforeSort: Ext.emptyFn,
/**
* @private
* Normalizes an array of sorter objects, ensuring that they are all {@link Ext.util.Sorter} instances.
* @param {Array} sorters The sorters array.
* @return {Array} Array of {@link Ext.util.Sorter} objects.
*/
decodeSorters: function(sorters) {
if (!Ext.isArray(sorters)) {
if (sorters === undefined) {
sorters = [];
} else {
sorters = [sorters];
}
}
var length = sorters.length,
Sorter = Ext.util.Sorter,
fields = this.model ? this.model.prototype.fields : null,
field,
config, i;
for (i = 0; i < length; i++) {
config = sorters[i];
if (!(config instanceof Sorter)) {
if (Ext.isString(config)) {
config = {
property: config
};
}
Ext.applyIf(config, {
root : this.sortRoot,
direction: "ASC"
});
if (config.fn) {
config.sorterFn = config.fn;
}
//support a function to be passed as a sorter definition
if (typeof config == 'function') {
config = {
sorterFn: config
};
}
// ensure sortType gets pushed on if necessary
if (fields && !config.transform) {
field = fields.get(config.property);
config.transform = field ? field.sortType : undefined;
}
sorters[i] = Ext.create('Ext.util.Sorter', config);
}
}
return sorters;
},
getSorters: function() {
return this.sorters.items;
},
destroy: function () {
this.callSuper();
Ext.destroy(this.sorters);
}
});
/**
* Represents a collection of a set of key and value pairs. Each key in the MixedCollection must be unique, the same key
* cannot exist twice. This collection is ordered, items in the collection can be accessed by index or via the key.
* Newly added items are added to the end of the collection. This class is similar to {@link Ext.util.HashMap} however
* it is heavier and provides more functionality. Sample usage:
*
* @example
* var coll = new Ext.util.MixedCollection();
* coll.add('key1', 'val1');
* coll.add('key2', 'val2');
* coll.add('key3', 'val3');
*
* alert(coll.get('key1')); // 'val1'
* alert(coll.indexOfKey('key3')); // 2
*
* The MixedCollection also has support for sorting and filtering of the values in the collection.
*
* @example
* var coll = new Ext.util.MixedCollection();
* coll.add('key1', 100);
* coll.add('key2', -100);
* coll.add('key3', 17);
* coll.add('key4', 0);
* var biggerThanZero = coll.filterBy(function(value){
* return value > 0;
* });
* alert(biggerThanZero.getCount()); // 2
*/
Ext.define('Ext.util.MixedCollection', {
extend: Ext.util.AbstractMixedCollection ,
mixins: {
sortable: Ext.util.Sortable
},
/**
* @event sort
* Fires whenever MixedCollection is sorted.
* @param {Ext.util.MixedCollection} this
*/
constructor: function() {
var me = this;
me.callParent(arguments);
me.mixins.sortable.initSortable.call(me);
},
doSort: function(sorterFn) {
this.sortBy(sorterFn);
},
/**
* @private
* Performs the actual sorting based on a direction and a sorting function. Internally,
* this creates a temporary array of all items in the MixedCollection, sorts it and then writes
* the sorted array data back into `this.items` and `this.keys`.
* @param {String} property Property to sort by ('key', 'value', or 'index')
* @param {String} [dir=ASC] (optional) Direction to sort 'ASC' or 'DESC'.
* @param {Function} fn (optional) Comparison function that defines the sort order.
* Defaults to sorting by numeric value.
*/
_sort: function(property, dir, fn){
var me = this,
i, len,
dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
//this is a temporary array used to apply the sorting function
c = [],
keys = me.keys,
items = me.items;
//default to a simple sorter function if one is not provided
fn = fn || function(a, b) {
return a - b;
};
//copy all the items into a temporary array, which we will sort
for(i = 0, len = items.length; i < len; i++){
c[c.length] = {
key : keys[i],
value: items[i],
index: i
};
}
//sort the temporary array
Ext.Array.sort(c, function(a, b){
var v = fn(a[property], b[property]) * dsc;
if(v === 0){
v = (a.index < b.index ? -1 : 1);
}
return v;
});
//copy the temporary array back into the main this.items and this.keys objects
for(i = 0, len = c.length; i < len; i++){
items[i] = c[i].value;
keys[i] = c[i].key;
}
me.fireEvent('sort', me);
},
/**
* Sorts the collection by a single sorter function.
* @param {Function} sorterFn The function to sort by.
*/
sortBy: function(sorterFn) {
var me = this,
items = me.items,
keys = me.keys,
length = items.length,
temp = [],
i;
//first we create a copy of the items array so that we can sort it
for (i = 0; i < length; i++) {
temp[i] = {
key : keys[i],
value: items[i],
index: i
};
}
Ext.Array.sort(temp, function(a, b) {
var v = sorterFn(a.value, b.value);
if (v === 0) {
v = (a.index < b.index ? -1 : 1);
}
return v;
});
//copy the temporary array back into the main this.items and this.keys objects
for (i = 0; i < length; i++) {
items[i] = temp[i].value;
keys[i] = temp[i].key;
}
me.fireEvent('sort', me, items, keys);
},
/**
* Reorders each of the items based on a mapping from old index to new index. Internally this just translates into a
* sort. The `sort` event is fired whenever reordering has occured.
* @param {Object} mapping Mapping from old item index to new item index.
*/
reorder: function(mapping) {
var me = this,
items = me.items,
index = 0,
length = items.length,
order = [],
remaining = [],
oldIndex;
me.suspendEvents();
//object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
for (oldIndex in mapping) {
order[mapping[oldIndex]] = items[oldIndex];
}
for (index = 0; index < length; index++) {
if (mapping[index] == undefined) {
remaining.push(items[index]);
}
}
for (index = 0; index < length; index++) {
if (order[index] == undefined) {
order[index] = remaining.shift();
}
}
me.clear();
me.addAll(order);
me.resumeEvents();
me.fireEvent('sort', me);
},
/**
* Sorts this collection by **key**s.
* @param {String} [dir=ASC] Sorting direction: 'ASC' or 'DESC'.
* @param {Function} [fn] Comparison function that defines the sort order. Defaults to sorting by case insensitive
* string.
*/
sortByKey: function(dir, fn){
this._sort('key', dir, fn || function(a, b){
var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
});
}
});
/**
* @private
*/
Ext.define('Ext.ItemCollection', {
extend: Ext.util.MixedCollection ,
getKey: function(item) {
return item.getItemId();
},
has: function(item) {
return this.map.hasOwnProperty(item.getId());
}
});
/**
* @private
*/
Ext.define('Ext.fx.easing.Momentum', {
extend: Ext.fx.easing.Abstract ,
config: {
acceleration: 30,
friction: 0,
startVelocity: 0
},
alpha: 0,
updateFriction: function(friction) {
var theta = Math.log(1 - (friction / 10));
this.theta = theta;
this.alpha = theta / this.getAcceleration();
},
updateStartVelocity: function(velocity) {
this.velocity = velocity * this.getAcceleration();
},
updateAcceleration: function(acceleration) {
this.velocity = this.getStartVelocity() * acceleration;
this.alpha = this.theta / acceleration;
},
getValue: function() {
return this.getStartValue() - this.velocity * (1 - this.getFrictionFactor()) / this.theta;
},
getFrictionFactor: function() {
var deltaTime = Ext.Date.now() - this.getStartTime();
return Math.exp(deltaTime * this.alpha);
},
getVelocity: function() {
return this.getFrictionFactor() * this.velocity;
}
});
/**
* @private
*/
Ext.define('Ext.fx.easing.Bounce', {
extend: Ext.fx.easing.Abstract ,
config: {
springTension: 0.3,
acceleration: 30,
startVelocity: 0
},
getValue: function() {
var deltaTime = Ext.Date.now() - this.getStartTime(),
theta = (deltaTime / this.getAcceleration()),
powTime = theta * Math.pow(Math.E, -this.getSpringTension() * theta);
return this.getStartValue() + (this.getStartVelocity() * powTime);
}
});
/**
* @private
*
* This easing is typically used for {@link Ext.scroll.Scroller}. It's a combination of
* {@link Ext.fx.easing.Momentum} and {@link Ext.fx.easing.Bounce}, which emulates deceleration when the animated element
* is still within its boundary, then bouncing back (snapping) when it's out-of-bound.
*/
Ext.define('Ext.fx.easing.BoundMomentum', {
extend: Ext.fx.easing.Abstract ,
config: {
/**
* @cfg {Object} momentum
* A valid config object for {@link Ext.fx.easing.Momentum}
* @accessor
*/
momentum: null,
/**
* @cfg {Object} bounce
* A valid config object for {@link Ext.fx.easing.Bounce}
* @accessor
*/
bounce: null,
minMomentumValue: 0,
maxMomentumValue: 0,
/**
* @cfg {Number} minVelocity
* The minimum velocity to end this easing
* @accessor
*/
minVelocity: 0.01,
/**
* @cfg {Number} startVelocity
* The start velocity
* @accessor
*/
startVelocity: 0
},
applyMomentum: function(config, currentEasing) {
return Ext.factory(config, Ext.fx.easing.Momentum, currentEasing);
},
applyBounce: function(config, currentEasing) {
return Ext.factory(config, Ext.fx.easing.Bounce, currentEasing);
},
updateStartTime: function(startTime) {
this.getMomentum().setStartTime(startTime);
this.callParent(arguments);
},
updateStartVelocity: function(startVelocity) {
this.getMomentum().setStartVelocity(startVelocity);
},
updateStartValue: function(startValue) {
this.getMomentum().setStartValue(startValue);
},
reset: function() {
this.lastValue = null;
this.isBouncingBack = false;
this.isOutOfBound = false;
return this.callParent(arguments);
},
getValue: function() {
var momentum = this.getMomentum(),
bounce = this.getBounce(),
startVelocity = momentum.getStartVelocity(),
direction = startVelocity > 0 ? 1 : -1,
minValue = this.getMinMomentumValue(),
maxValue = this.getMaxMomentumValue(),
boundedValue = (direction == 1) ? maxValue : minValue,
lastValue = this.lastValue,
value, velocity;
if (startVelocity === 0) {
return this.getStartValue();
}
if (!this.isOutOfBound) {
value = momentum.getValue();
velocity = momentum.getVelocity();
if (Math.abs(velocity) < this.getMinVelocity()) {
this.isEnded = true;
}
if (value >= minValue && value <= maxValue) {
return value;
}
this.isOutOfBound = true;
bounce.setStartTime(Ext.Date.now())
.setStartVelocity(velocity)
.setStartValue(boundedValue);
}
value = bounce.getValue();
if (!this.isEnded) {
if (!this.isBouncingBack) {
if (lastValue !== null) {
if ((direction == 1 && value < lastValue) || (direction == -1 && value > lastValue)) {
this.isBouncingBack = true;
}
}
}
else {
if (Math.round(value) == boundedValue) {
this.isEnded = true;
}
}
}
this.lastValue = value;
return value;
}
});
/**
* @private
*/
Ext.define('Ext.fx.easing.EaseOut', {
extend: Ext.fx.easing.Linear ,
alias: 'easing.ease-out',
config: {
exponent: 4,
duration: 1500
},
getValue: function() {
var deltaTime = Ext.Date.now() - this.getStartTime(),
duration = this.getDuration(),
startValue = this.getStartValue(),
endValue = this.getEndValue(),
distance = this.distance,
theta = deltaTime / duration,
thetaC = 1 - theta,
thetaEnd = 1 - Math.pow(thetaC, this.getExponent()),
currentValue = startValue + (thetaEnd * distance);
if (deltaTime >= duration) {
this.isEnded = true;
return endValue;
}
return currentValue;
}
});
/**
* @class Ext.scroll.Scroller
* @author Jacky Nguyen
*
* Momentum scrolling is one of the most important part of the framework's UI layer. In Sencha Touch there are
* several scroller implementations so we can have the best performance on all mobile devices and browsers.
*
* Scroller settings can be changed using the {@link Ext.Container#scrollable scrollable} configuration in
* {@link Ext.Container}. Anything you pass to that method will be passed to the scroller when it is
* instantiated in your container.
*
* Please note that the {@link Ext.Container#getScrollable} method returns an instance of {@link Ext.scroll.View}.
* So if you need to get access to the scroller after your container has been instantiated, you must use the
* {@link Ext.scroll.View#getScroller} method.
*
* // lets assume container is a container you have
* // created which is scrollable
* container.getScrollable().getScroller().setFps(10);
*
* ## Example
*
* Here is a simple example of how to adjust the scroller settings when using a {@link Ext.Container} (or anything
* that extends it).
*
* @example
* Ext.create('Ext.Container', {
* fullscreen: true,
* html: "Macaroni cheese roquefort
" +
* "port-salut. The big cheese
" +
* "fondue camembert de normandie
" +
* "cow boursin cheese swiss stinking
" +
* "bishop. Fromage feta edam fromage
" +
* "frais bavarian bergkase paneer
" +
* "paneer cheese and wine. Cow danish
" +
* "fontina roquefort bocconcini
" +
* "jarlsberg parmesan cheesecake
" +
* "danish fontina. Mascarpone
" +
* "bishop. Fromage feta edam fromage
" +
* "frais bavarian bergkase paneer
" +
* "paneer cheese and wine. Cow danish
" +
* "fontina roquefort bocconcini
" +
* "jarlsberg parmesan cheesecake
" +
* "emmental fromage frais cheesy
" +
* "grin say cheese squirty cheese
" +
* "parmesan queso. Cheese triangles
" +
* "st. agur blue cheese chalk and cheese
" +
* "cream cheese lancashire manchego
" +
* "taleggio blue castello. Port-salut
" +
* "paneer monterey jack
" +
* "say cheese fondue.",
* scrollable: {
* direction: 'vertical'
* }
* });
*
* As you can see, we are passing the {@link #direction} configuration into the scroller instance in our container.
*
* You can pass any of the configs below in that {@link Ext.Container#scrollable scrollable} configuration and it will
* just work.
*
* Go ahead and try it in the live code editor above!
*/
Ext.define('Ext.scroll.Scroller', {
extend: Ext.Evented ,
/**
* @event maxpositionchange
* Fires whenever the maximum position has changed.
* @param {Ext.scroll.Scroller} this
* @param {Number} maxPosition The new maximum position.
*/
/**
* @event refresh
* Fires whenever the Scroller is refreshed.
* @param {Ext.scroll.Scroller} this
*/
/**
* @event scrollstart
* Fires whenever the scrolling is started.
* @param {Ext.scroll.Scroller} this
* @param {Number} x The current x position.
* @param {Number} y The current y position.
*/
/**
* @event scrollend
* Fires whenever the scrolling is ended.
* @param {Ext.scroll.Scroller} this
* @param {Number} x The current x position.
* @param {Number} y The current y position.
*/
/**
* @event scroll
* Fires whenever the Scroller is scrolled.
* @param {Ext.scroll.Scroller} this
* @param {Number} x The new x position.
* @param {Number} y The new y position.
*/
config: {
/**
* @cfg element
* @private
*/
element: null,
/**
* @cfg {String} direction
* Possible values: 'auto', 'vertical', 'horizontal', or 'both'.
* @accessor
*/
direction: 'auto',
/**
* @cfg fps
* @private
*/
fps: 'auto',
/**
* @cfg {Boolean} disabled
* Whether or not this component is disabled.
* @accessor
*/
disabled: null,
/**
* @cfg {Boolean} directionLock
* `true` to lock the direction of the scroller when the user starts scrolling.
* This is useful when putting a scroller inside a scroller or a {@link Ext.Carousel}.
* @accessor
*/
directionLock: false,
/**
* @cfg {Object} momentumEasing
* A valid config for {@link Ext.fx.easing.BoundMomentum}. The default value is:
*
* {
* momentum: {
* acceleration: 30,
* friction: 0.5
* },
* bounce: {
* acceleration: 30,
* springTension: 0.3
* }
* }
*
* Note that supplied object will be recursively merged with the default object. For example, you can simply
* pass this to change the momentum acceleration only:
*
* {
* momentum: {
* acceleration: 10
* }
* }
*
* @accessor
*/
momentumEasing: {
momentum: {
acceleration: 30,
friction: 0.5
},
bounce: {
acceleration: 30,
springTension: 0.3
},
minVelocity: 1
},
/**
* @cfg bounceEasing
* @private
*/
bounceEasing: {
duration: 400
},
/**
* @cfg outOfBoundRestrictFactor
* @private
*/
outOfBoundRestrictFactor: 0.5,
/**
* @cfg startMomentumResetTime
* @private
*/
startMomentumResetTime: 300,
/**
* @cfg maxAbsoluteVelocity
* @private
*/
maxAbsoluteVelocity: 6,
/**
* @cfg containerSize
* @private
*/
containerSize: 'auto',
/**
* @cfg size
* @private
*/
size: 'auto',
/**
* @cfg autoRefresh
* @private
*/
autoRefresh: true,
/**
* @cfg {Object/Number} initialOffset
* The initial scroller position. When specified as Number,
* both x and y will be set to that value.
*/
initialOffset: {
x: 0,
y: 0
},
/**
* @cfg {Number/Object} slotSnapSize
* The size of each slot to snap to in 'px', can be either an object with `x` and `y` values, i.e:
*
* {
* x: 50,
* y: 100
* }
*
* or a number value to be used for both directions. For example, a value of `50` will be treated as:
*
* {
* x: 50,
* y: 50
* }
*
* @accessor
*/
slotSnapSize: {
x: 0,
y: 0
},
/**
* @cfg slotSnapOffset
* @private
*/
slotSnapOffset: {
x: 0,
y: 0
},
slotSnapEasing: {
duration: 150
},
translatable: {
translationMethod: 'auto',
useWrapper: false
}
},
cls: Ext.baseCSSPrefix + 'scroll-scroller',
containerCls: Ext.baseCSSPrefix + 'scroll-container',
dragStartTime: 0,
dragEndTime: 0,
isDragging: false,
isAnimating: false,
/**
* @private
* @constructor
* @chainable
*/
constructor: function(config) {
var element = config && config.element;
this.listeners = {
scope: this,
touchstart: 'onTouchStart',
touchend: 'onTouchEnd',
dragstart: 'onDragStart',
drag: 'onDrag',
dragend: 'onDragEnd'
};
this.minPosition = { x: 0, y: 0 };
this.startPosition = { x: 0, y: 0 };
this.position = { x: 0, y: 0 };
this.velocity = { x: 0, y: 0 };
this.isAxisEnabledFlags = { x: false, y: false };
this.flickStartPosition = { x: 0, y: 0 };
this.flickStartTime = { x: 0, y: 0 };
this.lastDragPosition = { x: 0, y: 0 };
this.dragDirection = { x: 0, y: 0};
this.initialConfig = config;
if (element) {
this.setElement(element);
}
return this;
},
/**
* @private
*/
applyElement: function(element) {
if (!element) {
return;
}
return Ext.get(element);
},
/**
* @private
* @chainable
*/
updateElement: function(element) {
this.initialize();
if (!this.FixedHBoxStretching) {
element.addCls(this.cls);
}
if (!this.getDisabled()) {
this.attachListeneners();
}
this.onConfigUpdate(['containerSize', 'size'], 'refreshMaxPosition');
this.on('maxpositionchange', 'snapToBoundary');
this.on('minpositionchange', 'snapToBoundary');
return this;
},
applyTranslatable: function(config, translatable) {
return Ext.factory(config, Ext.util.Translatable, translatable);
},
updateTranslatable: function(translatable) {
translatable.setConfig({
element: this.getElement(),
listeners: {
animationframe: 'onAnimationFrame',
animationend: 'onAnimationEnd',
scope: this
}
});
},
updateFps: function(fps) {
if (fps !== 'auto') {
this.getTranslatable().setFps(fps);
}
},
/**
* @private
*/
attachListeneners: function() {
this.getContainer().on(this.listeners);
},
/**
* @private
*/
detachListeners: function() {
this.getContainer().un(this.listeners);
},
/**
* @private
*/
updateDisabled: function(disabled) {
if (disabled) {
this.detachListeners();
}
else {
this.attachListeneners();
}
},
updateInitialOffset: function(initialOffset) {
if (typeof initialOffset == 'number') {
initialOffset = {
x: initialOffset,
y: initialOffset
};
}
var position = this.position,
x, y;
position.x = x = initialOffset.x;
position.y = y = initialOffset.y;
this.getTranslatable().translate(-x, -y);
},
/**
* @private
* @return {String}
*/
applyDirection: function(direction) {
var minPosition = this.getMinPosition(),
maxPosition = this.getMaxPosition(),
isHorizontal, isVertical;
this.givenDirection = direction;
if (direction === 'auto') {
isHorizontal = maxPosition.x > minPosition.x;
isVertical = maxPosition.y > minPosition.y;
if (isHorizontal && isVertical) {
direction = 'both';
}
else if (isHorizontal) {
direction = 'horizontal';
}
else {
direction = 'vertical';
}
}
return direction;
},
/**
* @private
*/
updateDirection: function(direction, oldDirection) {
var isAxisEnabledFlags = this.isAxisEnabledFlags,
verticalCls = this.cls + '-vertical',
horizontalCls = this.cls + '-horizontal',
element = this.getElement();
if (oldDirection === 'both' || oldDirection === 'horizontal') {
element.removeCls(horizontalCls);
}
if (oldDirection === 'both' || oldDirection === 'vertical') {
element.removeCls(verticalCls);
}
isAxisEnabledFlags.x = isAxisEnabledFlags.y = false;
if (direction === 'both' || direction === 'horizontal') {
isAxisEnabledFlags.x = true;
element.addCls(horizontalCls);
}
if (direction === 'both' || direction === 'vertical') {
isAxisEnabledFlags.y = true;
element.addCls(verticalCls);
}
},
/**
* Returns `true` if a specified axis is enabled.
* @param {String} axis The axis to check (`x` or `y`).
* @return {Boolean} `true` if the axis is enabled.
*/
isAxisEnabled: function(axis) {
this.getDirection();
return this.isAxisEnabledFlags[axis];
},
/**
* @private
* @return {Object}
*/
applyMomentumEasing: function(easing) {
var defaultClass = Ext.fx.easing.BoundMomentum;
return {
x: Ext.factory(easing, defaultClass),
y: Ext.factory(easing, defaultClass)
};
},
/**
* @private
* @return {Object}
*/
applyBounceEasing: function(easing) {
var defaultClass = Ext.fx.easing.EaseOut;
return {
x: Ext.factory(easing, defaultClass),
y: Ext.factory(easing, defaultClass)
};
},
updateBounceEasing: function(easing) {
this.getTranslatable().setEasingX(easing.x).setEasingY(easing.y);
},
/**
* @private
* @return {Object}
*/
applySlotSnapEasing: function(easing) {
var defaultClass = Ext.fx.easing.EaseOut;
return {
x: Ext.factory(easing, defaultClass),
y: Ext.factory(easing, defaultClass)
};
},
/**
* @private
* @return {Object}
*/
getMinPosition: function() {
var minPosition = this.minPosition;
if (!minPosition) {
this.minPosition = minPosition = {
x: 0,
y: 0
};
this.fireEvent('minpositionchange', this, minPosition);
}
return minPosition;
},
/**
* @private
* @return {Object}
*/
getMaxPosition: function() {
var maxPosition = this.maxPosition,
size, containerSize;
if (!maxPosition) {
size = this.getSize();
containerSize = this.getContainerSize();
this.maxPosition = maxPosition = {
x: Math.max(0, size.x - containerSize.x),
y: Math.max(0, size.y - containerSize.y)
};
this.fireEvent('maxpositionchange', this, maxPosition);
}
return maxPosition;
},
/**
* @private
*/
refreshMaxPosition: function() {
this.maxPosition = null;
this.getMaxPosition();
},
/**
* @private
* @return {Object}
*/
applyContainerSize: function(size) {
var containerDom = this.getContainer().dom,
x, y;
if (!containerDom) {
return;
}
this.givenContainerSize = size;
if (size === 'auto') {
x = containerDom.offsetWidth;
y = containerDom.offsetHeight;
}
else {
x = size.x;
y = size.y;
}
return {
x: x,
y: y
};
},
/**
* @private
* @param {String/Object} size
* @return {Object}
*/
applySize: function(size) {
var dom = this.getElement().dom,
x, y;
if (!dom) {
return;
}
this.givenSize = size;
if (size === 'auto') {
x = dom.offsetWidth;
y = dom.offsetHeight;
}
else if (typeof size == 'number') {
x = size;
y = size;
}
else {
x = size.x;
y = size.y;
}
return {
x: x,
y: y
};
},
/**
* @private
*/
updateAutoRefresh: function(autoRefresh) {
this.getElement().toggleListener(autoRefresh, 'resize', 'onElementResize', this);
this.getContainer().toggleListener(autoRefresh, 'resize', 'onContainerResize', this);
},
applySlotSnapSize: function(snapSize) {
if (typeof snapSize == 'number') {
return {
x: snapSize,
y: snapSize
};
}
return snapSize;
},
applySlotSnapOffset: function(snapOffset) {
if (typeof snapOffset == 'number') {
return {
x: snapOffset,
y: snapOffset
};
}
return snapOffset;
},
/**
* @private
* Returns the container for this scroller
*/
getContainer: function() {
var container = this.container,
element;
if (!container) {
element = this.getElement().getParent();
this.container = container = this.FixedHBoxStretching ? element.getParent() : element;
//
if (!container) {
Ext.Logger.error("Making an element scrollable that doesn't have any container");
}
//
container.addCls(this.containerCls);
}
return container;
},
/**
* @private
* @return {Ext.scroll.Scroller} this
* @chainable
*/
refresh: function() {
this.stopAnimation();
this.getTranslatable().refresh();
this.setSize(this.givenSize);
this.setContainerSize(this.givenContainerSize);
this.setDirection(this.givenDirection);
this.fireEvent('refresh', this);
return this;
},
onElementResize: function(element, info) {
this.setSize({
x: info.width,
y: info.height
});
this.refresh();
},
onContainerResize: function(container, info) {
this.setContainerSize({
x: info.width,
y: info.height
});
this.refresh();
},
/**
* Scrolls to the given location.
*
* @param {Number} x The scroll position on the x axis.
* @param {Number} y The scroll position on the y axis.
* @param {Boolean/Object} animation (optional) Whether or not to animate the scrolling to the new position.
*
* @return {Ext.scroll.Scroller} this
* @chainable
*/
scrollTo: function(x, y, animation) {
if (this.isDestroyed) {
return this;
}
var translatable = this.getTranslatable(),
position = this.position,
positionChanged = false,
translationX, translationY;
if (this.isAxisEnabled('x')) {
if (isNaN(x) || typeof x != 'number') {
x = position.x;
}
else {
if (position.x !== x) {
position.x = x;
positionChanged = true;
}
}
translationX = -x;
}
if (this.isAxisEnabled('y')) {
if (isNaN(y) || typeof y != 'number') {
y = position.y;
}
else {
if (position.y !== y) {
position.y = y;
positionChanged = true;
}
}
translationY = -y;
}
if (positionChanged) {
if (animation !== undefined && animation !== false) {
translatable.translateAnimated(translationX, translationY, animation);
}
else {
this.fireEvent('scroll', this, position.x, position.y);
translatable.translate(translationX, translationY);
}
}
return this;
},
/**
* @private
* @return {Ext.scroll.Scroller} this
* @chainable
*/
scrollToTop: function(animation) {
var initialOffset = this.getInitialOffset();
return this.scrollTo(initialOffset.x, initialOffset.y, animation);
},
/**
* Scrolls to the end of the scrollable view.
* @return {Ext.scroll.Scroller} this
* @chainable
*/
scrollToEnd: function(animation) {
var size = this.getSize(),
cntSize = this.getContainerSize();
return this.scrollTo(size.x - cntSize.x, size.y - cntSize.y, animation);
},
/**
* Change the scroll offset by the given amount.
* @param {Number} x The offset to scroll by on the x axis.
* @param {Number} y The offset to scroll by on the y axis.
* @param {Boolean/Object} animation (optional) Whether or not to animate the scrolling to the new position.
* @return {Ext.scroll.Scroller} this
* @chainable
*/
scrollBy: function(x, y, animation) {
var position = this.position;
x = (typeof x == 'number') ? x + position.x : null;
y = (typeof y == 'number') ? y + position.y : null;
return this.scrollTo(x, y, animation);
},
/**
* @private
*/
onTouchStart: function() {
this.isTouching = true;
this.stopAnimation();
},
/**
* @private
*/
onTouchEnd: function() {
var position = this.position;
this.isTouching = false;
if (!this.isDragging && this.snapToSlot()) {
this.fireEvent('scrollstart', this, position.x, position.y);
}
},
/**
* @private
*/
onDragStart: function(e) {
var direction = this.getDirection(),
absDeltaX = e.absDeltaX,
absDeltaY = e.absDeltaY,
directionLock = this.getDirectionLock(),
startPosition = this.startPosition,
flickStartPosition = this.flickStartPosition,
flickStartTime = this.flickStartTime,
lastDragPosition = this.lastDragPosition,
currentPosition = this.position,
dragDirection = this.dragDirection,
x = currentPosition.x,
y = currentPosition.y,
now = Ext.Date.now();
this.isDragging = true;
if (directionLock && direction !== 'both') {
if ((direction === 'horizontal' && absDeltaX > absDeltaY)
|| (direction === 'vertical' && absDeltaY > absDeltaX)) {
e.stopPropagation();
}
else {
this.isDragging = false;
return;
}
}
lastDragPosition.x = x;
lastDragPosition.y = y;
flickStartPosition.x = x;
flickStartPosition.y = y;
startPosition.x = x;
startPosition.y = y;
flickStartTime.x = now;
flickStartTime.y = now;
dragDirection.x = 0;
dragDirection.y = 0;
this.dragStartTime = now;
this.isDragging = true;
this.fireEvent('scrollstart', this, x, y);
},
/**
* @private
*/
onAxisDrag: function(axis, delta) {
if (!this.isAxisEnabled(axis)) {
return;
}
var flickStartPosition = this.flickStartPosition,
flickStartTime = this.flickStartTime,
lastDragPosition = this.lastDragPosition,
dragDirection = this.dragDirection,
old = this.position[axis],
min = this.getMinPosition()[axis],
max = this.getMaxPosition()[axis],
start = this.startPosition[axis],
last = lastDragPosition[axis],
current = start - delta,
lastDirection = dragDirection[axis],
restrictFactor = this.getOutOfBoundRestrictFactor(),
startMomentumResetTime = this.getStartMomentumResetTime(),
now = Ext.Date.now(),
distance;
if (current < min) {
current *= restrictFactor;
}
else if (current > max) {
distance = current - max;
current = max + distance * restrictFactor;
}
if (current > last) {
dragDirection[axis] = 1;
}
else if (current < last) {
dragDirection[axis] = -1;
}
if ((lastDirection !== 0 && (dragDirection[axis] !== lastDirection))
|| (now - flickStartTime[axis]) > startMomentumResetTime) {
flickStartPosition[axis] = old;
flickStartTime[axis] = now;
}
lastDragPosition[axis] = current;
},
/**
* @private
*/
onDrag: function(e) {
if (!this.isDragging) {
return;
}
var lastDragPosition = this.lastDragPosition;
this.onAxisDrag('x', e.deltaX);
this.onAxisDrag('y', e.deltaY);
this.scrollTo(lastDragPosition.x, lastDragPosition.y);
},
/**
* @private
*/
onDragEnd: function(e) {
var easingX, easingY;
if (!this.isDragging) {
return;
}
this.dragEndTime = Ext.Date.now();
this.onDrag(e);
this.isDragging = false;
easingX = this.getAnimationEasing('x', e);
easingY = this.getAnimationEasing('y', e);
if (easingX || easingY) {
this.getTranslatable().animate(easingX, easingY);
}
else {
this.onScrollEnd();
}
},
/**
* @private
*/
getAnimationEasing: function(axis, e) {
if (!this.isAxisEnabled(axis)) {
return null;
}
var currentPosition = this.position[axis],
minPosition = this.getMinPosition()[axis],
maxPosition = this.getMaxPosition()[axis],
maxAbsVelocity = this.getMaxAbsoluteVelocity(),
boundValue = null,
dragEndTime = this.dragEndTime,
velocity = e.flick.velocity[axis],
easing;
if (currentPosition < minPosition) {
boundValue = minPosition;
}
else if (currentPosition > maxPosition) {
boundValue = maxPosition;
}
// Out of bound, to be pulled back
if (boundValue !== null) {
easing = this.getBounceEasing()[axis];
easing.setConfig({
startTime: dragEndTime,
startValue: -currentPosition,
endValue: -boundValue
});
return easing;
}
if (velocity === 0) {
return null;
}
if (velocity < -maxAbsVelocity) {
velocity = -maxAbsVelocity;
}
else if (velocity > maxAbsVelocity) {
velocity = maxAbsVelocity;
}
if (Ext.browser.is.IE) {
velocity *= 2;
}
easing = this.getMomentumEasing()[axis];
easing.setConfig({
startTime: dragEndTime,
startValue: -currentPosition,
startVelocity: velocity * 1.5,
minMomentumValue: -maxPosition,
maxMomentumValue: 0
});
return easing;
},
/**
* @private
*/
onAnimationFrame: function(translatable, x, y) {
var position = this.position;
position.x = -x;
position.y = -y;
this.fireEvent('scroll', this, position.x, position.y);
},
/**
* @private
*/
onAnimationEnd: function() {
this.snapToBoundary();
this.onScrollEnd();
},
/**
* @private
* Stops the animation of the scroller at any time.
*/
stopAnimation: function() {
this.getTranslatable().stopAnimation();
},
/**
* @private
*/
onScrollEnd: function() {
var position = this.position;
if (this.isTouching || !this.snapToSlot()) {
this.fireEvent('scrollend', this, position.x, position.y);
}
},
/**
* @private
* @return {Boolean}
*/
snapToSlot: function() {
var snapX = this.getSnapPosition('x'),
snapY = this.getSnapPosition('y'),
easing = this.getSlotSnapEasing();
if (snapX !== null || snapY !== null) {
this.scrollTo(snapX, snapY, {
easingX: easing.x,
easingY: easing.y
});
return true;
}
return false;
},
/**
* @private
* @return {Number/null}
*/
getSnapPosition: function(axis) {
var snapSize = this.getSlotSnapSize()[axis],
snapPosition = null,
position, snapOffset, maxPosition, mod;
if (snapSize !== 0 && this.isAxisEnabled(axis)) {
position = this.position[axis];
snapOffset = this.getSlotSnapOffset()[axis];
maxPosition = this.getMaxPosition()[axis];
mod = Math.floor((position - snapOffset) % snapSize);
if (mod !== 0) {
if (position !== maxPosition) {
if (Math.abs(mod) > snapSize / 2) {
snapPosition = Math.min(maxPosition, position + ((mod > 0) ? snapSize - mod : mod - snapSize));
}
else {
snapPosition = position - mod;
}
}
else {
snapPosition = position - mod;
}
}
}
return snapPosition;
},
/**
* @private
*/
snapToBoundary: function() {
var position = this.position,
minPosition = this.getMinPosition(),
maxPosition = this.getMaxPosition(),
minX = minPosition.x,
minY = minPosition.y,
maxX = maxPosition.x,
maxY = maxPosition.y,
x = Math.round(position.x),
y = Math.round(position.y);
if (x < minX) {
x = minX;
}
else if (x > maxX) {
x = maxX;
}
if (y < minY) {
y = minY;
}
else if (y > maxY) {
y = maxY;
}
this.scrollTo(x, y);
},
destroy: function() {
var element = this.getElement(),
sizeMonitors = this.sizeMonitors,
container;
if (sizeMonitors) {
sizeMonitors.element.destroy();
sizeMonitors.container.destroy();
}
if (element && !element.isDestroyed) {
element.removeCls(this.cls);
container = this.getContainer();
if (container && !container.isDestroyed) {
container.removeCls(this.containerCls);
}
}
Ext.destroy(this.getTranslatable());
this.callParent(arguments);
}
}, function() {
});
(function() {
var lastTime = 0,
vendors = ['ms', 'moz', 'webkit', 'o'],
ln = vendors.length,
i, vendor;
for (i = 0; i < ln && !window.requestAnimationFrame; ++i) {
vendor = vendors[i];
if (window[vendor + 'RequestAnimationFrame']) {
window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame'];
}
}
if (!window.Ext) {
window.Ext = {};
}
Ext.performance = {};
if (window.performance && window.performance.now) {
Ext.performance.now = function() {
return window.performance.now();
}
}
else {
Ext.performance.now = function() {
return Date.now();
}
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback) {
var currTime = Ext.performance.now(),
timeToCall = Math.max(0, 16 - (currTime - lastTime)),
id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
else {
Ext.trueRequestAnimationFrames = true;
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}());
(function(global) {
/**
* @private
*/
Ext.define('Ext.AnimationQueue', {
singleton: true,
constructor: function() {
var bind = Ext.Function.bind;
this.queue = [];
this.taskQueue = [];
this.runningQueue = [];
this.idleQueue = [];
this.isRunning = false;
this.isIdle = true;
this.run = bind(this.run, this);
this.whenIdle = bind(this.whenIdle, this);
this.processIdleQueueItem = bind(this.processIdleQueueItem, this);
this.processTaskQueueItem = bind(this.processTaskQueueItem, this);
// iOS has a nasty bug which causes pending requestAnimationFrame to not release
// the callback when the WebView is switched back and forth from / to being background process
// We use a watchdog timer to workaround this, and restore the pending state correctly if this happens
// This timer has to be set as an interval from the very beginning and we have to keep it running for
// as long as the app lives, setting it later doesn't seem to work
if (Ext.os.is.iOS) {
setInterval(this.watch, 500);
}
},
/**
*
* @param {Function} fn
* @param {Object} [scope]
* @param {Object} [args]
*/
start: function(fn, scope, args) {
this.queue.push(arguments);
if (!this.isRunning) {
if (this.hasOwnProperty('idleTimer')) {
clearTimeout(this.idleTimer);
delete this.idleTimer;
}
if (this.hasOwnProperty('idleQueueTimer')) {
clearTimeout(this.idleQueueTimer);
delete this.idleQueueTimer;
}
this.isIdle = false;
this.isRunning = true;
//
this.startCountTime = Ext.performance.now();
this.count = 0;
//
this.doStart();
}
},
watch: function() {
if (this.isRunning && Date.now() - this.lastRunTime >= 500) {
this.run();
}
},
run: function() {
if (!this.isRunning) {
return;
}
var queue = this.runningQueue,
i, ln;
this.lastRunTime = Date.now();
this.frameStartTime = Ext.performance.now();
queue.push.apply(queue, this.queue);
for (i = 0, ln = queue.length; i < ln; i++) {
this.invoke(queue[i]);
}
queue.length = 0;
//
var now = this.frameStartTime,
startCountTime = this.startCountTime,
elapse = now - startCountTime,
count = ++this.count;
if (elapse >= 200) {
this.onFpsChanged(count * 1000 / elapse, count, elapse);
this.startCountTime = now;
this.count = 0;
}
//
this.doIterate();
},
//
onFpsChanged: Ext.emptyFn,
onStop: Ext.emptyFn,
//
doStart: function() {
this.animationFrameId = requestAnimationFrame(this.run);
this.lastRunTime = Date.now();
},
doIterate: function() {
this.animationFrameId = requestAnimationFrame(this.run);
},
doStop: function() {
cancelAnimationFrame(this.animationFrameId);
},
/**
*
* @param {Function} fn
* @param {Object} [scope]
* @param {Object} [args]
*/
stop: function(fn, scope, args) {
if (!this.isRunning) {
return;
}
var queue = this.queue,
ln = queue.length,
i, item;
for (i = 0; i < ln; i++) {
item = queue[i];
if (item[0] === fn && item[1] === scope && item[2] === args) {
queue.splice(i, 1);
i--;
ln--;
}
}
if (ln === 0) {
this.doStop();
//
this.onStop();
//
this.isRunning = false;
this.idleTimer = setTimeout(this.whenIdle, 100);
}
},
onIdle: function(fn, scope, args) {
var listeners = this.idleQueue,
i, ln, listener;
for (i = 0, ln = listeners.length; i < ln; i++) {
listener = listeners[i];
if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
return;
}
}
listeners.push(arguments);
if (this.isIdle) {
this.processIdleQueue();
}
},
unIdle: function(fn, scope, args) {
var listeners = this.idleQueue,
i, ln, listener;
for (i = 0, ln = listeners.length; i < ln; i++) {
listener = listeners[i];
if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
listeners.splice(i, 1);
return true;
}
}
return false;
},
queueTask: function(fn, scope, args) {
this.taskQueue.push(arguments);
this.processTaskQueue();
},
dequeueTask: function(fn, scope, args) {
var listeners = this.taskQueue,
i, ln, listener;
for (i = 0, ln = listeners.length; i < ln; i++) {
listener = listeners[i];
if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
listeners.splice(i, 1);
i--;
ln--;
}
}
},
invoke: function(listener) {
var fn = listener[0],
scope = listener[1],
args = listener[2];
fn = (typeof fn == 'string' ? scope[fn] : fn);
if (Ext.isArray(args)) {
fn.apply(scope, args);
}
else {
fn.call(scope, args);
}
},
whenIdle: function() {
this.isIdle = true;
this.processIdleQueue();
},
processIdleQueue: function() {
if (!this.hasOwnProperty('idleQueueTimer')) {
this.idleQueueTimer = setTimeout(this.processIdleQueueItem, 1);
}
},
processIdleQueueItem: function() {
delete this.idleQueueTimer;
if (!this.isIdle) {
return;
}
var listeners = this.idleQueue,
listener;
if (listeners.length > 0) {
listener = listeners.shift();
this.invoke(listener);
this.processIdleQueue();
}
},
processTaskQueue: function() {
if (!this.hasOwnProperty('taskQueueTimer')) {
this.taskQueueTimer = setTimeout(this.processTaskQueueItem, 15);
}
},
processTaskQueueItem: function() {
delete this.taskQueueTimer;
var listeners = this.taskQueue,
listener;
if (listeners.length > 0) {
listener = listeners.shift();
this.invoke(listener);
this.processTaskQueue();
}
},
showFps: function() {
if (!Ext.trueRequestAnimationFrames) {
alert("This browser does not support requestAnimationFrame. The FPS listed will not be accurate");
}
Ext.onReady(function() {
Ext.Viewport.add([{
xtype: 'component',
bottom: 50,
left: 0,
width: 50,
height: 20,
html: 'Average',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__averageFps',
xtype: 'component',
bottom: 0,
left: 0,
width: 50,
height: 50,
html: '0',
style: 'background-color: red; color: white; text-align: center; line-height: 50px;'
},
{
xtype: 'component',
bottom: 50,
left: 50,
width: 50,
height: 20,
html: 'Min (Last 1k)',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__minFps',
xtype: 'component',
bottom: 0,
left: 50,
width: 50,
height: 50,
html: '0',
style: 'background-color: orange; color: white; text-align: center; line-height: 50px;'
},
{
xtype: 'component',
bottom: 50,
left: 100,
width: 50,
height: 20,
html: 'Max (Last 1k)',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__maxFps',
xtype: 'component',
bottom: 0,
left: 100,
width: 50,
height: 50,
html: '0',
style: 'background-color: yellow; color: black; text-align: center; line-height: 50px;'
},
{
xtype: 'component',
bottom: 50,
left: 150,
width: 50,
height: 20,
html: 'Current',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__currentFps',
xtype: 'component',
bottom: 0,
left: 150,
width: 50,
height: 50,
html: '0',
style: 'background-color: green; color: white; text-align: center; line-height: 50px;'
}
]);
Ext.AnimationQueue.resetFps();
});
},
resetFps: function() {
var currentFps = Ext.getCmp('__currentFps'),
averageFps = Ext.getCmp('__averageFps'),
minFps = Ext.getCmp('__minFps'),
maxFps = Ext.getCmp('__maxFps'),
min = 1000,
max = 0,
count = 0,
sum = 0;
Ext.AnimationQueue.onFpsChanged = function(fps) {
count++;
if (!(count % 10)) {
min = 1000;
max = 0;
}
sum += fps;
min = Math.min(min, fps);
max = Math.max(max, fps);
currentFps.setHtml(Math.round(fps));
averageFps.setHtml(Math.round(sum / count));
minFps.setHtml(Math.round(min));
maxFps.setHtml(Math.round(max));
};
}
}, function() {
/*
Global FPS indicator. Add ?showfps to use in any application. Note that this REQUIRES true requestAnimationFrame
to be accurate.
*/
//
var paramsString = window.location.search.substr(1),
paramsArray = paramsString.split("&");
if (paramsArray.indexOf("showfps") !== -1) {
Ext.AnimationQueue.showFps();
}
//
});
})(this);
/**
* @private
* Handle batch read / write of DOMs, currently used in SizeMonitor + PaintMonitor
*/
Ext.define('Ext.TaskQueue', {
singleton: true,
pending: false,
mode: true,
constructor: function() {
this.readQueue = [];
this.writeQueue = [];
this.run = Ext.Function.bind(this.run, this);
this.watch = Ext.Function.bind(this.watch, this);
// iOS has a nasty bug which causes pending requestAnimationFrame to not release
// the callback when the WebView is switched back and forth from / to being background process
// We use a watchdog timer to workaround this, and restore the pending state correctly if this happens
// This timer has to be set as an interval from the very beginning and we have to keep it running for
// as long as the app lives, setting it later doesn't seem to work
if (Ext.os.is.iOS) {
setInterval(this.watch, 500);
}
},
requestRead: function(fn, scope, args) {
this.request(true);
this.readQueue.push(arguments);
},
requestWrite: function(fn, scope, args) {
this.request(false);
this.writeQueue.push(arguments);
},
request: function(mode) {
if (!this.pending) {
this.pendingTime = Date.now();
this.pending = true;
this.mode = mode;
if (mode) {
setTimeout(this.run, 1);
} else {
requestAnimationFrame(this.run);
}
}
},
watch: function() {
if (this.pending && Date.now() - this.pendingTime >= 500) {
this.run();
}
},
run: function() {
this.pending = false;
var readQueue = this.readQueue,
writeQueue = this.writeQueue,
request = null,
queue;
if (this.mode) {
queue = readQueue;
if (writeQueue.length > 0) {
request = false;
}
}
else {
queue = writeQueue;
if (readQueue.length > 0) {
request = true;
}
}
var tasks = queue.slice(),
i, ln, task, fn, scope;
queue.length = 0;
for (i = 0, ln = tasks.length; i < ln; i++) {
task = tasks[i];
fn = task[0];
scope = task[1];
if (typeof fn == 'string') {
fn = scope[fn];
}
if (task.length > 2) {
fn.apply(scope, task[2]);
}
else {
fn.call(scope);
}
}
tasks.length = 0;
if (request !== null) {
this.request(request);
}
}
});
/**
* @private
*/
Ext.define('Ext.scroll.indicator.Abstract', {
extend: Ext.Component ,
config: {
baseCls: 'x-scroll-indicator',
axis: 'x',
value: null,
length: null,
minLength: 6,
hidden: true,
ui: 'dark',
/**
* @cfg {Boolean} [autoHide=true] Set to `false` to always show the indicator for this axis.
*/
autoHide : true
},
cachedConfig: {
ratio: 1,
barCls: 'x-scroll-bar',
active: true
},
barElement: null,
barLength: 0,
gapLength: 0,
getElementConfig: function() {
return {
reference: 'barElement',
children: [this.callParent()]
};
},
applyRatio: function(ratio) {
if (isNaN(ratio) || ratio > 1) {
ratio = 1;
}
return ratio;
},
refresh: function() {
var bar = this.barElement,
barDom = bar.dom,
ratio = this.getRatio(),
axis = this.getAxis(),
barLength = (axis === 'x') ? barDom.offsetWidth : barDom.offsetHeight,
length = barLength * ratio;
this.barLength = barLength;
this.gapLength = barLength - length;
this.setLength(length);
this.updateValue(this.getValue());
},
updateBarCls: function(barCls) {
this.barElement.addCls(barCls);
},
updateAxis: function(axis) {
this.element.addCls(this.getBaseCls(), null, axis);
this.barElement.addCls(this.getBarCls(), null, axis);
},
updateValue: function(value) {
var barLength = this.barLength,
gapLength = this.gapLength,
length = this.getLength(),
newLength, offset, extra;
if (value <= 0) {
offset = 0;
this.updateLength(this.applyLength(length + value * barLength));
}
else if (value >= 1) {
extra = Math.round((value - 1) * barLength);
newLength = this.applyLength(length - extra);
extra = length - newLength;
this.updateLength(newLength);
offset = gapLength + extra;
}
else {
offset = gapLength * value;
}
this.setOffset(offset);
},
updateActive: function(active) {
this.barElement[active ? 'addCls' : 'removeCls']('active');
},
doSetHidden: function(hidden) {
var me = this;
if (hidden) {
me.getAutoHide() && me.setOffset(-10000);
} else {
delete me.lastLength;
delete me.lastOffset;
me.updateValue(me.getValue());
}
},
applyLength: function(length) {
return Math.max(this.getMinLength(), length);
},
updateLength: function(length) {
length = Math.round(length);
if (this.lastLength === length) {
return;
}
this.lastLength = length;
Ext.TaskQueue.requestWrite('doUpdateLength', this, [length]);
},
doUpdateLength: function(length){
if (!this.isDestroyed) {
var axis = this.getAxis(),
element = this.element;
if (axis === 'x') {
element.setWidth(length);
}
else {
element.setHeight(length);
}
}
},
setOffset: function(offset) {
offset = Math.round(offset);
if (this.lastOffset === offset || this.lastOffset === -10000) {
return;
}
this.lastOffset = offset;
Ext.TaskQueue.requestWrite('doSetOffset', this,[offset]);
},
doSetOffset: function(offset) {
if (!this.isDestroyed) {
var axis = this.getAxis(),
element = this.element;
if (axis === 'x') {
element.translate(offset, 0);
}
else {
element.translate(0, offset);
}
}
}
});
/**
* @private
*/
Ext.define('Ext.scroll.indicator.CssTransform', {
extend: Ext.scroll.indicator.Abstract ,
config: {
cls: 'csstransform'
}
});
/**
* @private
*/
Ext.define('Ext.scroll.indicator.ScrollPosition', {
extend: Ext.scroll.indicator.Abstract ,
config: {
cls: 'scrollposition'
},
getElementConfig: function() {
var config = this.callParent(arguments);
config.children.unshift({
className: 'x-scroll-bar-stretcher'
});
return config;
},
updateValue: function(value) {
if (this.gapLength === 0) {
if (value >= 1) {
value--;
}
this.setOffset(this.barLength * value);
}
else {
this.setOffset(this.gapLength * value);
}
},
doUpdateLength: function() {
if (!this.isDestroyed) {
var scrollOffset = this.barLength,
element = this.element;
this.callParent(arguments);
if (this.getAxis() === 'x') {
element.setLeft(scrollOffset);
}
else {
element.setTop(scrollOffset);
}
}
},
doSetOffset: function(offset) {
if (!this.isDestroyed) {
var barLength = this.barLength,
minLength = this.getMinLength(),
barDom = this.barElement.dom;
if (offset !== -10000) {
offset = Math.min(barLength - minLength, Math.max(offset, minLength - this.getLength()));
offset = barLength - offset;
}
if (this.getAxis() === 'x') {
barDom.scrollLeft = offset;
}
else {
barDom.scrollTop = offset;
}
}
}
});
/**
* @private
*/
Ext.define('Ext.scroll.indicator.Rounded', {
extend: Ext.scroll.indicator.Abstract ,
config: {
cls: 'rounded'
},
constructor: function() {
this.callParent(arguments);
this.transformPropertyName = Ext.browser.getVendorProperyName('transform');
},
getElementConfig: function() {
var config = this.callParent();
config.children[0].children = [
{
reference: 'startElement'
},
{
reference: 'middleElement'
},
{
reference: 'endElement'
}
];
return config;
},
refresh: function() {
var axis = this.getAxis(),
startElementDom = this.startElement.dom,
endElementDom = this.endElement.dom,
middleElement = this.middleElement,
startElementLength, endElementLength;
if (axis === 'x') {
startElementLength = startElementDom.offsetWidth;
endElementLength = endElementDom.offsetWidth;
middleElement.setLeft(startElementLength);
}
else {
startElementLength = startElementDom.offsetHeight;
endElementLength = endElementDom.offsetHeight;
middleElement.setTop(startElementLength);
}
this.startElementLength = startElementLength;
this.endElementLength = endElementLength;
this.callParent();
},
doUpdateLength: function(length) {
if (!this.isDestroyed) {
var axis = this.getAxis(),
endElement = this.endElement,
middleElementStyle = this.middleElement.dom.style,
endElementLength = this.endElementLength,
endElementOffset = length - endElementLength,
middleElementLength = endElementOffset - this.startElementLength,
transformPropertyName = this.transformPropertyName;
if (axis === 'x') {
endElement.translate(endElementOffset, 0);
middleElementStyle[transformPropertyName] = 'translate3d(0, 0, 0) scaleX(' + middleElementLength + ')';
}
else {
endElement.translate(0, endElementOffset);
middleElementStyle[transformPropertyName] = 'translate3d(0, 0, 0) scaleY(' + middleElementLength + ')';
}
}
}
});
/**
* @private
*/
Ext.define('Ext.scroll.Indicator', {
alternateClassName: 'Ext.util.Indicator',
constructor: function(config) {
var namespace = Ext.scroll.indicator;
switch (Ext.browser.getPreferredTranslationMethod(config)) {
case 'scrollposition':
return new namespace.ScrollPosition(config);
case 'csstransform':
if (Ext.browser.is.AndroidStock4) {
return new namespace.CssTransform(config);
} else {
return new namespace.Rounded(config);
}
}
}
});
/**
* This is a simple container that is used to compile content and a {@link Ext.scroll.View} instance. It also
* provides scroll indicators.
*
* 99% of the time all you need to use in this class is {@link #getScroller}.
*
* This should never be extended.
*/
Ext.define('Ext.scroll.View', {
extend: Ext.Evented ,
alternateClassName: 'Ext.util.ScrollView',
config: {
/**
* @cfg {String} indicatorsUi
* The style of the indicators of this view. Available options are `dark` or `light`.
*/
indicatorsUi: 'dark',
element: null,
scroller: {},
indicators: {
x: {
axis: 'x'
},
y: {
axis: 'y'
}
},
indicatorsHidingDelay: 100,
cls: Ext.baseCSSPrefix + 'scroll-view'
},
/**
* @method getScroller
* Returns the scroller instance in this view. Checkout the documentation of {@link Ext.scroll.Scroller} and
* {@link Ext.Container#getScrollable} for more information.
* @return {Ext.scroll.View} The scroller
*/
/**
* @private
*/
processConfig: function(config) {
if (!config) {
return null;
}
if (typeof config == 'string') {
config = {
direction: config
};
}
config = Ext.merge({}, config);
var scrollerConfig = config.scroller,
name;
if (!scrollerConfig) {
config.scroller = scrollerConfig = {};
}
for (name in config) {
if (config.hasOwnProperty(name)) {
if (!this.hasConfig(name)) {
scrollerConfig[name] = config[name];
delete config[name];
}
}
}
return config;
},
constructor: function(config) {
config = this.processConfig(config);
this.useIndicators = { x: true, y: true };
this.doHideIndicators = Ext.Function.bind(this.doHideIndicators, this);
this.initConfig(config);
},
setConfig: function(config) {
return this.callParent([this.processConfig(config)]);
},
updateIndicatorsUi: function(newUi) {
var indicators = this.getIndicators();
indicators.x.setUi(newUi);
indicators.y.setUi(newUi);
},
applyScroller: function(config, currentScroller) {
return Ext.factory(config, Ext.scroll.Scroller, currentScroller);
},
applyIndicators: function(config, indicators) {
var defaultClass = Ext.scroll.Indicator,
useIndicators = this.useIndicators;
if (!config) {
config = {};
}
if (!config.x) {
useIndicators.x = false;
config.x = {};
}
if (!config.y) {
useIndicators.y = false;
config.y = {};
}
return {
x: Ext.factory(config.x, defaultClass, indicators && indicators.x),
y: Ext.factory(config.y, defaultClass, indicators && indicators.y)
};
},
updateIndicators: function(indicators) {
this.indicatorsGrid = Ext.Element.create({
className: 'x-scroll-bar-grid-wrapper',
children: [{
className: 'x-scroll-bar-grid',
children: [
{
children: [{}, {
children: [indicators.y.barElement]
}]
},
{
children: [{
children: [indicators.x.barElement]
}, {}]
}
]
}]
});
},
updateScroller: function(scroller) {
scroller.on({
scope: this,
scrollstart: 'onScrollStart',
scroll: 'onScroll',
scrollend: 'onScrollEnd',
refresh: 'refreshIndicators'
});
},
isAxisEnabled: function(axis) {
return this.getScroller().isAxisEnabled(axis) && this.useIndicators[axis];
},
applyElement: function(element) {
if (element) {
return Ext.get(element);
}
},
updateElement: function(element) {
var scroller = this.getScroller(),
scrollerElement;
scrollerElement = element.getFirstChild().getFirstChild();
if (this.FixedHBoxStretching) {
scrollerElement = scrollerElement.getFirstChild();
}
element.addCls(this.getCls());
element.insertFirst(this.indicatorsGrid);
scroller.setElement(scrollerElement);
this.refreshIndicators();
return this;
},
showIndicators: function() {
var indicators = this.getIndicators();
if (this.hasOwnProperty('indicatorsHidingTimer')) {
clearTimeout(this.indicatorsHidingTimer);
delete this.indicatorsHidingTimer;
}
if (this.isAxisEnabled('x')) {
indicators.x.show();
}
if (this.isAxisEnabled('y')) {
indicators.y.show();
}
},
hideIndicators: function() {
var delay = this.getIndicatorsHidingDelay();
if (delay > 0) {
this.indicatorsHidingTimer = setTimeout(this.doHideIndicators, delay);
}
else {
this.doHideIndicators();
}
},
doHideIndicators: function() {
var indicators = this.getIndicators();
if (this.isAxisEnabled('x')) {
indicators.x.hide();
}
if (this.isAxisEnabled('y')) {
indicators.y.hide();
}
},
onScrollStart: function() {
this.onScroll.apply(this, arguments);
this.showIndicators();
},
onScrollEnd: function() {
this.hideIndicators();
},
onScroll: function(scroller, x, y) {
this.setIndicatorValue('x', x);
this.setIndicatorValue('y', y);
//
if (this.isBenchmarking) {
this.framesCount++;
}
//
},
//
isBenchmarking: false,
framesCount: 0,
getCurrentFps: function() {
var now = Date.now(),
fps;
if (!this.isBenchmarking) {
this.isBenchmarking = true;
fps = 0;
}
else {
fps = Math.round(this.framesCount * 1000 / (now - this.framesCountStartTime));
}
this.framesCountStartTime = now;
this.framesCount = 0;
return fps;
},
//
setIndicatorValue: function(axis, scrollerPosition) {
if (!this.isAxisEnabled(axis)) {
return this;
}
var scroller = this.getScroller(),
scrollerMaxPosition = scroller.getMaxPosition()[axis],
scrollerContainerSize = scroller.getContainerSize()[axis],
value;
if (scrollerMaxPosition === 0) {
value = scrollerPosition / scrollerContainerSize;
if (scrollerPosition >= 0) {
value += 1;
}
}
else {
if (scrollerPosition > scrollerMaxPosition) {
value = 1 + ((scrollerPosition - scrollerMaxPosition) / scrollerContainerSize);
}
else if (scrollerPosition < 0) {
value = scrollerPosition / scrollerContainerSize;
}
else {
value = scrollerPosition / scrollerMaxPosition;
}
}
this.getIndicators()[axis].setValue(value);
},
refreshIndicator: function(axis) {
if (!this.isAxisEnabled(axis)) {
return this;
}
var scroller = this.getScroller(),
indicator = this.getIndicators()[axis],
scrollerContainerSize = scroller.getContainerSize()[axis],
scrollerSize = scroller.getSize()[axis],
ratio = scrollerContainerSize / scrollerSize;
indicator.setRatio(ratio);
indicator.refresh();
},
refresh: function() {
return this.getScroller().refresh();
},
refreshIndicators: function() {
var indicators = this.getIndicators();
indicators.x.setActive(this.isAxisEnabled('x'));
indicators.y.setActive(this.isAxisEnabled('y'));
this.refreshIndicator('x');
this.refreshIndicator('y');
},
destroy: function() {
var element = this.getElement(),
indicators = this.getIndicators();
Ext.destroy(this.getScroller(), this.indicatorsGrid);
if (this.hasOwnProperty('indicatorsHidingTimer')) {
clearTimeout(this.indicatorsHidingTimer);
delete this.indicatorsHidingTimer;
}
if (element && !element.isDestroyed) {
element.removeCls(this.getCls());
}
indicators.x.destroy();
indicators.y.destroy();
delete this.indicatorsGrid;
this.callParent(arguments);
}
});
/**
* @private
*/
Ext.define('Ext.behavior.Scrollable', {
extend: Ext.behavior.Behavior ,
constructor: function() {
this.listeners = {
painted: 'onComponentPainted',
scope: this
};
this.callParent(arguments);
},
onComponentPainted: function() {
this.scrollView.refresh();
},
setConfig: function(config) {
var scrollView = this.scrollView,
component = this.component,
scrollerElement, extraWrap, scroller, direction;
if (config) {
if (!scrollView) {
this.scrollView = scrollView = new Ext.scroll.View(config);
scrollView.on('destroy', 'onScrollViewDestroy', this);
component.setUseBodyElement(true);
this.scrollerElement = scrollerElement = component.innerElement;
if (!Ext.feature.has.ProperHBoxStretching) {
scroller = scrollView.getScroller();
direction = (Ext.isObject(config) ? config.direction : config) || 'auto';
if (direction !== 'vertical') {
extraWrap = scrollerElement.wrap();
extraWrap.addCls(Ext.baseCSSPrefix + 'translatable-hboxfix');
if (direction == 'horizontal') {
extraWrap.setStyle({height: '100%'});
}
this.scrollContainer = extraWrap.wrap();
scrollView.FixedHBoxStretching = scroller.FixedHBoxStretching = true;
}
else {
this.scrollContainer = scrollerElement.wrap();
}
}
else {
this.scrollContainer = scrollerElement.wrap();
}
scrollView.setElement(component.bodyElement);
if (component.isPainted()) {
this.onComponentPainted();
}
component.on(this.listeners);
}
else if (Ext.isString(config) || Ext.isObject(config)) {
scrollView.setConfig(config);
}
}
else if (scrollView) {
scrollView.destroy();
}
return this;
},
getScrollView: function() {
return this.scrollView;
},
onScrollViewDestroy: function() {
var component = this.component,
scrollerElement = this.scrollerElement;
if (!scrollerElement.isDestroyed) {
this.scrollerElement.unwrap();
}
this.scrollContainer.destroy();
if (!component.isDestroyed) {
component.un(this.listeners);
}
delete this.scrollerElement;
delete this.scrollView;
delete this.scrollContainer;
},
onComponentDestroy: function() {
var scrollView = this.scrollView;
if (scrollView) {
scrollView.destroy();
}
}
});
/**
*
* @private
* A utility class to disable input fields in WP7,8 because they stay still clickable even if they are under other elements.
*/
Ext.define('Ext.util.InputBlocker', {
singleton: true,
blockInputs: function () {
if (Ext.browser.is.ie) {
Ext.select('.x-field-text .x-field-input:not(.x-item-disabled) .x-input-el, .x-field-textarea .x-field-input:not(.x-item-disabled) .x-input-el, .x-field-search .x-field-input:not(.x-item-disabled) .x-input-el').each(function (item) {
if (item.dom.offsetWidth > 0) {
item.dom.setAttribute('disabled', true);
item.dom.setAttribute('overlayfix', true);
}
});
}
},
unblockInputs: function () {
if (Ext.browser.is.ie) {
Ext.select('[overlayfix]').each(function (item) {
item.dom.removeAttribute('disabled');
item.dom.removeAttribute('overlayfix');
});
}
}
});
/**
* A simple class used to mask any {@link Ext.Container}.
*
* This should rarely be used directly, instead look at the {@link Ext.Container#masked} configuration.
*
* ## Example
*
* @example miniphone
* // Create our container
* var container = Ext.create('Ext.Container', {
* html: 'My container!'
* });
*
* // Add the container to the Viewport
* Ext.Viewport.add(container);
*
* // Mask the container
* container.setMasked(true);
*/
Ext.define('Ext.Mask', {
extend: Ext.Component ,
xtype: 'mask',
config: {
/**
* @cfg
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'mask',
/**
* @cfg {Boolean} transparent True to make this mask transparent.
*/
transparent: false,
/**
* @cfg
* @hide
*/
top: 0,
/**
* @cfg
* @hide
*/
left: 0,
/**
* @cfg
* @hide
*/
right: 0,
/**
* @cfg
* @hide
*/
bottom: 0
},
/**
* @event tap
* A tap event fired when a user taps on this mask
* @param {Ext.Mask} this The mask instance
* @param {Ext.EventObject} e The event object
*/
initialize: function() {
this.callSuper();
this.element.on('*', 'onEvent', this);
this.on({
hide: 'onHide'
});
},
onHide: function(){
Ext.util.InputBlocker.unblockInputs();
// Oh how I loves the Android
if (Ext.browser.is.AndroidStock4 && Ext.os.version.getMinor() === 0) {
var firstChild = this.element.getFirstChild();
if (firstChild) {
firstChild.redraw();
}
}
},
onEvent: function(e) {
var controller = arguments[arguments.length - 1];
if (controller.info.eventName === 'tap') {
this.fireEvent('tap', this, e);
return false;
}
if (e && e.stopEvent) {
e.stopEvent();
}
return false;
},
updateTransparent: function(newTransparent) {
this[newTransparent ? 'addCls' : 'removeCls'](this.getBaseCls() + '-transparent');
}
});
/**
* A Container has all of the abilities of {@link Ext.Component Component}, but lets you nest other Components inside
* it. Applications are made up of lots of components, usually nested inside one another. Containers allow you to
* render and arrange child Components inside them. Most apps have a single top-level Container called a Viewport,
* which takes up the entire screen. Inside of this are child components, for example in a mail app the Viewport
* Container's two children might be a message List and an email preview pane.
*
* Containers give the following extra functionality:
*
* - Adding child Components at instantiation and run time
* - Removing child Components
* - Specifying a [Layout](../../../core_concepts/layouts.html)
*
* Layouts determine how the child Components should be laid out on the screen. In our mail app example we'd use an
* HBox layout so that we can pin the email list to the left hand edge of the screen and allow the preview pane to
* occupy the rest. There are several layouts in Sencha Touch 2, each of which help you achieve your desired
* application structure, further explained in the [Layout guide](../../../core_concepts/layouts.html).
*
* ## Adding Components to Containers
*
* As we mentioned above, Containers are special Components that can have child Components arranged by a Layout. One of
* the code samples above showed how to create a Panel with 2 child Panels already defined inside it but it's easy to
* do this at run time too:
*
* @example miniphone
* //this is the Panel we'll be adding below
* var aboutPanel = Ext.create('Ext.Panel', {
* html: 'About this app'
* });
*
* //this is the Panel we'll be adding to
* var mainPanel = Ext.create('Ext.Panel', {
* fullscreen: true,
*
* layout: 'hbox',
* defaults: {
* flex: 1
* },
*
* items: {
* html: 'First Panel',
* style: 'background-color: #5E99CC;'
* }
* });
*
* //now we add the first panel inside the second
* mainPanel.add(aboutPanel);
*
* Here we created three Panels in total. First we made the aboutPanel, which we might use to tell the user a little
* about the app. Then we create one called mainPanel, which already contains a third Panel in its
* {@link Ext.Container#cfg-items items} configuration, with some dummy text ("First Panel"). Finally, we add the first
* panel to the second by calling the {@link Ext.Container#method-add add} method on `mainPanel`.
*
* In this case we gave our mainPanel another hbox layout, but we also introduced some
* {@link Ext.Container#defaults defaults}. These are applied to every item in the Panel, so in this case every child
* inside `mainPanel` will be given a `flex: 1` configuration. The effect of this is that when we first render the screen
* only a single child is present inside `mainPanel`, so that child takes up the full width available to it. Once the
* `mainPanel.add` line is called though, the `aboutPanel` is rendered inside of it and also given a `flex` of 1, which will
* cause it and the first panel to both receive half the full width of the `mainPanel`.
*
* Likewise, it's easy to remove items from a Container:
*
* mainPanel.remove(aboutPanel);
*
* After this line is run everything is back to how it was, with the first child panel once again taking up the full
* width inside `mainPanel`.
*
* ## Further Reading
*
* See the [Component & Container Guide](../../../core_concepts/components.html) for more information, and check out the
* {@link Ext.Container} class docs also.
*/
Ext.define('Ext.Container', {
extend: Ext.Component ,
alternateClassName: 'Ext.lib.Container',
xtype: 'container',
/**
* @event add
* Fires whenever item added to the Container.
* @param {Ext.Container} this The Container instance.
* @param {Object} item The item added to the Container.
* @param {Number} index The index of the item within the Container.
*/
/**
* @event remove
* Fires whenever item removed from the Container.
* @param {Ext.Container} this The Container instance.
* @param {Object} item The item removed from the Container.
* @param {Number} index The index of the item that was removed.
*/
/**
* @event move
* Fires whenever item moved within the Container.
* @param {Ext.Container} this The Container instance.
* @param {Object} item The item moved within the Container.
* @param {Number} toIndex The new index of the item.
* @param {Number} fromIndex The old index of the item.
*/
/**
* @private
* @event renderedchange
* Fires whenever an item is rendered into a container or derendered
* from a Container.
* @param {Ext.Container} this The Container instance.
* @param {Object} item The item in the Container.
* @param {Boolean} rendered The current rendered status of the item.
*/
/**
* @event activate
* Fires whenever item within the Container is activated.
* @param {Object} newActiveItem The new active item within the container.
* @param {Ext.Container} this The Container instance.
* @param {Object} oldActiveItem The old active item within the container.
*/
/**
* @event deactivate
* Fires whenever item within the Container is deactivated.
* @param {Object} oldActiveItem The old active item within the container.
* @param {Ext.Container} this The Container instance.
* @param {Object} newActiveItem The new active item within the container.
*/
eventedConfig: {
/**
* @cfg {Object/String/Number} activeItem The item from the {@link #cfg-items} collection that will be active first. This is
* usually only meaningful in a {@link Ext.layout.Card card layout}, where only one item can be active at a
* time. If passes a string, it will be assumed to be a {@link Ext.ComponentQuery} selector.
* @accessor
* @evented
*/
activeItem: 0,
/**
* @cfg {Boolean/String/Object} scrollable
* Configuration options to make this Container scrollable. Acceptable values are:
*
* - `'horizontal'`, `'vertical'`, `'both'` to enabling scrolling for that direction.
* - `true`/`false` to explicitly enable/disable scrolling.
*
* Alternatively, you can give it an object which is then passed to the scroller instance:
*
* scrollable: {
* direction: 'vertical',
* directionLock: true
* }
*
* Please look at the {@link Ext.scroll.Scroller} documentation for more example on how to use this.
* @return {Ext.scroll.View} The scroll view.
* @accessor
* @evented
*/
scrollable: null
},
config: {
/**
* @cfg {String/Object/Boolean} cardSwitchAnimation
* Animation to be used during transitions of cards.
* @removed 2.0.0 Please use {@link Ext.layout.Card#animation} instead
*/
/**
* @cfg {Object/String} layout Configuration for this Container's layout. Example:
*
* Ext.create('Ext.Container', {
* layout: {
* type: 'hbox',
* align: 'middle'
* },
* items: [
* {
* xtype: 'panel',
* flex: 1,
* style: 'background-color: red;'
* },
* {
* xtype: 'panel',
* flex: 2,
* style: 'background-color: green'
* }
* ]
* });
*
* See the [Layouts Guide](../../../core_concepts/layouts.html) for more information.
*
* @accessor
*/
layout: null,
/**
* @cfg {Object} control Enables you to easily control Components inside this Container by listening to their
* events and taking some action. For example, if we had a container with a nested Disable button, and we
* wanted to hide the Container when the Disable button is tapped, we could do this:
*
* Ext.create('Ext.Container', {
* control: {
* 'button[text=Disable]': {
* tap: 'hideMe'
* }
* },
*
* hideMe: function () {
* this.hide();
* }
* });
*
* We used a {@link Ext.ComponentQuery} selector to listen to the {@link Ext.Button#tap tap} event on any
* {@link Ext.Button button} anywhere inside the Container that has the {@link Ext.Button#text text} 'Disable'.
* Whenever a Component matching that selector fires the `tap` event our `hideMe` function is called. `hideMe` is
* called with scope: `this` (e.g. `this` is the Container instance).
*
*/
control: {},
/**
* @cfg {Object} defaults A set of default configurations to apply to all child Components in this Container.
* It's often useful to specify defaults when creating more than one items with similar configurations. For
* example here we can specify that each child is a panel and avoid repeating the xtype declaration for each
* one:
*
* Ext.create('Ext.Container', {
* defaults: {
* xtype: 'panel'
* },
* items: [
* {
* html: 'Panel 1'
* },
* {
* html: 'Panel 2'
* }
* ]
* });
*
* @accessor
*/
defaults: null,
/**
* @cfg {Array/Object} items The child items to add to this Container. This is usually an array of Component
* configurations or instances, for example:
*
* Ext.create('Ext.Container', {
* items: [
* {
* xtype: 'panel',
* html: 'This is an item'
* }
* ]
* });
* @accessor
*/
items: null,
/**
* @cfg {Boolean} autoDestroy If `true`, child items will be destroyed as soon as they are {@link #method-remove removed}
* from this container.
* @accessor
*/
autoDestroy: true,
/** @cfg {String} defaultType
* The default {@link Ext.Component xtype} of child Components to create in this Container when a child item
* is specified as a raw configuration object, rather than as an instantiated Component.
* @accessor
*/
defaultType: null,
//@private
useBodyElement: null,
/**
* @cfg {Boolean/Object/Ext.Mask/Ext.LoadMask} masked
* A configuration to allow you to mask this container.
* You can optionally pass an object block with and xtype of `loadmask`, and an optional `message` value to
* display a loading mask. Please refer to the {@link Ext.LoadMask} component to see other configurations.
*
* masked: {
* xtype: 'loadmask',
* message: 'My message'
* }
*
* Alternatively, you can just call the setter at any time with `true`/`false` to show/hide the mask:
*
* setMasked(true); //show the mask
* setMasked(false); //hides the mask
*
* There are also two convenient methods, {@link #method-mask} and {@link #unmask}, to allow you to mask and unmask
* this container at any time.
*
* Remember, the {@link Ext.Viewport} is always a container, so if you want to mask your whole application at anytime,
* can call:
*
* Ext.Viewport.setMasked({
* xtype: 'loadmask',
* message: 'Hello'
* });
*
* @accessor
*/
masked: null,
/**
* @cfg {Boolean} modal `true` to make this Container modal. This will create a mask underneath the Container
* that covers its parent and does not allow the user to interact with any other Components until this
* Container is dismissed.
* @accessor
*/
modal: null,
/**
* @cfg {Boolean} hideOnMaskTap When using a {@link #modal} Component, setting this to `true` will hide the modal
* mask and the Container when the mask is tapped on.
* @accessor
*/
hideOnMaskTap: null
},
isContainer: true,
constructor: function(config) {
var me = this;
me._items = me.items = new Ext.ItemCollection();
me.innerItems = [];
me.onItemAdd = me.onFirstItemAdd;
me.callParent(arguments);
},
getElementConfig: function() {
return {
reference: 'element',
classList: ['x-container', 'x-unsized'],
children: [{
reference: 'innerElement',
className: 'x-inner'
}]
};
},
/**
* Changes the {@link #masked} configuration when its setter is called, which will convert the value
* into a proper object/instance of {@link Ext.Mask}/{@link Ext.LoadMask}. If a mask already exists,
* it will use that instead.
* @param {Boolean/Object/Ext.Mask/Ext.LoadMask} masked
* @return {Object}
*/
applyMasked: function(masked) {
var isVisible = true,
currentMask;
if (masked === false) {
masked = true;
isVisible = false;
}
currentMask = Ext.factory(masked, Ext.Mask, this.getMasked());
if (currentMask) {
this.add(currentMask);
currentMask.setHidden(!isVisible);
}
return currentMask;
},
/**
* Convenience method which calls {@link #setMasked} with a value of `true` (to show the mask). For additional
* functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
* for more information).
*/
mask: function(mask) {
this.setMasked(mask || true);
},
/**
* Convenience method which calls {@link #setMasked} with a value of false (to hide the mask). For additional
* functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
* for more information).
*/
unmask: function() {
this.setMasked(false);
},
setParent: function(container) {
this.callSuper(arguments);
if (container) {
var modal = this.getModal();
if (modal) {
container.insertBefore(modal, this);
modal.setZIndex(this.getZIndex() - 1);
}
}
},
applyModal: function(modal, currentModal) {
var isVisible = true;
if (modal === false) {
modal = true;
isVisible = false;
}
currentModal = Ext.factory(modal, Ext.Mask, currentModal);
if (currentModal) {
currentModal.setVisibility(isVisible);
}
return currentModal;
},
updateModal: function(modal) {
var container = this.getParent();
if (container) {
if (modal) {
container.insertBefore(modal, this);
modal.setZIndex(this.getZIndex() - 1);
}
else {
container.remove(modal);
}
}
},
updateHideOnMaskTap : function(hide) {
var mask = this.getModal();
if (mask) {
mask[hide ? 'on' : 'un'].call(mask, 'tap', 'hide', this);
}
},
updateZIndex: function(zIndex) {
var modal = this.getModal();
this.callParent(arguments);
if (modal) {
modal.setZIndex(zIndex - 1);
}
},
updateBaseCls: function(newBaseCls, oldBaseCls) {
var me = this,
ui = me.getUi();
if (oldBaseCls) {
this.element.removeCls(oldBaseCls);
this.innerElement.removeCls(newBaseCls, null, 'inner');
if (ui) {
this.element.removeCls(this.currentUi);
}
}
if (newBaseCls) {
this.element.addCls(newBaseCls);
this.innerElement.addCls(newBaseCls, null, 'inner');
if (ui) {
this.element.addCls(newBaseCls, null, ui);
this.currentUi = newBaseCls + '-' + ui;
}
}
},
updateUseBodyElement: function(useBodyElement) {
if (useBodyElement) {
this.link('bodyElement', this.innerElement.wrap({
cls: 'x-body'
}));
}
},
applyItems: function(items, collection) {
if (items) {
var me = this;
me.getDefaultType();
me.getDefaults();
if (me.initialized && collection.length > 0) {
me.removeAll();
}
me.add(items);
//Don't need to call setActiveItem when Container is first initialized
if (me.initialized) {
var activeItem = me.initialConfig.activeItem || me.config.activeItem || 0;
me.setActiveItem(activeItem);
}
}
},
/**
* @private
*/
applyControl: function(selectors) {
var selector, key, listener, listeners;
for (selector in selectors) {
listeners = selectors[selector];
for (key in listeners) {
listener = listeners[key];
if (Ext.isObject(listener)) {
listener.delegate = selector;
}
}
listeners.delegate = selector;
this.addListener(listeners);
}
return selectors;
},
/**
* Initialize layout and event listeners the very first time an item is added
* @private
*/
onFirstItemAdd: function() {
delete this.onItemAdd;
if (this.innerHtmlElement && !this.getHtml()) {
this.innerHtmlElement.destroy();
delete this.innerHtmlElement;
}
this.on('innerstatechange', 'onItemInnerStateChange', this, {
delegate: '> component'
});
return this.onItemAdd.apply(this, arguments);
},
//
updateLayout: function(newLayout, oldLayout) {
if (oldLayout && oldLayout.isLayout) {
Ext.Logger.error('Replacing a layout after one has already been initialized is not currently supported.');
}
},
//
getLayout: function() {
var layout = this.layout;
if (!layout) {
layout = this.link('_layout', this.link('layout', Ext.factory(this._layout || 'default', Ext.layout.Default, null, 'layout')));
layout.setContainer(this);
}
return layout;
},
updateDefaultType: function(defaultType) {
// Cache the direct reference to the default item class here for performance
this.defaultItemClass = Ext.ClassManager.getByAlias('widget.' + defaultType);
//
if (!this.defaultItemClass) {
Ext.Logger.error("Invalid defaultType of: '" + defaultType + "', must be a valid component xtype");
}
//
},
applyDefaults: function(defaults) {
if (defaults) {
this.factoryItem = this.factoryItemWithDefaults;
return defaults;
}
},
factoryItem: function(item) {
//
if (!item) {
Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
"or an existing component instance");
}
//
return Ext.factory(item, this.defaultItemClass);
},
factoryItemWithDefaults: function(item) {
//
if (!item) {
Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
"or an existing component instance");
}
//
var me = this,
defaults = me.getDefaults(),
instance;
if (!defaults) {
return Ext.factory(item, me.defaultItemClass);
}
// Existing instance
if (item.isComponent) {
instance = item;
// Apply defaults only if this is not already an item of this container
if (defaults && item.isInnerItem() && !me.has(instance)) {
instance.setConfig(defaults, true);
}
}
// Config object
else {
if (defaults && !item.ignoreDefaults) {
// Note:
// - defaults is only applied to inner items
// - we merge the given config together with defaults into a new object so that the original object stays intact
if (!(
item.hasOwnProperty('left') &&
item.hasOwnProperty('right') &&
item.hasOwnProperty('top') &&
item.hasOwnProperty('bottom') &&
item.hasOwnProperty('docked') &&
item.hasOwnProperty('centered')
)) {
item = Ext.mergeIf({}, item, defaults);
}
}
instance = Ext.factory(item, me.defaultItemClass);
}
return instance;
},
/**
* Adds one or more Components to this Container. Example:
*
* var myPanel = Ext.create('Ext.Panel', {
* html: 'This will be added to a Container'
* });
*
* myContainer.add([myPanel]);
*
* @param {Object/Object[]/Ext.Component/Ext.Component[]} newItems The new items to add to the Container.
* @return {Ext.Component} The last item added to the Container from the `newItems` array.
*/
add: function(newItems) {
var me = this,
i, ln, item, newActiveItem;
if (Ext.isArray(newItems)) {
for (i = 0, ln = newItems.length; i < ln; i++) {
item = me.factoryItem(newItems[i]);
this.doAdd(item);
if (!newActiveItem && !this.getActiveItem() && this.innerItems.length > 0 && item.isInnerItem()) {
newActiveItem = item;
}
}
} else {
item = me.factoryItem(newItems);
this.doAdd(item);
if (!newActiveItem && !this.getActiveItem() && this.innerItems.length > 0 && item.isInnerItem()) {
newActiveItem = item;
}
}
if (newActiveItem) {
this.setActiveItem(newActiveItem);
}
return item;
},
/**
* @private
* @param {Ext.Component} item
*/
doAdd: function(item) {
var me = this,
items = me.getItems(),
index;
if (!items.has(item)) {
index = items.length;
items.add(item);
if (item.isInnerItem()) {
me.insertInner(item);
}
item.setParent(me);
me.onItemAdd(item, index);
}
},
/**
* Removes an item from this Container, optionally destroying it.
* @param {Object} item The item to remove.
* @param {Boolean} [destroy] Calls the Component's {@link Ext.Component#method-destroy destroy}
* method if `true`.
* @return {Ext.Component} this
*/
remove: function(item, destroy) {
var me = this,
index = me.indexOf(item),
innerItems = me.getInnerItems();
if (destroy === undefined) {
destroy = me.getAutoDestroy();
}
if (index !== -1) {
if (!me.removingAll && innerItems.length > 1 && item === me.getActiveItem()) {
me.on({
activeitemchange: 'doRemove',
scope: me,
single: true,
order: 'after',
args: [item, index, destroy]
});
me.doResetActiveItem(innerItems.indexOf(item));
}
else {
me.doRemove(item, index, destroy);
if (innerItems.length === 0) {
me.setActiveItem(null);
}
}
}
return me;
},
doResetActiveItem: function(innerIndex) {
if (innerIndex === 0) {
this.setActiveItem(1);
}
else {
this.setActiveItem(0);
}
},
doRemove: function(item, index, destroy) {
var me = this;
me.items.remove(item);
if (item.isInnerItem()) {
me.removeInner(item);
}
me.onItemRemove(item, index, destroy);
item.setParent(null);
if (destroy) {
item.destroy();
}
},
/**
* Removes all items currently in the Container, optionally destroying them all.
* @param {Boolean} destroy If `true`, {@link Ext.Component#method-destroy destroys}
* each removed Component.
* @param {Boolean} everything If `true`, completely remove all items including
* docked / centered and floating items.
* @return {Ext.Component} this
*/
removeAll: function(destroy, everything) {
var items = this.items,
ln = items.length,
i = 0,
item;
if (typeof destroy != 'boolean') {
destroy = this.getAutoDestroy();
}
everything = Boolean(everything);
// removingAll flag is used so we don't unnecessarily change activeItem while removing all items.
this.removingAll = true;
for (; i < ln; i++) {
item = items.getAt(i);
if (item && (everything || item.isInnerItem())) {
this.doRemove(item, i, destroy);
i--;
ln--;
}
}
this.setActiveItem(null);
this.removingAll = false;
return this;
},
/**
* Returns the Component for a given index in the Container's {@link #property-items}.
* @param {Number} index The index of the Component to return.
* @return {Ext.Component} The item at the specified `index`, if found.
*/
getAt: function(index) {
return this.items.getAt(index);
},
getInnerAt: function(index) {
return this.innerItems[index];
},
/**
* Removes the Component at the specified index:
*
* myContainer.removeAt(0); // removes the first item
*
* @param {Number} index The index of the Component to remove.
*/
removeAt: function(index) {
var item = this.getAt(index);
if (item) {
this.remove(item);
}
return this;
},
/**
* Removes an inner Component at the specified index:
*
* myContainer.removeInnerAt(0); // removes the first item of the innerItems property
*
* @param {Number} index The index of the Component to remove.
*/
removeInnerAt: function(index) {
var item = this.getInnerItems()[index];
if (item) {
this.remove(item);
}
return this;
},
/**
* @private
*/
has: function(item) {
return this.getItems().indexOf(item) != -1;
},
/**
* @private
*/
hasInnerItem: function(item) {
return this.innerItems.indexOf(item) != -1;
},
/**
* @private
*/
indexOf: function(item) {
return this.getItems().indexOf(item);
},
innerIndexOf: function(item) {
return this.innerItems.indexOf(item);
},
/**
* @private
* @param {Ext.Component} item
* @param {Number} index
*/
insertInner: function(item, index) {
var items = this.getItems().items,
innerItems = this.innerItems,
currentInnerIndex = innerItems.indexOf(item),
newInnerIndex = -1,
nextSibling;
if (currentInnerIndex !== -1) {
innerItems.splice(currentInnerIndex, 1);
}
if (typeof index == 'number') {
do {
nextSibling = items[++index];
} while (nextSibling && !nextSibling.isInnerItem());
if (nextSibling) {
newInnerIndex = innerItems.indexOf(nextSibling);
innerItems.splice(newInnerIndex, 0, item);
}
}
if (newInnerIndex === -1) {
innerItems.push(item);
newInnerIndex = innerItems.length - 1;
}
if (currentInnerIndex !== -1) {
this.onInnerItemMove(item, newInnerIndex, currentInnerIndex);
}
return this;
},
onInnerItemMove: Ext.emptyFn,
/**
* @private
* @param {Ext.Component} item
*/
removeInner: function(item) {
Ext.Array.remove(this.innerItems, item);
return this;
},
/**
* Adds a child Component at the given index. For example, here's how we can add a new item, making it the first
* child Component of this Container:
*
* myContainer.insert(0, {xtype: 'panel', html: 'new item'});
*
* @param {Number} index The index to insert the Component at.
* @param {Object} item The Component to insert.
*/
insert: function(index, item) {
var me = this,
i;
//
if (typeof index != 'number') {
Ext.Logger.error("Invalid index of '" + index + "', must be a valid number");
}
//
if (Ext.isArray(item)) {
for (i = item.length - 1; i >= 0; i--) {
me.insert(index, item[i]);
}
return me;
}
item = this.factoryItem(item);
this.doInsert(index, item);
return item;
},
/**
* @private
* @param {Number} index
* @param {Ext.Component} item
*/
doInsert: function(index, item) {
var me = this,
items = me.items,
itemsLength = items.length,
currentIndex, isInnerItem;
isInnerItem = item.isInnerItem();
if (index > itemsLength) {
index = itemsLength;
}
if (items[index - 1] === item) {
return me;
}
currentIndex = me.indexOf(item);
if (currentIndex !== -1) {
if (currentIndex < index) {
index -= 1;
}
items.removeAt(currentIndex);
}
items.insert(index, item);
if (currentIndex === -1) {
item.setParent(me);
}
if (isInnerItem) {
me.insertInner(item, index);
}
if (currentIndex !== -1) {
me.onItemMove(item, index, currentIndex);
}
else {
me.onItemAdd(item, index);
}
},
/**
* @private
*/
insertFirst: function(item) {
return this.insert(0, item);
},
/**
* @private
*/
insertLast: function(item) {
return this.insert(this.getItems().length, item);
},
/**
* @private
*/
insertBefore: function(item, relativeToItem) {
var index = this.indexOf(relativeToItem);
if (index !== -1) {
this.insert(index, item);
}
return this;
},
/**
* @private
*/
insertAfter: function(item, relativeToItem) {
var index = this.indexOf(relativeToItem);
if (index !== -1) {
this.insert(index + 1, item);
}
return this;
},
/**
* @private
*/
onItemAdd: function(item, index) {
this.doItemLayoutAdd(item, index);
if (this.initialized) {
this.fireEvent('add', this, item, index);
}
},
doItemLayoutAdd: function(item, index) {
var layout = this.getLayout();
if (this.isRendered() && item.setRendered(true)) {
item.fireAction('renderedchange', [this, item, true], 'onItemAdd', layout, { args: [item, index] });
}
else {
layout.onItemAdd(item, index);
}
},
/**
* @private
*/
onItemRemove: function(item, index, destroying) {
this.doItemLayoutRemove(item, index, destroying);
this.fireEvent('remove', this, item, index);
},
doItemLayoutRemove: function(item, index, destroying) {
var layout = this.getLayout();
if (this.isRendered() && item.setRendered(false)) {
item.fireAction('renderedchange', [this, item, false], 'onItemRemove', layout, { args: [item, index, destroying] });
}
else {
layout.onItemRemove(item, index, destroying);
}
},
/**
* @private
*/
onItemMove: function(item, toIndex, fromIndex) {
if (item.isDocked()) {
item.setDocked(null);
}
this.doItemLayoutMove(item, toIndex, fromIndex);
this.fireEvent('move', this, item, toIndex, fromIndex);
},
doItemLayoutMove: function(item, toIndex, fromIndex) {
this.getLayout().onItemMove(item, toIndex, fromIndex);
},
onItemInnerStateChange: function(item, isInner) {
var layout = this.getLayout();
if (isInner) {
this.insertInner(item, this.items.indexOf(item));
}
else {
this.removeInner(item);
}
layout.onItemInnerStateChange.apply(layout, arguments);
},
/**
* Returns all inner {@link #property-items} of this container. `inner` means that the item is not `docked` or
* `floating`.
* @return {Array} The inner items of this container.
*/
getInnerItems: function() {
return this.innerItems;
},
/**
* Returns all the {@link Ext.Component#docked} items in this container.
* @return {Array} The docked items of this container.
*/
getDockedItems: function() {
var items = this.getItems().items,
dockedItems = [],
ln = items.length,
item, i;
for (i = 0; i < ln; i++) {
item = items[i];
if (item.isDocked()) {
dockedItems.push(item);
}
}
return dockedItems;
},
/**
* @private
*/
applyActiveItem: function(activeItem, currentActiveItem) {
var innerItems = this.getInnerItems();
// Make sure the items are already initialized
this.getItems();
// No items left to be active, reset back to 0 on falsy changes
if (!activeItem && innerItems.length === 0) {
return 0;
}
else if (typeof activeItem == 'number') {
activeItem = Math.max(0, Math.min(activeItem, innerItems.length - 1));
activeItem = innerItems[activeItem];
if (activeItem) {
return activeItem;
}
else if (currentActiveItem) {
return null;
}
}
else if (activeItem) {
var item;
//ComponentQuery selector?
if (typeof activeItem == 'string') {
item = this.child(activeItem);
activeItem = {
xtype : activeItem
};
}
if (!item || !item.isComponent) {
item = this.factoryItem(activeItem);
}
this.pendingActiveItem = item;
//
if (!item.isInnerItem()) {
Ext.Logger.error("Setting activeItem to be a non-inner item");
}
//
if (!this.has(item)) {
this.add(item);
}
return item;
}
},
/**
* Animates to the supplied `activeItem` with a specified animation. Currently this only works
* with a Card layout. This passed animation will override any default animations on the
* container, for a single card switch. The animation will be destroyed when complete.
* @param {Object/Number} activeItem The item or item index to make active.
* @param {Object/Ext.fx.layout.Card} animation Card animation configuration or instance.
*/
animateActiveItem: function(activeItem, animation) {
var layout = this.getLayout(),
defaultAnimation;
if (this.activeItemAnimation) {
this.activeItemAnimation.destroy();
}
this.activeItemAnimation = animation = new Ext.fx.layout.Card(animation);
if (animation && layout.isCard) {
animation.setLayout(layout);
defaultAnimation = layout.getAnimation();
if (defaultAnimation) {
defaultAnimation.disable();
}
animation.on('animationend', function() {
if (defaultAnimation) {
defaultAnimation.enable();
}
animation.destroy();
}, this);
}
return this.setActiveItem(activeItem);
},
/**
* @private
*/
doSetActiveItem: function(newActiveItem, oldActiveItem) {
delete this.pendingActiveItem;
if (oldActiveItem) {
oldActiveItem.fireEvent('deactivate', oldActiveItem, this, newActiveItem);
}
if (newActiveItem) {
newActiveItem.fireEvent('activate', newActiveItem, this, oldActiveItem);
}
},
show:function(){
this.callParent(arguments);
var modal = this.getModal();
if (modal) {
modal.setHidden(false);
}
return this;
},
hide:function(){
this.callParent(arguments);
var modal = this.getModal();
if (modal) {
modal.setHidden(true);
}
return this;
},
doSetHidden: function(hidden) {
var modal = this.getModal();
if (modal && (modal.getHidden() !== hidden)) {
modal.setHidden(hidden);
}
this.callSuper(arguments);
},
/**
* @private
*/
setRendered: function(rendered) {
if (this.callParent(arguments)) {
var items = this.items.items,
i, ln;
for (i = 0,ln = items.length; i < ln; i++) {
items[i].setRendered(rendered);
}
return true;
}
return false;
},
/**
* @private
*/
getScrollableBehavior: function() {
var behavior = this.scrollableBehavior;
if (!behavior) {
behavior = this.scrollableBehavior = new Ext.behavior.Scrollable(this);
}
return behavior;
},
/**
* @private
*/
applyScrollable: function(config) {
if (typeof config === 'boolean') {
//
if (config === false && !(this.getHeight() !== null || this.heightLayoutSized || (this.getTop() !== null && this.getBottom() !== null))) {
Ext.Logger.warn("This container is set to scrollable: false but has no specified height. " +
"You may need to set the container to scrollable: null or provide a height.", this);
}
//
this.getScrollableBehavior().setConfig({disabled: !config});
} else if (config && !config.isObservable) {
this.getScrollableBehavior().setConfig(config);
}
return config;
},
doSetScrollable: function() {
// Used for plugins when they need to reinitialize scroller listeners
},
/**
* Returns an the scrollable instance for this container, which is a {@link Ext.scroll.View} class.
*
* Please checkout the documentation for {@link Ext.scroll.View}, {@link Ext.scroll.View#getScroller}
* and {@link Ext.scroll.Scroller} for more information.
* @return {Ext.scroll.View} The scroll view.
*/
getScrollable: function() {
return this.getScrollableBehavior().getScrollView();
},
// Used by ComponentQuery to retrieve all of the items
// which can potentially be considered a child of this Container.
// This should be overridden by components which have child items
// that are not contained in items. For example `dockedItems`, `menu`, etc
// @private
getRefItems: function(deep) {
var items = this.getItems().items.slice(),
ln = items.length,
i, item;
if (deep) {
for (i = 0; i < ln; i++) {
item = items[i];
if (item.getRefItems) {
items = items.concat(item.getRefItems(true));
}
}
}
return items;
},
/**
* Examines this container's `{@link #property-items}` property
* and gets a direct child component of this container.
* @param {String/Number} component This parameter may be any of the following:
*
* - {String} : representing the `itemId`
* or `{@link Ext.Component#getId id}` of the child component.
* - {Number} : representing the position of the child component
* within the `{@link #property-items}` property.
*
* For additional information see {@link Ext.util.MixedCollection#get}.
* @return {Ext.Component} The component (if found).
*/
getComponent: function(component) {
if (Ext.isObject(component)) {
component = component.getItemId();
}
return this.getItems().get(component);
},
/**
* Finds a docked item of this container using a reference, `id `or an `index` of its location
* in {@link #getDockedItems}.
* @param {String/Number} component The `id` or `index` of the component to find.
* @return {Ext.Component/Boolean} The docked component, if found.
*/
getDockedComponent: function(component) {
if (Ext.isObject(component)) {
component = component.getItemId();
}
var dockedItems = this.getDockedItems(),
ln = dockedItems.length,
item, i;
if (Ext.isNumber(component)) {
return dockedItems[component];
}
for (i = 0; i < ln; i++) {
item = dockedItems[i];
if (item.id == component) {
return item;
}
}
return false;
},
/**
* Retrieves all descendant components which match the passed selector.
* Executes an Ext.ComponentQuery.query using this container as its root.
* @param {String} selector Selector complying to an Ext.ComponentQuery selector.
* @return {Array} Ext.Component's which matched the selector.
*/
query: function(selector) {
return Ext.ComponentQuery.query(selector, this);
},
/**
* Retrieves the first direct child of this container which matches the passed selector.
* The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
* @param {String} selector An {@link Ext.ComponentQuery} selector.
* @return {Ext.Component}
*/
child: function(selector) {
return this.query('> ' + selector)[0] || null;
},
/**
* Retrieves the first descendant of this container which matches the passed selector.
* The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
* @param {String} selector An {@link Ext.ComponentQuery} selector.
* @return {Ext.Component}
*/
down: function(selector) {
return this.query(selector)[0] || null;
},
destroy: function() {
var me = this,
modal = me.getModal();
if (modal) {
modal.destroy();
}
me.removeAll(true, true);
me.unlink('_scrollable');
Ext.destroy(me.items);
me.callSuper();
}
}, function() {
this.addMember('defaultItemClass', this);
});
/**
* Represents a 2D point with x and y properties, useful for comparison and instantiation
* from an event:
*
* var point = Ext.util.Point.fromEvent(e);
*/
Ext.define('Ext.util.Point', {
radianToDegreeConstant: 180 / Math.PI,
statics: {
/**
* Returns a new instance of {@link Ext.util.Point} based on the `pageX` / `pageY` values of the given event.
* @static
* @param {Event} e The event.
* @return {Ext.util.Point}
*/
fromEvent: function(e) {
var changedTouches = e.changedTouches,
touch = (changedTouches && changedTouches.length > 0) ? changedTouches[0] : e;
return this.fromTouch(touch);
},
/**
* Returns a new instance of {@link Ext.util.Point} based on the `pageX` / `pageY` values of the given touch.
* @static
* @param {Event} touch
* @return {Ext.util.Point}
*/
fromTouch: function(touch) {
return new this(touch.pageX, touch.pageY);
},
/**
* Returns a new point from an object that has `x` and `y` properties, if that object is not an instance
* of {@link Ext.util.Point}. Otherwise, returns the given point itself.
* @param {Object} object
* @return {Ext.util.Point}
*/
from: function(object) {
if (!object) {
return new this(0, 0);
}
if (!(object instanceof this)) {
return new this(object.x, object.y);
}
return object;
}
},
/**
* Creates point on 2D plane.
* @param {Number} [x=0] X coordinate.
* @param {Number} [y=0] Y coordinate.
*/
constructor: function(x, y) {
if (typeof x == 'undefined') {
x = 0;
}
if (typeof y == 'undefined') {
y = 0;
}
this.x = x;
this.y = y;
return this;
},
/**
* Copy a new instance of this point.
* @return {Ext.util.Point} The new point.
*/
clone: function() {
return new this.self(this.x, this.y);
},
/**
* Clones this Point.
* @deprecated 2.0.0 Please use {@link #clone} instead.
* @return {Ext.util.Point} The new point.
*/
copy: function() {
return this.clone.apply(this, arguments);
},
/**
* Copy the `x` and `y` values of another point / object to this point itself.
* @param {Ext.util.Point/Object} point.
* @return {Ext.util.Point} This point.
*/
copyFrom: function(point) {
this.x = point.x;
this.y = point.y;
return this;
},
/**
* Returns a human-eye-friendly string that represents this point,
* useful for debugging.
* @return {String} For example `Point[12,8]`.
*/
toString: function() {
return "Point[" + this.x + "," + this.y + "]";
},
/**
* Compare this point and another point.
* @param {Ext.util.Point/Object} point The point to compare with, either an instance
* of {@link Ext.util.Point} or an object with `x` and `y` properties.
* @return {Boolean} Returns whether they are equivalent.
*/
equals: function(point) {
return (this.x === point.x && this.y === point.y);
},
/**
* Whether the given point is not away from this point within the given threshold amount.
* @param {Ext.util.Point/Object} point The point to check with, either an instance
* of {@link Ext.util.Point} or an object with `x` and `y` properties.
* @param {Object/Number} threshold Can be either an object with `x` and `y` properties or a number.
* @return {Boolean}
*/
isCloseTo: function(point, threshold) {
if (typeof threshold == 'number') {
threshold = {x: threshold};
threshold.y = threshold.x;
}
var x = point.x,
y = point.y,
thresholdX = threshold.x,
thresholdY = threshold.y;
return (this.x <= x + thresholdX && this.x >= x - thresholdX &&
this.y <= y + thresholdY && this.y >= y - thresholdY);
},
/**
* Returns `true` if this point is close to another one.
* @deprecated 2.0.0 Please use {@link #isCloseTo} instead.
* @return {Boolean}
*/
isWithin: function() {
return this.isCloseTo.apply(this, arguments);
},
/**
* Translate this point by the given amounts.
* @param {Number} x Amount to translate in the x-axis.
* @param {Number} y Amount to translate in the y-axis.
* @return {Boolean}
*/
translate: function(x, y) {
this.x += x;
this.y += y;
return this;
},
/**
* Compare this point with another point when the `x` and `y` values of both points are rounded. For example:
* [100.3,199.8] will equals to [100, 200].
* @param {Ext.util.Point/Object} point The point to compare with, either an instance
* of Ext.util.Point or an object with `x` and `y` properties.
* @return {Boolean}
*/
roundedEquals: function(point) {
if (typeof point != 'object') {
point = { x: 0, y: 0};
}
return (Math.round(this.x) === Math.round(point.x) &&
Math.round(this.y) === Math.round(point.y));
},
getDistanceTo: function(point) {
if (typeof point != 'object') {
point = { x: 0, y: 0};
}
var deltaX = this.x - point.x,
deltaY = this.y - point.y;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
},
getAngleTo: function(point) {
if (typeof point != 'object') {
point = { x: 0, y: 0};
}
var deltaX = this.x - point.x,
deltaY = this.y - point.y;
return Math.atan2(deltaY, deltaX) * this.radianToDegreeConstant;
}
});
/**
* @class Ext.util.LineSegment
*
* Utility class that represents a line segment, constructed by two {@link Ext.util.Point}
*/
Ext.define('Ext.util.LineSegment', {
/**
* Creates new LineSegment out of two points.
* @param {Ext.util.Point} point1
* @param {Ext.util.Point} point2
*/
constructor: function(point1, point2) {
var Point = Ext.util.Point;
this.point1 = Point.from(point1);
this.point2 = Point.from(point2);
},
/**
* Returns the point where two lines intersect.
* @param {Ext.util.LineSegment} lineSegment The line to intersect with.
* @return {Ext.util.Point}
*/
intersects: function(lineSegment) {
var point1 = this.point1,
point2 = this.point2,
point3 = lineSegment.point1,
point4 = lineSegment.point2,
x1 = point1.x,
x2 = point2.x,
x3 = point3.x,
x4 = point4.x,
y1 = point1.y,
y2 = point2.y,
y3 = point3.y,
y4 = point4.y,
d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4),
xi, yi;
if (d == 0) {
return null;
}
xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)
|| xi < Math.min(x3, x4) || xi > Math.max(x3, x4)
|| yi < Math.min(y1, y2) || yi > Math.max(y1, y2)
|| yi < Math.min(y3, y4) || yi > Math.max(y3, y4)) {
return null;
}
return new Ext.util.Point(xi, yi);
},
getLength: function() {
return Math.abs(this.point1.getDistanceTo(this.point2));
},
getAngleToX: function() {
var point1 = this.point1,
point2 = this.point2,
deltaY = point2.y - point1.y,
deltaX = point2.x - point1.x;
return Math.atan2(deltaY, deltaX);
},
getInBetweenPoint: function(distance) {
var point1 = this.point1,
angle = this.getAngleToX(),
x = point1.x + Math.cos(angle) * distance,
y = point1.y + Math.sin(angle) * distance;
return new Ext.util.Point(x, y);
},
/**
* Returns string representation of the line. Useful for debugging.
* @return {String} For example `Point[12,8] Point[0,0]`
*/
toString: function() {
return this.point1.toString() + " " + this.point2.toString();
}
});
/**
* Panels are most useful as Overlays - containers that float over your application. They contain extra styling such
* that when you {@link #showBy} another component, the container will appear in a rounded black box with a 'tip'
* pointing to a reference component.
*
* If you don't need this extra functionality, you should use {@link Ext.Container} instead. See the
* [Overlays example](#!/example/overlays) for more use cases.
*
* @example miniphone preview
*
* var button = Ext.create('Ext.Button', {
* text: 'Button',
* id: 'rightButton'
* });
*
* Ext.create('Ext.Container', {
* fullscreen: true,
* items: [
* {
* docked: 'top',
* xtype: 'titlebar',
* items: [
* button
* ]
* }
* ]
* });
*
* Ext.create('Ext.Panel', {
* html: 'Floating Panel',
* left: 0,
* padding: 10
* }).showBy(button);
*
* For more information, see our [Floating Components Guide](../../../components/floating_components.html).
*/
Ext.define('Ext.Panel', {
extend: Ext.Container ,
alternateClassName: 'Ext.lib.Panel',
xtype: 'panel',
isPanel: true,
config: {
baseCls: Ext.baseCSSPrefix + 'panel',
/**
* @cfg {Number/Boolean/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.
* @deprecated 2.0.0
*/
bodyPadding: null,
/**
* @cfg {Number/Boolean/String} bodyMargin
* A shortcut for setting a margin style on the body element. The value can either be
* a number to be applied to all sides, or a normal CSS string describing margins.
* @deprecated 2.0.0
*/
bodyMargin: null,
/**
* @cfg {Number/Boolean/String} bodyBorder
* A shortcut for setting a border style on the body element. The value can either be
* a number to be applied to all sides, or a normal CSS string describing borders.
* @deprecated 2.0.0
*/
bodyBorder: null
},
getElementConfig: function() {
return {
reference: 'element',
classList: ['x-container', 'x-unsized'],
children: [
{
reference: 'innerElement',
className: 'x-inner'
},
{
reference: 'tipElement',
className: 'x-anchor',
hidden: true
}
]
};
},
applyBodyPadding: function(bodyPadding) {
if (bodyPadding === true) {
bodyPadding = 5;
}
if (bodyPadding) {
bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
}
return bodyPadding;
},
updateBodyPadding: function(newBodyPadding) {
this.element.setStyle('padding', newBodyPadding);
},
applyBodyMargin: function(bodyMargin) {
if (bodyMargin === true) {
bodyMargin = 5;
}
if (bodyMargin) {
bodyMargin = Ext.dom.Element.unitizeBox(bodyMargin);
}
return bodyMargin;
},
updateBodyMargin: function(newBodyMargin) {
this.element.setStyle('margin', newBodyMargin);
},
applyBodyBorder: function(bodyBorder) {
if (bodyBorder === true) {
bodyBorder = 1;
}
if (bodyBorder) {
bodyBorder = Ext.dom.Element.unitizeBox(bodyBorder);
}
return bodyBorder;
},
updateBodyBorder: function(newBodyBorder) {
this.element.setStyle('border-width', newBodyBorder);
},
alignTo: function(component, alignment) {
var alignmentInfo = this.getAlignmentInfo(component, alignment);
if(alignmentInfo.isAligned) return;
var tipElement = this.tipElement;
tipElement.hide();
if (this.currentTipPosition) {
tipElement.removeCls('x-anchor-' + this.currentTipPosition);
}
this.callParent(arguments);
var LineSegment = Ext.util.LineSegment,
alignToElement = component.isComponent ? component.renderElement : component,
element = this.renderElement,
alignToBox = alignToElement.getPageBox(),
box = element.getPageBox(),
left = box.left,
top = box.top,
right = box.right,
bottom = box.bottom,
centerX = left + (box.width / 2),
centerY = top + (box.height / 2),
leftTopPoint = { x: left, y: top },
rightTopPoint = { x: right, y: top },
leftBottomPoint = { x: left, y: bottom },
rightBottomPoint = { x: right, y: bottom },
boxCenterPoint = { x: centerX, y: centerY },
alignToCenterX = alignToBox.left + (alignToBox.width / 2),
alignToCenterY = alignToBox.top + (alignToBox.height / 2),
alignToBoxCenterPoint = { x: alignToCenterX, y: alignToCenterY },
centerLineSegment = new LineSegment(boxCenterPoint, alignToBoxCenterPoint),
offsetLeft = 0,
offsetTop = 0,
tipSize, tipWidth, tipHeight, tipPosition, tipX, tipY;
tipElement.setVisibility(false);
tipElement.show();
tipSize = tipElement.getSize();
tipWidth = tipSize.width;
tipHeight = tipSize.height;
if (centerLineSegment.intersects(new LineSegment(leftTopPoint, rightTopPoint))) {
tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - (tipWidth));
tipY = top;
offsetTop = tipHeight + 10;
tipPosition = 'top';
}
else if (centerLineSegment.intersects(new LineSegment(leftTopPoint, leftBottomPoint))) {
tipX = left;
tipY = Math.min(Math.max(alignToCenterY + (tipWidth / 2), tipWidth * 1.6), bottom - (tipWidth / 2.2));
offsetLeft = tipHeight + 10;
tipPosition = 'left';
}
else if (centerLineSegment.intersects(new LineSegment(leftBottomPoint, rightBottomPoint))) {
tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - tipWidth);
tipY = bottom;
offsetTop = -tipHeight - 10;
tipPosition = 'bottom';
}
else if (centerLineSegment.intersects(new LineSegment(rightTopPoint, rightBottomPoint))) {
tipX = right;
tipY = Math.max(Math.min(alignToCenterY - tipHeight, bottom - tipWidth * 1.3), tipWidth / 2);
offsetLeft = -tipHeight - 10;
tipPosition = 'right';
}
if (tipX || tipY) {
this.currentTipPosition = tipPosition;
tipElement.addCls('x-anchor-' + tipPosition);
tipElement.setLeft(tipX - left);
tipElement.setTop(tipY - top);
tipElement.setVisibility(true);
this.setLeft(this.getLeft() + offsetLeft);
this.setTop(this.getTop() + offsetTop);
}
}
});
/**
* A simple class to display a button in Sencha Touch.
*
* There are various different styles of Button you can create by using the {@link #icon},
* {@link #iconCls}, {@link #iconAlign}, {@link #ui}, and {@link #text}
* configurations.
*
* ## Simple Button
*
* Here is a Button in it's simplest form:
*
* @example miniphone
* var button = Ext.create('Ext.Button', {
* text: 'Button'
* });
* Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
*
* ## Icons
*
* You can also create a Button with just an icon using the {@link #iconCls} configuration:
*
* @example miniphone
* var button = Ext.create('Ext.Button', {
* iconCls: 'refresh'
* });
* Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
*
* Sencha provides the "Font" and "PNG" icons packs from http://wwww.pictos.cc.
* Use icons with the {@link Global_CSS#icon icon} mixin in your Sass.
*
* ## Badges
*
* Buttons can also have a badge on them, by using the {@link #badgeText} configuration:
*
* @example
* Ext.create('Ext.Container', {
* fullscreen: true,
* padding: 10,
* items: {
* xtype: 'button',
* text: 'My Button',
* badgeText: '2'
* }
* });
*
* ## UI
*
* Buttons also come with a range of different default UIs. Here are the included UIs
* available (if {@link #$include-button-uis $include-button-uis} is set to `true`):
*
* - **normal** - a basic gray button
* - **back** - a back button
* - **forward** - a forward button
* - **round** - a round button
* - **action** - shaded using the {@link Global_CSS#$active-color $active-color} (dark blue by default)
* - **decline** - shaded using the {@link Global_CSS#$alert-color $alert-color} (red by default)
* - **confirm** - shaded using the {@link Global_CSS#$confirm-color $confirm-color} (green by default)
*
* You can also append `-round` to each of the last three UI's to give it a round shape:
*
* - **action-round**
* - **decline-round**
* - **confirm-round**
*
* And setting them is very simple:
*
* var uiButton = Ext.create('Ext.Button', {
* text: 'My Button',
* ui: 'action'
* });
*
* And how they look:
*
* @example miniphone preview
* Ext.create('Ext.Container', {
* fullscreen: true,
* padding: 4,
* defaults: {
* xtype: 'button',
* margin: 5
* },
* layout: {
* type: 'vbox',
* align: 'center'
* },
* items: [
* { ui: 'normal', text: 'normal' },
* { ui: 'round', text: 'round' },
* { ui: 'action', text: 'action' },
* { ui: 'decline', text: 'decline' },
* { ui: 'confirm', text: 'confirm' }
* ]
* });
*
* Note that the default {@link #ui} is **normal**.
*
* You can also use the {@link #sencha-button-ui sencha-button-ui} CSS Mixin to create your own UIs.
*
* ## Example
*
* This example shows a bunch of icons on the screen in two toolbars. When you click on the center
* button, it switches the {@link #iconCls} on every button on the page.
*
* @example preview
* Ext.createWidget('container', {
* fullscreen: true,
* layout: {
* type: 'vbox',
* pack:'center',
* align: 'center'
* },
* items: [
* {
* xtype: 'button',
* text: 'Change iconCls',
* handler: function() {
* // classes for all the icons to loop through.
* var availableIconCls = [
* 'action', 'add', 'arrow_down', 'arrow_left',
* 'arrow_right', 'arrow_up', 'compose', 'delete',
* 'organize', 'refresh', 'reply', 'search',
* 'settings', 'star', 'trash', 'maps', 'locate',
* 'home'
* ];
* // get the text of this button,
* // so we know which button we don't want to change
* var text = this.getText();
*
* // use ComponentQuery to find all buttons on the page
* // and loop through all of them
* Ext.Array.forEach(Ext.ComponentQuery.query('button'), function(button) {
* // if the button is the change iconCls button, continue
* if (button.getText() === text) {
* return;
* }
*
* // get the index of the new available iconCls
* var index = availableIconCls.indexOf(button.getIconCls()) + 1;
*
* // update the iconCls of the button with the next iconCls, if one exists.
* // if not, use the first one
* button.setIconCls(availableIconCls[(index === availableIconCls.length) ? 0 : index]);
* });
* }
* },
* {
* xtype: 'toolbar',
* docked: 'top',
* items: [
* { xtype: 'spacer' },
* { iconCls: 'action' },
* { iconCls: 'add' },
* { iconCls: 'arrow_down' },
* { iconCls: 'arrow_left' },
* { iconCls: 'arrow_up' },
* { iconCls: 'compose' },
* { iconCls: 'delete' },
* { iconCls: 'organize' },
* { iconCls: 'refresh' },
* { xtype: 'spacer' }
* ]
* },
* {
* xtype: 'toolbar',
* docked: 'bottom',
* ui: 'light',
* items: [
* { xtype: 'spacer' },
* { iconCls: 'reply' },
* { iconCls: 'search' },
* { iconCls: 'settings' },
* { iconCls: 'star' },
* { iconCls: 'trash' },
* { iconCls: 'maps' },
* { iconCls: 'locate' },
* { iconCls: 'home' },
* { xtype: 'spacer' }
* ]
* }
* ]
* });
*
*/
Ext.define('Ext.Button', {
extend: Ext.Component ,
xtype: 'button',
/**
* @event tap
* @preventable doTap
* Fires whenever a button is tapped.
* @param {Ext.Button} this The item added to the Container.
* @param {Ext.EventObject} e The event object.
*/
/**
* @event release
* @preventable doRelease
* Fires whenever the button is released.
* @param {Ext.Button} this The item added to the Container.
* @param {Ext.EventObject} e The event object.
*/
cachedConfig: {
/**
* @cfg {String} pressedCls
* The CSS class to add to the Button when it is pressed.
* @accessor
*/
pressedCls: Ext.baseCSSPrefix + 'button-pressing',
/**
* @cfg {String} badgeCls
* The CSS class to add to the Button's badge, if it has one. Badges appear as small numbers, letters, or icons that sit on top of your button. For instance, a small red number indicating how many updates are available.
* @accessor
*/
badgeCls: Ext.baseCSSPrefix + 'badge',
/**
* @cfg {String} hasBadgeCls
* The CSS class to add to the Button if it has a badge (note that this goes on the
* Button element itself, not on the badge element).
* @private
* @accessor
*/
hasBadgeCls: Ext.baseCSSPrefix + 'hasbadge',
/**
* @cfg {String} labelCls
* The CSS class to add to the field's label element.
* @accessor
*/
labelCls: Ext.baseCSSPrefix + 'button-label',
/**
* @cfg {String} iconCls
* Optional CSS class to add to the icon element. This is useful if you want to use a CSS
* background image to create your Button icon.
* @accessor
*/
iconCls: null
},
config: {
/**
* @cfg {String} badgeText
* Optional badge text. Badges appear as small numbers, letters, or icons that sit on top of your button. For instance, a small red number indicating how many updates are available.
* @accessor
*/
badgeText: null,
/**
* @cfg {String} text
* The Button text.
* @accessor
*/
text: null,
/**
* @cfg {String} icon
* Url to the icon image to use if you want an icon to appear on your button.
* @accessor
*/
icon: false,
/**
* @cfg {String} iconAlign
* The position within the Button to render the icon Options are: `top`, `right`, `bottom`, `left` and `center` (when you have
* no {@link #text} set).
* @accessor
*/
iconAlign: 'left',
/**
* @cfg {Number/Boolean} pressedDelay
* The amount of delay between the `tapstart` and the moment we add the `pressedCls` (in milliseconds).
* Settings this to `true` defaults to 100ms.
*/
pressedDelay: 0,
/**
* @cfg {Function} handler
* The handler function to run when the Button is tapped on.
* @accessor
*/
handler: null,
/**
* @cfg {Object} scope
* The scope to fire the configured {@link #handler} in.
* @accessor
*/
scope: null,
/**
* @cfg {String} autoEvent
* Optional event name that will be fired instead of `tap` when the Button is tapped on.
* @accessor
*/
autoEvent: null,
/**
* @cfg {String} ui
* The ui style to render this button with. The valid default options are:
*
* - `'normal'` - a basic gray button (default).
* - `'back'` - a back button.
* - `'forward'` - a forward button.
* - `'round'` - a round button.
* - `'plain'`
* - `'action'` - shaded using the {@link Global_CSS#$active-color $active-color} (dark blue by default).
* - `'decline'` - shaded using the {@link Global_CSS#$alert-color $alert-color} (red by default).
* - `'confirm'` - shaded using the {@link Global_CSS#$confirm-color $confirm-color} (green by default).
*
* You can also append `-round` to each of the last three UI's to give it a round shape:
*
* - **action-round**
* - **decline-round**
* - **confirm-round**
*
* @accessor
*/
ui: 'normal',
/**
* @cfg {String} html The HTML to put in this button.
*
* If you want to just add text, please use the {@link #text} configuration.
*/
/**
* @cfg
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'button'
},
template: [
{
tag: 'span',
reference: 'badgeElement',
hidden: true
},
{
tag: 'span',
className: Ext.baseCSSPrefix + 'button-icon',
reference: 'iconElement'
},
{
tag: 'span',
reference: 'textElement',
hidden: true
}
],
initialize: function() {
this.callParent();
this.element.on({
scope : this,
tap : 'onTap',
touchstart : 'onPress',
touchend : 'onRelease'
});
},
/**
* @private
*/
updateBadgeText: function(badgeText) {
var element = this.element,
badgeElement = this.badgeElement;
if (badgeText) {
badgeElement.show();
badgeElement.setText(badgeText);
}
else {
badgeElement.hide();
}
element[(badgeText) ? 'addCls' : 'removeCls'](this.getHasBadgeCls());
},
/**
* @private
*/
updateText: function(text) {
var textElement = this.textElement;
if (textElement) {
if (text) {
textElement.show();
textElement.setHtml(text);
} else {
textElement.hide();
}
this.refreshIconAlign();
}
},
/**
* @private
*/
updateHtml: function(html) {
var textElement = this.textElement;
if (html) {
textElement.show();
textElement.setHtml(html);
}
else {
textElement.hide();
}
},
/**
* @private
*/
updateBadgeCls: function(badgeCls, oldBadgeCls) {
this.badgeElement.replaceCls(oldBadgeCls, badgeCls);
},
/**
* @private
*/
updateHasBadgeCls: function(hasBadgeCls, oldHasBadgeCls) {
var element = this.element;
if (element.hasCls(oldHasBadgeCls)) {
element.replaceCls(oldHasBadgeCls, hasBadgeCls);
}
},
/**
* @private
*/
updateLabelCls: function(labelCls, oldLabelCls) {
this.textElement.replaceCls(oldLabelCls, labelCls);
},
/**
* @private
*/
updatePressedCls: function(pressedCls, oldPressedCls) {
var element = this.element;
if (element.hasCls(oldPressedCls)) {
element.replaceCls(oldPressedCls, pressedCls);
}
},
/**
* @private
*/
updateIcon: function(icon) {
var me = this,
element = me.iconElement;
if (icon) {
me.showIconElement();
element.setStyle('background-image', 'url(' + icon + ')');
me.refreshIconAlign();
} else {
element.setStyle('background-image', '');
me.hideIconElement();
}
},
/**
* @private
*/
updateIconCls: function(iconCls, oldIconCls) {
var me = this,
element = me.iconElement;
if (iconCls) {
me.showIconElement();
element.replaceCls(oldIconCls, iconCls);
me.refreshIconAlign();
} else {
element.removeCls(oldIconCls);
me.hideIconElement();
}
},
/**
* @private
*/
updateIconAlign: function(alignment, oldAlignment) {
var element = this.element,
baseCls = Ext.baseCSSPrefix + 'iconalign-';
if (!this.getText()) {
alignment = "center";
}
element.removeCls(baseCls + "center");
element.removeCls(baseCls + oldAlignment);
if (this.getIcon() || this.getIconCls()) {
element.addCls(baseCls + alignment);
}
},
refreshIconAlign: function() {
this.updateIconAlign(this.getIconAlign());
},
applyAutoEvent: function(autoEvent) {
var me = this;
if (typeof autoEvent == 'string') {
autoEvent = {
name : autoEvent,
scope: me.scope || me
};
}
return autoEvent;
},
/**
* @private
*/
updateAutoEvent: function(autoEvent) {
var name = autoEvent.name,
scope = autoEvent.scope;
this.setHandler(function() {
scope.fireEvent(name, scope, this);
});
this.setScope(scope);
},
/**
* Used by `icon` and `iconCls` configurations to hide the icon element.
* @private
*/
hideIconElement: function() {
this.iconElement.removeCls(Ext.baseCSSPrefix + 'shown');
this.iconElement.addCls(Ext.baseCSSPrefix + 'hidden');
},
/**
* Used by `icon` and `iconCls` configurations to show the icon element.
* @private
*/
showIconElement: function() {
this.iconElement.removeCls(Ext.baseCSSPrefix + 'hidden');
this.iconElement.addCls(Ext.baseCSSPrefix + 'shown');
},
/**
* We override this to check for '{ui}-back'. This is because if you have a UI of back, you need to actually add two class names.
* The ui class, and the back class:
*
* `ui: 'action-back'` would turn into:
*
* `class="x-button-action x-button-back"`
*
* But `ui: 'action'` would turn into:
*
* `class="x-button-action"`
*
* So we just split it up into an array and add both of them as a UI, when it has `back`.
* @private
*/
applyUi: function(config) {
if (config && Ext.isString(config)) {
var array = config.split('-');
if (array && (array[1] == "back" || array[1] == "forward")) {
return array;
}
}
return config;
},
getUi: function() {
//Now that the UI can sometimes be an array, we need to check if it an array and return the proper value.
var ui = this._ui;
if (Ext.isArray(ui)) {
return ui.join('-');
}
return ui;
},
applyPressedDelay: function(delay) {
if (Ext.isNumber(delay)) {
return delay;
}
return (delay) ? 100 : 0;
},
// @private
onPress: function() {
var me = this,
element = me.element,
pressedDelay = me.getPressedDelay(),
pressedCls = me.getPressedCls();
if (!me.getDisabled()) {
if (pressedDelay > 0) {
me.pressedTimeout = setTimeout(function() {
delete me.pressedTimeout;
if (element) {
element.addCls(pressedCls);
}
}, pressedDelay);
}
else {
element.addCls(pressedCls);
}
}
},
// @private
onRelease: function(e) {
this.fireAction('release', [this, e], 'doRelease');
},
// @private
doRelease: function(me, e) {
if (!me.getDisabled()) {
if (me.hasOwnProperty('pressedTimeout')) {
clearTimeout(me.pressedTimeout);
delete me.pressedTimeout;
}
else {
me.element.removeCls(me.getPressedCls());
}
}
},
// @private
onTap: function(e) {
if (this.getDisabled()) {
return false;
}
this.fireAction('tap', [this, e], 'doTap');
},
/**
* @private
*/
doTap: function(me, e) {
var handler = me.getHandler(),
scope = me.getScope() || me;
if (!handler) {
return;
}
if (typeof handler == 'string') {
handler = scope[handler];
}
//this is done so if you hide the button in the handler, the tap event will not fire on the new element
//where the button was.
if (e && e.preventDefault) {
e.preventDefault();
}
handler.apply(scope, arguments);
}
}, function() {
});
/**
* A general sheet class. This renderable container provides base support for orientation-aware transitions for popup or
* side-anchored sliding Panels.
*
* In most cases, you should use {@link Ext.ActionSheet}, {@link Ext.MessageBox}, {@link Ext.picker.Picker}, or {@link Ext.picker.Date}.
*/
Ext.define('Ext.Sheet', {
extend: Ext.Panel ,
xtype: 'sheet',
config: {
/**
* @cfg
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'sheet',
/**
* @cfg
* @inheritdoc
*/
modal: true,
/**
* @cfg {Boolean} centered
* Whether or not this component is absolutely centered inside its container.
* @accessor
* @evented
*/
centered: true,
/**
* @cfg {Boolean} stretchX `true` to stretch this sheet horizontally.
*/
stretchX: null,
/**
* @cfg {Boolean} stretchY `true` to stretch this sheet vertically.
*/
stretchY: null,
/**
* @cfg {String} enter
* The viewport side used as the enter point when shown. Valid values are 'top', 'bottom', 'left', and 'right'.
* Applies to sliding animation effects only.
*/
enter: 'bottom',
/**
* @cfg {String} exit
* The viewport side used as the exit point when hidden. Valid values are 'top', 'bottom', 'left', and 'right'.
* Applies to sliding animation effects only.
*/
exit: 'bottom',
/**
* @cfg
* @inheritdoc
*/
showAnimation: !Ext.browser.is.AndroidStock2 ? {
type: 'slideIn',
duration: 250,
easing: 'ease-out'
} : null,
/**
* @cfg
* @inheritdoc
*/
hideAnimation: !Ext.browser.is.AndroidStock2 ? {
type: 'slideOut',
duration: 250,
easing: 'ease-in'
} : null
},
isInputRegex: /^(input|textarea|select|a)$/i,
beforeInitialize: function() {
var me = this;
// Temporary fix for a mysterious bug on iOS where double tapping on a sheet
// being animated from the bottom shift the whole body up
Ext.os.is.iOS && this.element.dom.addEventListener('touchstart', function(e) {
if (!me.isInputRegex.test(e.target.tagName)) {
e.preventDefault();
}
}, true);
},
platformConfig: [{
theme: ['Windows'],
enter: 'top',
exit: 'top'
}],
applyHideAnimation: function(config) {
var exit = this.getExit(),
direction = exit;
if (exit === null) {
return null;
}
if (config === true) {
config = {
type: 'slideOut'
};
}
if (Ext.isString(config)) {
config = {
type: config
};
}
var anim = Ext.factory(config, Ext.fx.Animation);
if (anim) {
if (exit == 'bottom') {
direction = 'down';
}
if (exit == 'top') {
direction = 'up';
}
anim.setDirection(direction);
}
return anim;
},
applyShowAnimation: function(config) {
var enter = this.getEnter(),
direction = enter;
if (enter === null) {
return null;
}
if (config === true) {
config = {
type: 'slideIn'
};
}
if (Ext.isString(config)) {
config = {
type: config
};
}
var anim = Ext.factory(config, Ext.fx.Animation);
if (anim) {
if (enter == 'bottom') {
direction = 'down';
}
if (enter == 'top') {
direction = 'up';
}
anim.setBefore({
display: null
});
anim.setReverse(true);
anim.setDirection(direction);
}
return anim;
},
updateStretchX: function(newStretchX) {
this.getLeft();
this.getRight();
if (newStretchX) {
this.setLeft(0);
this.setRight(0);
}
},
updateStretchY: function(newStretchY) {
this.getTop();
this.getBottom();
if (newStretchY) {
this.setTop(0);
this.setBottom(0);
}
}
});
/**
* {@link Ext.ActionSheet ActionSheets} are used to display a list of {@link Ext.Button buttons} in a popup dialog.
*
* The key difference between ActionSheet and {@link Ext.Sheet} is that ActionSheets are docked at the bottom of the
* screen, and the {@link #defaultType} is set to {@link Ext.Button button}.
*
* ## Example
*
* @example preview miniphone
* var actionSheet = Ext.create('Ext.ActionSheet', {
* items: [
* {
* text: 'Delete draft',
* ui : 'decline'
* },
* {
* text: 'Save draft'
* },
* {
* text: 'Cancel',
* ui : 'confirm'
* }
* ]
* });
*
* Ext.Viewport.add(actionSheet);
* actionSheet.show();
*
* As you can see from the code above, you no longer have to specify a `xtype` when creating buttons within a {@link Ext.ActionSheet ActionSheet},
* because the {@link #defaultType} is set to {@link Ext.Button button}.
*
*/
Ext.define('Ext.ActionSheet', {
extend: Ext.Sheet ,
alias : 'widget.actionsheet',
config: {
/**
* @cfg
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'sheet-action',
/**
* @cfg
* @inheritdoc
*/
left: 0,
/**
* @cfg
* @inheritdoc
*/
right: 0,
/**
* @cfg
* @inheritdoc
*/
bottom: 0,
// @hide
centered: false,
/**
* @cfg
* @inheritdoc
*/
height: 'auto',
/**
* @cfg
* @inheritdoc
*/
defaultType: 'button'
},
platformConfig: [{
theme: ['Windows'],
top: 0,
bottom: null
}]
});
/**
* The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
* to a configured URL, or to a URL specified at request time.
*
* Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
* to the statement immediately following the {@link #request} call. To process returned data, use a success callback
* in the request options object, or an {@link #requestcomplete event listener}.
*
* # File Uploads
*
* File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
* Instead the form is submitted in the standard manner with the DOM `