3.0 source code

This commit is contained in:
agolybev
2015-04-28 17:59:00 +03:00
parent c69fd34bdd
commit 7b3b2248e5
16311 changed files with 1445974 additions and 3108429 deletions

View File

@@ -0,0 +1,739 @@
/**
* @private
*/
Ext.define('Ext.util.AbstractMixedCollection', {
requires: ['Ext.util.Filter'],
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} o 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} o (optional) 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} allowBlank (optional) 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} [startIndex=0] (optional) The starting index.
* @param {Number} [endIndex=-1] (optional) 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;
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,139 @@
/**
* The DelayedTask class provides a convenient way to "buffer" the execution of a method,
* performing `setTimeout` where a new timeout cancels the old timeout. When called, the
* task will wait the specified time period before executing. If during that time period,
* the task is called again, the original call will be canceled. This continues so that
* the function is only called a single time for each iteration.
*
* This method is especially useful for things like detecting whether a user has finished
* typing in a text field. An example would be performing validation on a keypress. You can
* use this class to buffer the keypress events for a certain number of milliseconds, and
* perform only if they stop for that amount of time.
*
* Using {@link Ext.util.DelayedTask} is very simple:
*
* //create the delayed task instance with our callback
* var task = Ext.create('Ext.util.DelayedTask', function() {
* console.log('callback!');
* });
*
* task.delay(1500); //the callback function will now be called after 1500ms
*
* task.cancel(); //the callback function will never be called now, unless we call delay() again
*
* ## Example
*
* @example
* //create a textfield where we can listen to text
* var field = Ext.create('Ext.field.Text', {
* xtype: 'textfield',
* label: 'Length: 0'
* });
*
* //add the textfield into a fieldset
* Ext.Viewport.add({
* xtype: 'formpanel',
* items: [{
* xtype: 'fieldset',
* items: [field],
* instructions: 'Type into the field and watch the count go up after 500ms.'
* }]
* });
*
* //create our delayed task with a function that returns the fields length as the fields label
* var task = Ext.create('Ext.util.DelayedTask', function() {
* field.setLabel('Length: ' + field.getValue().length);
* });
*
* // Wait 500ms before calling our function. If the user presses another key
* // during that 500ms, it will be canceled and we'll wait another 500ms.
* field.on('keyup', function() {
* task.delay(500);
* });
*
* @constructor
* The parameters to this constructor serve as defaults and are not required.
* @param {Function} fn The default function to call.
* @param {Object} scope The default scope (The `this` reference) in which the function is called. If
* not specified, `this` will refer to the browser window.
* @param {Array} args The default Array of arguments.
*/
Ext.define('Ext.util.DelayedTask', {
config: {
interval: null,
delay: null,
fn: null,
scope: null,
args: null
},
constructor: function(fn, scope, args) {
var config = {
fn: fn,
scope: scope,
args: args
};
this.initConfig(config);
},
/**
* Cancels any pending timeout and queues a new one.
* @param {Number} delay The milliseconds to delay
* @param {Function} newFn Overrides the original function passed when instantiated.
* @param {Object} newScope Overrides the original `scope` passed when instantiated. Remember that if no scope
* is specified, `this` will refer to the browser window.
* @param {Array} newArgs Overrides the original `args` passed when instantiated.
*/
delay: function(delay, newFn, newScope, newArgs) {
var me = this;
//cancel any existing queued functions
me.cancel();
//set all the new configurations
me.setConfig({
delay: delay,
fn: newFn,
scope: newScope,
args: newArgs
});
//create the callback method for this delayed task
var call = function() {
me.getFn().apply(me.getScope(), me.getArgs() || []);
me.cancel();
};
me.setInterval(setInterval(call, me.getDelay()));
},
/**
* Cancel the last queued timeout
*/
cancel: function() {
this.setInterval(null);
},
/**
* @private
* Clears the old interval
*/
updateInterval: function(newInterval, oldInterval) {
if (oldInterval) {
clearInterval(oldInterval);
}
},
/**
* @private
* Changes the value into an array if it isn't one.
*/
applyArgs: function(config) {
if (!Ext.isArray(config)) {
config = [config];
}
return config;
}
});

View File

@@ -0,0 +1,415 @@
/**
* A core util class to bring Draggable behavior to any DOM element
*/
Ext.define('Ext.util.Draggable', {
isDraggable: true,
mixins: [
'Ext.mixin.Observable'
],
requires: [
'Ext.util.Translatable'
],
/**
* @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',
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);
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);
},
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() {
//<deprecated product=touch since=2.0>
this.override({
constructor: function(config) {
if (config && config.constrain) {
//<debug warn>
Ext.Logger.deprecate("'constrain' config is deprecated, please use 'contraint' instead");
//</debug>
config.contraint = config.constrain;
delete config.constrain;
}
return this.callOverridden(arguments);
}
});
//</deprecated>
});

View File

@@ -0,0 +1,247 @@
/**
*
*/
Ext.define('Ext.util.Droppable', {
mixins: {
observable: 'Ext.mixin.Observable'
},
config: {
/**
* @cfg
* @inheritdoc
*/
baseCls: Ext.baseCSSPrefix + 'droppable'
},
/**
* @cfg {String} activeCls
* The CSS added to a Droppable when a Draggable in the same group is being
* dragged.
*/
activeCls: Ext.baseCSSPrefix + 'drop-active',
/**
* @cfg {String} invalidCls
* The CSS class to add to the droppable when dragging a draggable that is
* not in the same group.
*/
invalidCls: Ext.baseCSSPrefix + 'drop-invalid',
/**
* @cfg {String} hoverCls
* The CSS class to add to the droppable when hovering over a valid drop.
*/
hoverCls: Ext.baseCSSPrefix + 'drop-hover',
/**
* @cfg {String} validDropMode
* Determines when a drop is considered 'valid' whether it simply need to
* intersect the region or if it needs to be contained within the region.
* Valid values are: 'intersects' or 'contains'
*/
validDropMode: 'intersect',
/**
* @cfg {Boolean} disabled
*/
disabled: false,
/**
* @cfg {String} group
* Draggable and Droppable objects can participate in a group which are
* capable of interacting.
*/
group: 'base',
// not yet implemented
tolerance: null,
// @private
monitoring: false,
/**
* Creates new Droppable.
* @param {Mixed} el String, HtmlElement or Ext.Element representing an
* element on the page.
* @param {Object} config Configuration options for this class.
*/
constructor: function(el, config) {
var me = this;
config = config || {};
Ext.apply(me, config);
/**
* @event dropactivate
* @param {Ext.util.Droppable} this
* @param {Ext.util.Draggable} draggable
* @param {Ext.event.Event} e
*/
/**
* @event dropdeactivate
* @param {Ext.util.Droppable} this
* @param {Ext.util.Draggable} draggable
* @param {Ext.event.Event} e
*/
/**
* @event dropenter
* @param {Ext.util.Droppable} this
* @param {Ext.util.Draggable} draggable
* @param {Ext.event.Event} e
*/
/**
* @event dropleave
* @param {Ext.util.Droppable} this
* @param {Ext.util.Draggable} draggable
* @param {Ext.event.Event} e
*/
/**
* @event drop
* @param {Ext.util.Droppable} this
* @param {Ext.util.Draggable} draggable
* @param {Ext.event.Event} e
*/
me.el = Ext.get(el);
me.callParent();
me.mixins.observable.constructor.call(me);
if (!me.disabled) {
me.enable();
}
me.el.addCls(me.baseCls);
},
// @private
onDragStart: function(draggable, e) {
if (draggable.group === this.group) {
this.monitoring = true;
this.el.addCls(this.activeCls);
this.region = this.el.getPageBox(true);
draggable.on({
drag: this.onDrag,
beforedragend: this.onBeforeDragEnd,
dragend: this.onDragEnd,
scope: this
});
if (this.isDragOver(draggable)) {
this.setCanDrop(true, draggable, e);
}
this.fireEvent('dropactivate', this, draggable, e);
}
else {
draggable.on({
dragend: function() {
this.el.removeCls(this.invalidCls);
},
scope: this,
single: true
});
this.el.addCls(this.invalidCls);
}
},
// @private
isDragOver: function(draggable, region) {
return this.region[this.validDropMode](draggable.region);
},
// @private
onDrag: function(draggable, e) {
this.setCanDrop(this.isDragOver(draggable), draggable, e);
},
// @private
setCanDrop: function(canDrop, draggable, e) {
if (canDrop && !this.canDrop) {
this.canDrop = true;
this.el.addCls(this.hoverCls);
this.fireEvent('dropenter', this, draggable, e);
}
else if (!canDrop && this.canDrop) {
this.canDrop = false;
this.el.removeCls(this.hoverCls);
this.fireEvent('dropleave', this, draggable, e);
}
},
// @private
onBeforeDragEnd: function(draggable, e) {
draggable.cancelRevert = this.canDrop;
},
// @private
onDragEnd: function(draggable, e) {
this.monitoring = false;
this.el.removeCls(this.activeCls);
draggable.un({
drag: this.onDrag,
beforedragend: this.onBeforeDragEnd,
dragend: this.onDragEnd,
scope: this
});
if (this.canDrop) {
this.canDrop = false;
this.el.removeCls(this.hoverCls);
this.fireEvent('drop', this, draggable, e);
}
this.fireEvent('dropdeactivate', this, draggable, e);
},
/**
* Enable the Droppable target.
* This is invoked immediately after constructing a Droppable if the
* disabled parameter is NOT set to true.
*/
enable: function() {
if (!this.mgr) {
this.mgr = Ext.util.Observable.observe(Ext.util.Draggable);
}
this.mgr.on({
dragstart: this.onDragStart,
scope: this
});
this.disabled = false;
},
/**
* Disable the Droppable target.
*/
disable: function() {
this.mgr.un({
dragstart: this.onDragStart,
scope: this
});
this.disabled = true;
},
/**
* Method to determine whether this Component is currently disabled.
* @return {Boolean} the disabled state of this Component.
*/
isDisabled: function() {
return this.disabled;
},
/**
* Method to determine whether this Droppable is currently monitoring drag operations of Draggables.
* @return {Boolean} the monitoring state of this Droppable
*/
isMonitoring: function() {
return this.monitoring;
}
});

View File

@@ -0,0 +1,188 @@
/**
* 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.
*/
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) {
// <debug>
Ext.Logger.error('A Filter requires either a property and value, or a filterFn to be set');
// </debug>
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;
}
});

View File

@@ -0,0 +1,192 @@
/**
* Reusable data formatting functions
*/
Ext.define('Ext.util.Format', {
requires: [
'Ext.DateExtras'
],
singleton: true,
/**
* The global default date format.
*/
defaultDateFormat: 'm/d/Y',
escapeRe: /('|\\)/g,
trimRe: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
formatRe: /\{(\d+)\}/g,
escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
dashesRe: /-/g,
iso8601TestRe: /\d\dT\d\d/,
iso8601SplitRe: /[- :T\.Z\+]/,
/**
* Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
* @param {String} value The string to truncate.
* @param {Number} length The maximum length to allow before truncating.
* @param {Boolean} word True to try to find a common word break.
* @return {String} The converted text.
*/
ellipsis: function(value, len, word) {
if (value && value.length > len) {
if (word) {
var vs = value.substr(0, len - 2),
index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
if (index != -1 && index >= (len - 15)) {
return vs.substr(0, index) + "...";
}
}
return value.substr(0, len - 3) + "...";
}
return value;
},
/**
* Escapes the passed string for use in a regular expression.
* @param {String} str
* @return {String}
*/
escapeRegex: function(s) {
return s.replace(Ext.util.Format.escapeRegexRe, "\\$1");
},
/**
* Escapes the passed string for ' and \.
* @param {String} string The string to escape.
* @return {String} The escaped string.
*/
escape: function(string) {
return string.replace(Ext.util.Format.escapeRe, "\\$1");
},
/**
* Utility function that allows you to easily switch a string between two alternating values. The passed value
* is compared to the current string, and if they are equal, the other value that was passed in is returned. If
* they are already different, the first value passed in is returned.
*
* __Note:__ This method returns the new value but does not change the current string.
*
* // alternate sort directions
* sort = Ext.util.Format.toggle(sort, 'ASC', 'DESC');
*
* // instead of conditional logic:
* sort = (sort === 'ASC' ? 'DESC' : 'ASC');
*
* @param {String} string The current string
* @param {String} value The value to compare to the current string
* @param {String} other The new value to use if the string already equals the first value passed in
* @return {String} The new value
*/
toggle: function(string, value, other) {
return string == value ? other : value;
},
/**
* Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
*
* var s = ' foo bar ';
* alert('-' + s + '-'); // alerts "- foo bar -"
* alert('-' + Ext.util.Format.trim(s) + '-'); // alerts "-foo bar-"
*
* @param {String} string The string to escape
* @return {String} The trimmed string
*/
trim: function(string) {
return string.replace(Ext.util.Format.trimRe, "");
},
/**
* Pads the left side of a string with a specified character. This is especially useful
* for normalizing number and date strings. Example usage:
*
* var s = Ext.util.Format.leftPad('123', 5, '0');
* // s now contains the string: '00123'
*
* @param {String} string The original string.
* @param {Number} size The total length of the output string.
* @param {String} [char=' '] (optional) The character with which to pad the original string.
* @return {String} The padded string.
*/
leftPad: function (val, size, ch) {
var result = String(val);
ch = ch || " ";
while (result.length < size) {
result = ch + result;
}
return result;
},
/**
* Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
* token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
*
* var cls = 'my-class', text = 'Some text';
* var s = Ext.util.Format.format('<div class="{0}">{1}</div>', cls, text);
* // s now contains the string: '<div class="my-class">Some text</div>'
*
* @param {String} string The tokenized string to be formatted.
* @param {String...} values The values to replace token {0}, {1}, etc.
* @return {String} The formatted string.
*/
format: function (format) {
var args = Ext.toArray(arguments, 1);
return format.replace(Ext.util.Format.formatRe, function(m, i) {
return args[i];
});
},
/**
* Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
* @param {String} value The string to encode.
* @return {String} The encoded text.
*/
htmlEncode: function(value) {
return ! value ? value: String(value).replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
},
/**
* Convert certain characters (&, <, >, and ') from their HTML character equivalents.
* @param {String} value The string to decode.
* @return {String} The decoded text.
*/
htmlDecode: function(value) {
return ! value ? value: String(value).replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
},
/**
* Parse a value into a formatted date using the specified format pattern.
* @param {String/Date} value The value to format. Strings must conform to the format expected by the JavaScript
* Date object's [parse() method](http://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse).
* @param {String} [format='m/d/Y'] (optional) Any valid date format string.
* @return {String} The formatted date string.
*/
date: function(value, format) {
var date = value;
if (!value) {
return "";
}
if (!Ext.isDate(value)) {
date = new Date(Date.parse(value));
if (isNaN(date)) {
// Dates with ISO 8601 format are not well supported by mobile devices, this can work around the issue.
if (this.iso8601TestRe.test(value)) {
date = value.split(this.iso8601SplitRe);
date = new Date(date[0], date[1]-1, date[2], date[3], date[4], date[5]);
}
if (isNaN(date)) {
// Dates with the format "2012-01-20" fail, but "2012/01/20" work in some browsers. We'll try and
// get around that.
date = new Date(Date.parse(value.replace(this.dashesRe, "/")));
//<debug>
if (isNaN(date)) {
Ext.Logger.error("Cannot parse the passed value " + value + " into a valid date");
}
//</debug>
}
}
value = date;
}
return Ext.Date.format(value, format || Ext.util.Format.defaultDateFormat);
}
});

View File

@@ -0,0 +1,414 @@
/**
* Provides a cross browser class for retrieving location information.
*
* Based on the [Geolocation API Specification](http://dev.w3.org/geo/api/spec-source.html)
*
* When instantiated, by default this class immediately begins tracking location information,
* firing a {@link #locationupdate} event when new location information is available. To disable this
* location tracking (which may be battery intensive on mobile devices), set {@link #autoUpdate} to `false`.
*
* When this is done, only calls to {@link #updateLocation} will trigger a location retrieval.
*
* A {@link #locationerror} event is raised when an error occurs retrieving the location, either due to a user
* denying the application access to it, or the browser not supporting it.
*
* The below code shows a GeoLocation making a single retrieval of location information.
*
* var geo = Ext.create('Ext.util.Geolocation', {
* autoUpdate: false,
* listeners: {
* locationupdate: function(geo) {
* alert('New latitude: ' + geo.getLatitude());
* },
* locationerror: function(geo, bTimeout, bPermissionDenied, bLocationUnavailable, message) {
* if(bTimeout){
* alert('Timeout occurred.');
* } else {
* alert('Error occurred.');
* }
* }
* }
* });
* geo.updateLocation();
*/
Ext.define('Ext.util.Geolocation', {
extend: 'Ext.Evented',
alternateClassName: ['Ext.util.GeoLocation'],
config: {
/**
* @event locationerror
* Raised when a location retrieval operation failed.
*
* In the case of calling updateLocation, this event will be raised only once.
*
* If {@link #autoUpdate} is set to `true`, this event could be raised repeatedly.
* The first error is relative to the moment {@link #autoUpdate} was set to `true`
* (or this {@link Ext.util.Geolocation} was initialized with the {@link #autoUpdate} config option set to `true`).
* Subsequent errors are relative to the moment when the device determines that it's position has changed.
* @param {Ext.util.Geolocation} this
* @param {Boolean} timeout
* Boolean indicating a timeout occurred
* @param {Boolean} permissionDenied
* Boolean indicating the user denied the location request
* @param {Boolean} locationUnavailable
* Boolean indicating that the location of the device could not be determined.
* For instance, one or more of the location providers used in the location acquisition
* process reported an internal error that caused the process to fail entirely.
* @param {String} message An error message describing the details of the error encountered.
*
* This attribute is primarily intended for debugging and should not be used
* directly in an application user interface.
*/
/**
* @event locationupdate
* Raised when a location retrieval operation has been completed successfully.
* @param {Ext.util.Geolocation} this
* Retrieve the current location information from the GeoLocation object by using the read-only
* properties: {@link #latitude}, {@link #longitude}, {@link #accuracy}, {@link #altitude}, {@link #altitudeAccuracy}, {@link #heading}, and {@link #speed}.
*/
/**
* @cfg {Boolean} autoUpdate
* When set to `true`, continually monitor the location of the device (beginning immediately)
* and fire {@link #locationupdate} and {@link #locationerror} events.
*/
autoUpdate: true,
/**
* @cfg {Number} frequency
* The frequency of each update if {@link #autoUpdate} is set to `true`.
*/
frequency: 10000,
/**
* Read-only property representing the last retrieved
* geographical coordinate specified in degrees.
* @type Number
* @readonly
*/
latitude: null,
/**
* Read-only property representing the last retrieved
* geographical coordinate specified in degrees.
* @type Number
* @readonly
*/
longitude: null,
/**
* Read-only property representing the last retrieved
* accuracy level of the latitude and longitude coordinates,
* specified in meters.
*
* This will always be a non-negative number.
*
* This corresponds to a 95% confidence level.
* @type Number
* @readonly
*/
accuracy: null,
/**
* Read-only property representing the last retrieved
* height of the position, specified in meters above the ellipsoid
* [WGS84](http://dev.w3.org/geo/api/spec-source.html#ref-wgs).
* @type Number
* @readonly
*/
altitude: null,
/**
* Read-only property representing the last retrieved
* accuracy level of the altitude coordinate, specified in meters.
*
* If altitude is not null then this will be a non-negative number.
* Otherwise this returns `null`.
*
* This corresponds to a 95% confidence level.
* @type Number
* @readonly
*/
altitudeAccuracy: null,
/**
* Read-only property representing the last retrieved
* direction of travel of the hosting device,
* specified in non-negative degrees between 0 and 359,
* counting clockwise relative to the true north.
*
* If speed is 0 (device is stationary), then this returns `NaN`.
* @type Number
* @readonly
*/
heading: null,
/**
* Read-only property representing the last retrieved
* current ground speed of the device, specified in meters per second.
*
* If this feature is unsupported by the device, this returns `null`.
*
* If the device is stationary, this returns 0,
* otherwise it returns a non-negative number.
* @type Number
* @readonly
*/
speed: null,
/**
* Read-only property representing when the last retrieved
* positioning information was acquired by the device.
* @type Date
* @readonly
*/
timestamp: null,
//PositionOptions interface
/**
* @cfg {Boolean} allowHighAccuracy
* When set to `true`, provide a hint that the application would like to receive
* the best possible results. This may result in slower response times or increased power consumption.
* The user might also deny this capability, or the device might not be able to provide more accurate
* results than if this option was set to `false`.
*/
allowHighAccuracy: false,
/**
* @cfg {Number} timeout
* The maximum number of milliseconds allowed to elapse between a location update operation
* and the corresponding {@link #locationupdate} event being raised. If a location was not successfully
* acquired before the given timeout elapses (and no other internal errors have occurred in this interval),
* then a {@link #locationerror} event will be raised indicating a timeout as the cause.
*
* Note that the time that is spent obtaining the user permission is **not** included in the period
* covered by the timeout. The `timeout` attribute only applies to the location acquisition operation.
*
* In the case of calling `updateLocation`, the {@link #locationerror} event will be raised only once.
*
* If {@link #autoUpdate} is set to `true`, the {@link #locationerror} event could be raised repeatedly.
* The first timeout is relative to the moment {@link #autoUpdate} was set to `true`
* (or this {@link Ext.util.Geolocation} was initialized with the {@link #autoUpdate} config option set to `true`).
* Subsequent timeouts are relative to the moment when the device determines that it's position has changed.
*/
timeout: Infinity,
/**
* @cfg {Number} maximumAge
* This option indicates that the application is willing to accept cached location information whose age
* is no greater than the specified time in milliseconds. If `maximumAge` is set to 0, an attempt to retrieve
* new location information is made immediately.
*
* Setting the `maximumAge` to Infinity returns a cached position regardless of its age.
*
* If the device does not have cached location information available whose age is no
* greater than the specified `maximumAge`, then it must acquire new location information.
*
* For example, if location information no older than 10 minutes is required, set this property to 600000.
*/
maximumAge: 0,
// @private
provider : undefined
},
updateMaximumAge: function() {
if (this.watchOperation) {
this.updateWatchOperation();
}
},
updateTimeout: function() {
if (this.watchOperation) {
this.updateWatchOperation();
}
},
updateAllowHighAccuracy: function() {
if (this.watchOperation) {
this.updateWatchOperation();
}
},
applyProvider: function(config) {
if (Ext.feature.has.Geolocation) {
if (!config) {
if (navigator && navigator.geolocation) {
config = navigator.geolocation;
}
else if (window.google) {
config = google.gears.factory.create('beta.geolocation');
}
}
}
else {
this.fireEvent('locationerror', this, false, false, true, 'This device does not support Geolocation.');
}
return config;
},
updateAutoUpdate: function(newAutoUpdate, oldAutoUpdate) {
var me = this,
provider = me.getProvider();
if (oldAutoUpdate && provider) {
clearInterval(me.watchOperationId);
me.watchOperationId = null;
}
if (newAutoUpdate) {
if (!provider) {
me.fireEvent('locationerror', me, false, false, true, null);
return;
}
try {
me.updateWatchOperation();
}
catch(e) {
me.fireEvent('locationerror', me, false, false, true, e.message);
}
}
},
// @private
updateWatchOperation: function() {
var me = this,
provider = me.getProvider();
// The native watchPosition method is currently broken in iOS5...
if (me.watchOperationId) {
clearInterval(me.watchOperationId);
}
function pollPosition() {
provider.getCurrentPosition(
Ext.bind(me.fireUpdate, me),
Ext.bind(me.fireError, me),
me.parseOptions()
);
}
pollPosition();
me.watchOperationId = setInterval(pollPosition, this.getFrequency());
},
/**
* Executes a onetime location update operation,
* raising either a {@link #locationupdate} or {@link #locationerror} event.
*
* Does not interfere with or restart ongoing location monitoring.
* @param {Function} callback
* A callback method to be called when the location retrieval has been completed.
*
* Will be called on both success and failure.
*
* The method will be passed one parameter, {@link Ext.util.Geolocation} (**this** reference),
* set to `null` on failure.
*
* geo.updateLocation(function (geo) {
* alert('Latitude: ' + (geo !== null ? geo.latitude : 'failed'));
* });
*
* @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed.
*
* **If omitted, defaults to the object which fired the event.**
* <!--positonOptions undocumented param, see W3C spec-->
*/
updateLocation: function(callback, scope, positionOptions) {
var me = this,
provider = me.getProvider();
var failFunction = function(message, error) {
if (error) {
me.fireError(error);
}
else {
me.fireEvent('locationerror', me, false, false, true, message);
}
if (callback) {
callback.call(scope || me, null, me); //last parameter for legacy purposes
}
};
if (!provider) {
failFunction(null);
return;
}
try {
provider.getCurrentPosition(
//success callback
function(position) {
me.fireUpdate(position);
if (callback) {
callback.call(scope || me, me, me); //last parameter for legacy purposes
}
},
//error callback
function(error) {
failFunction(null, error);
},
positionOptions || me.parseOptions()
);
}
catch(e) {
failFunction(e.message);
}
},
// @private
fireUpdate: function(position) {
var me = this,
coords = position.coords;
this.position = position;
me.setConfig({
timestamp: position.timestamp,
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
altitude: coords.altitude,
altitudeAccuracy: coords.altitudeAccuracy,
heading: coords.heading,
speed: coords.speed
});
me.fireEvent('locationupdate', me);
},
// @private
fireError: function(error) {
var errorCode = error.code;
this.fireEvent('locationerror', this,
errorCode == error.TIMEOUT,
errorCode == error.PERMISSION_DENIED,
errorCode == error.POSITION_UNAVAILABLE,
error.message == undefined ? null : error.message
);
},
// @private
parseOptions: function() {
var timeout = this.getTimeout(),
ret = {
maximumAge: this.getMaximumAge(),
enableHighAccuracy: this.getAllowHighAccuracy()
};
//Google doesn't like Infinity
if (timeout !== Infinity) {
ret.timeout = timeout;
}
return ret;
},
destroy : function() {
this.setAutoUpdate(false);
}
});

View File

@@ -0,0 +1,98 @@
/**
* @private
*/
Ext.define('Ext.util.Grouper', {
/* Begin Definitions */
extend: 'Ext.util.Sorter',
isGrouper: true,
config: {
/**
* @cfg {Function} groupFn This function will be called for each item in the collection to
* determine the group to which it belongs.
* @cfg {Object} groupFn.item The current item from the collection
* @cfg {String} groupFn.return The group identifier for the item
*/
groupFn: null,
/**
* @cfg {String} sortProperty You can define this configuration if you want the groups to be sorted
* on something other then the group string returned by the `groupFn`.
*/
sortProperty: null,
/**
* @cfg {Function} sorterFn
* Grouper has a custom sorterFn that cannot be overridden by the user. If a property has been defined
* on this grouper, we use the default `sorterFn`, else we sort based on the returned group string.
*/
sorterFn: function(item1, item2) {
var property = this.getSortProperty(),
groupFn, group1, group2, modifier;
groupFn = this.getGroupFn();
group1 = groupFn.call(this, item1);
group2 = groupFn.call(this, item2);
if (property) {
if (group1 !== group2) {
return this.defaultSortFn.call(this, item1, item2);
} else {
return 0;
}
}
return (group1 > group2) ? 1 : ((group1 < group2) ? -1 : 0);
}
},
/**
* @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._sortProperty;
if (root !== null) {
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);
},
updateProperty: function(property) {
this.setGroupFn(this.standardGroupFn);
},
standardGroupFn: function(item) {
var root = this.getRoot(),
property = this.getProperty(),
data = item;
if (root) {
data = item[root];
}
return data[property];
},
getGroupString: function(item) {
var group = this.getGroupFn().call(this, item);
return typeof group != 'undefined' ? group.toString() : '';
}
});

View File

@@ -0,0 +1,320 @@
/**
* Represents a collection of a set of key and value pairs. Each key in the HashMap must be unique, the same
* key cannot exist twice. Access to items is provided via the key only. Sample usage:
*
* var map = Ext.create('Ext.util.HashMap');
* map.add('key1', 1);
* map.add('key2', 2);
* map.add('key3', 3);
*
* map.each(function(key, value, length){
* console.log(key, value, length);
* });
*
* The HashMap is an unordered class, there is no guarantee when iterating over the items that they will be in
* any particular order. If this is required, then use a {@link Ext.util.MixedCollection}.
*/
Ext.define('Ext.util.HashMap', {
mixins: {
observable: 'Ext.mixin.Observable'
},
/**
* @cfg {Function} keyFn
* A function that is used to retrieve a default key for a passed object.
* A default is provided that returns the **id** property on the object.
* This function is only used if the add method is called with a single argument.
*/
/**
* Creates new HashMap.
* @param {Object} config The configuration options
*/
constructor: function(config) {
/**
* @event add
* Fires when a new item is added to the hash.
* @param {Ext.util.HashMap} this
* @param {String} key The key of the added item.
* @param {Object} value The value of the added item.
*/
/**
* @event clear
* Fires when the hash is cleared.
* @param {Ext.util.HashMap} this
*/
/**
* @event remove
* Fires when an item is removed from the hash.
* @param {Ext.util.HashMap} this
* @param {String} key The key of the removed item.
* @param {Object} value The value of the removed item.
*/
/**
* @event replace
* Fires when an item is replaced in the hash.
* @param {Ext.util.HashMap} this
* @param {String} key The key of the replaced item.
* @param {Object} value The new value for the item.
* @param {Object} old The old value for the item.
*/
this.callParent();
this.mixins.observable.constructor.call(this);
this.clear(true);
},
/**
* Gets the number of items in the hash.
* @return {Number} The number of items in the hash.
*/
getCount: function() {
return this.length;
},
/**
* Implementation for being able to extract the key from an object if only
* a single argument is passed.
* @private
* @param {String} key The key
* @param {Object} value The value
* @return {Array} [key, value]
*/
getData: function(key, value) {
// if we have no value, it means we need to get the key from the object
if (value === undefined) {
value = key;
key = this.getKey(value);
}
return [key, value];
},
/**
* Extracts the key from an object. This is a default implementation, it may be overridden.
* @private
* @param {Object} o The object to get the key from.
* @return {String} The key to use.
*/
getKey: function(o) {
return o.id;
},
/**
* Add a new item to the hash. An exception will be thrown if the key already exists.
* @param {String} key The key of the new item.
* @param {Object} value The value of the new item.
* @return {Object} The value of the new item added.
*/
add: function(key, value) {
var me = this,
data;
if (me.containsKey(key)) {
throw new Error('This key already exists in the HashMap');
}
data = this.getData(key, value);
key = data[0];
value = data[1];
me.map[key] = value;
++me.length;
me.fireEvent('add', me, key, value);
return value;
},
/**
* Replaces an item in the hash. If the key doesn't exist, the
* `{@link #method-add}` method will be used.
* @param {String} key The key of the item.
* @param {Object} value The new value for the item.
* @return {Object} The new value of the item.
*/
replace: function(key, value) {
var me = this,
map = me.map,
old;
if (!me.containsKey(key)) {
me.add(key, value);
}
old = map[key];
map[key] = value;
me.fireEvent('replace', me, key, value, old);
return value;
},
/**
* Remove an item from the hash.
* @param {Object} o The value of the item to remove.
* @return {Boolean} `true` if the item was successfully removed.
*/
remove: function(o) {
var key = this.findKey(o);
if (key !== undefined) {
return this.removeByKey(key);
}
return false;
},
/**
* Remove an item from the hash.
* @param {String} key The key to remove.
* @return {Boolean} `true` if the item was successfully removed.
*/
removeByKey: function(key) {
var me = this,
value;
if (me.containsKey(key)) {
value = me.map[key];
delete me.map[key];
--me.length;
me.fireEvent('remove', me, key, value);
return true;
}
return false;
},
/**
* Retrieves an item with a particular key.
* @param {String} key The key to lookup.
* @return {Object} The value at that key. If it doesn't exist, `undefined` is returned.
*/
get: function(key) {
return this.map[key];
},
/**
* Removes all items from the hash.
* @return {Ext.util.HashMap} this
*/
clear: function(/* private */ initial) {
var me = this;
me.map = {};
me.length = 0;
if (initial !== true) {
me.fireEvent('clear', me);
}
return me;
},
/**
* Checks whether a key exists in the hash.
* @param {String} key The key to check for.
* @return {Boolean} `true` if they key exists in the hash.
*/
containsKey: function(key) {
return this.map[key] !== undefined;
},
/**
* Checks whether a value exists in the hash.
* @param {Object} value The value to check for.
* @return {Boolean} `true` if the value exists in the dictionary.
*/
contains: function(value) {
return this.containsKey(this.findKey(value));
},
/**
* Return all of the keys in the hash.
* @return {Array} An array of keys.
*/
getKeys: function() {
return this.getArray(true);
},
/**
* Return all of the values in the hash.
* @return {Array} An array of values.
*/
getValues: function() {
return this.getArray(false);
},
/**
* Gets either the keys/values in an array from the hash.
* @private
* @param {Boolean} isKey `true` to extract the keys, otherwise, the value.
* @return {Array} An array of either keys/values from the hash.
*/
getArray: function(isKey) {
var arr = [],
key,
map = this.map;
for (key in map) {
if (map.hasOwnProperty(key)) {
arr.push(isKey ? key : map[key]);
}
}
return arr;
},
/**
* Executes the specified function once for each item in the hash.
*
* @param {Function} fn The function to execute.
* @param {String} fn.key The key of the item.
* @param {Number} fn.value The value of the item.
* @param {Number} fn.length The total number of items in the hash.
* @param {Boolean} fn.return Returning `false` from the function will cease the iteration.
* @param {Object} [scope=this] The scope to execute in.
* @return {Ext.util.HashMap} this
*/
each: function(fn, scope) {
// copy items so they may be removed during iteration.
var items = Ext.apply({}, this.map),
key,
length = this.length;
scope = scope || this;
for (key in items) {
if (items.hasOwnProperty(key)) {
if (fn.call(scope, key, items[key], length) === false) {
break;
}
}
}
return this;
},
/**
* Performs a shallow copy on this hash.
* @return {Ext.util.HashMap} The new hash object.
*/
clone: function() {
var hash = new Ext.util.HashMap(),
map = this.map,
key;
hash.suspendEvents();
for (key in map) {
if (map.hasOwnProperty(key)) {
hash.add(key, map[key]);
}
}
hash.resumeEvents();
return hash;
},
/**
* @private
* Find the key for a value.
* @param {Object} value The value to find.
* @return {Object} The value of the item. Returns `undefined` if not found.
*/
findKey: function(value) {
var key,
map = this.map;
for (key in map) {
if (map.hasOwnProperty(key) && map[key] === value) {
return key;
}
}
return undefined;
}
});

View File

@@ -0,0 +1,300 @@
/**
* General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
* {@link #ordinalize ordinalizes} words. Sample usage:
*
* // turning singular words into plurals
* Ext.util.Inflector.pluralize('word'); // 'words'
* Ext.util.Inflector.pluralize('person'); // 'people'
* Ext.util.Inflector.pluralize('sheep'); // 'sheep'
*
* // turning plurals into singulars
* Ext.util.Inflector.singularize('words'); // 'word'
* Ext.util.Inflector.singularize('people'); // 'person'
* Ext.util.Inflector.singularize('sheep'); // 'sheep'
*
* // ordinalizing numbers
* Ext.util.Inflector.ordinalize(11); // "11th"
* Ext.util.Inflector.ordinalize(21); // "21st"
* Ext.util.Inflector.ordinalize(1043); // "1043rd"
*
* ## Customization
*
* The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
* rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
* Here is how we might add a rule that pluralizes "ox" to "oxen":
*
* Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
*
* Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string.
* In this case, the regular expression will only match the string "ox", and will replace that match with "oxen".
* Here's how we could add the inverse rule:
*
* Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
*
* __Note:__ The ox/oxen rules are present by default.
*/
Ext.define('Ext.util.Inflector', {
/* Begin Definitions */
singleton: true,
/* End Definitions */
/**
* @private
* The registered plural tuples. Each item in the array should contain two items - the first must be a regular
* expression that matchers the singular form of a word, the second must be a String that replaces the matched
* part of the regular expression. This is managed by the {@link #plural} method.
* @property plurals
* @type Array
*/
plurals: [
[(/(quiz)$/i), "$1zes" ],
[(/^(ox)$/i), "$1en" ],
[(/([m|l])ouse$/i), "$1ice" ],
[(/(matr|vert|ind)ix|ex$/i), "$1ices" ],
[(/(x|ch|ss|sh)$/i), "$1es" ],
[(/([^aeiouy]|qu)y$/i), "$1ies" ],
[(/(hive)$/i), "$1s" ],
[(/(?:([^f])fe|([lr])f)$/i), "$1$2ves"],
[(/sis$/i), "ses" ],
[(/([ti])um$/i), "$1a" ],
[(/(buffal|tomat|potat)o$/i), "$1oes" ],
[(/(bu)s$/i), "$1ses" ],
[(/(alias|status|sex)$/i), "$1es" ],
[(/(octop|vir)us$/i), "$1i" ],
[(/(ax|test)is$/i), "$1es" ],
[(/^person$/), "people" ],
[(/^man$/), "men" ],
[(/^(child)$/), "$1ren" ],
[(/s$/i), "s" ],
[(/$/), "s" ]
],
/**
* @private
* The set of registered singular matchers. Each item in the array should contain two items - the first must be a
* regular expression that matches the plural form of a word, the second must be a String that replaces the
* matched part of the regular expression. This is managed by the {@link #singular} method.
* @property singulars
* @type Array
*/
singulars: [
[(/(quiz)zes$/i), "$1" ],
[(/(matr)ices$/i), "$1ix" ],
[(/(vert|ind)ices$/i), "$1ex" ],
[(/^(ox)en/i), "$1" ],
[(/(alias|status)es$/i), "$1" ],
[(/(octop|vir)i$/i), "$1us" ],
[(/(cris|ax|test)es$/i), "$1is" ],
[(/(shoe)s$/i), "$1" ],
[(/(o)es$/i), "$1" ],
[(/(bus)es$/i), "$1" ],
[(/([m|l])ice$/i), "$1ouse" ],
[(/(x|ch|ss|sh)es$/i), "$1" ],
[(/(m)ovies$/i), "$1ovie" ],
[(/(s)eries$/i), "$1eries"],
[(/([^aeiouy]|qu)ies$/i), "$1y" ],
[(/([lr])ves$/i), "$1f" ],
[(/(tive)s$/i), "$1" ],
[(/(hive)s$/i), "$1" ],
[(/([^f])ves$/i), "$1fe" ],
[(/(^analy)ses$/i), "$1sis" ],
[(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
[(/([ti])a$/i), "$1um" ],
[(/(n)ews$/i), "$1ews" ],
[(/people$/i), "person" ],
[(/s$/i), "" ]
],
/**
* @private
* The registered uncountable words
* @property uncountable
* @type Array
*/
uncountable: [
"sheep",
"fish",
"series",
"species",
"money",
"rice",
"information",
"equipment",
"grass",
"mud",
"offspring",
"deer",
"means"
],
/**
* Adds a new singularization rule to the Inflector. See the intro docs for more information
* @param {RegExp} matcher The matcher regex
* @param {String} replacer The replacement string, which can reference matches from the matcher argument
*/
singular: function(matcher, replacer) {
this.singulars.unshift([matcher, replacer]);
},
/**
* Adds a new pluralization rule to the Inflector. See the intro docs for more information
* @param {RegExp} matcher The matcher regex
* @param {String} replacer The replacement string, which can reference matches from the matcher argument
*/
plural: function(matcher, replacer) {
this.plurals.unshift([matcher, replacer]);
},
/**
* Removes all registered singularization rules
*/
clearSingulars: function() {
this.singulars = [];
},
/**
* Removes all registered pluralization rules
*/
clearPlurals: function() {
this.plurals = [];
},
/**
* Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
* @param {String} word The word to test
* @return {Boolean} True if the word is transnumeral
*/
isTransnumeral: function(word) {
return Ext.Array.indexOf(this.uncountable, word) != -1;
},
/**
* Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
* @param {String} word The word to pluralize
* @return {String} The pluralized form of the word
*/
pluralize: function(word) {
if (this.isTransnumeral(word)) {
return word;
}
var plurals = this.plurals,
length = plurals.length,
tuple, regex, i;
for (i = 0; i < length; i++) {
tuple = plurals[i];
regex = tuple[0];
if (regex == word || (regex.test && regex.test(word))) {
return word.replace(regex, tuple[1]);
}
}
return word;
},
/**
* Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
* @param {String} word The word to singularize
* @return {String} The singularized form of the word
*/
singularize: function(word) {
if (this.isTransnumeral(word)) {
return word;
}
var singulars = this.singulars,
length = singulars.length,
tuple, regex, i;
for (i = 0; i < length; i++) {
tuple = singulars[i];
regex = tuple[0];
if (regex == word || (regex.test && regex.test(word))) {
return word.replace(regex, tuple[1]);
}
}
return word;
},
/**
* Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
* package
* @param {String} word The word to classify
* @return {String} The classified version of the word
*/
classify: function(word) {
return Ext.String.capitalize(this.singularize(word));
},
/**
* Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
* number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
* @param {Number} number The number to ordinalize
* @return {String} The ordinalized number
*/
ordinalize: function(number) {
var parsed = parseInt(number, 10),
mod10 = parsed % 10,
mod100 = parsed % 100;
//11 through 13 are a special case
if (11 <= mod100 && mod100 <= 13) {
return number + "th";
} else {
switch(mod10) {
case 1 : return number + "st";
case 2 : return number + "nd";
case 3 : return number + "rd";
default: return number + "th";
}
}
}
}, function() {
//aside from the rules above, there are a number of words that have irregular pluralization so we add them here
var irregulars = {
alumnus: 'alumni',
cactus : 'cacti',
focus : 'foci',
nucleus: 'nuclei',
radius: 'radii',
stimulus: 'stimuli',
ellipsis: 'ellipses',
paralysis: 'paralyses',
oasis: 'oases',
appendix: 'appendices',
index: 'indexes',
beau: 'beaux',
bureau: 'bureaux',
tableau: 'tableaux',
woman: 'women',
child: 'children',
man: 'men',
corpus: 'corpora',
criterion: 'criteria',
curriculum: 'curricula',
genus: 'genera',
memorandum: 'memoranda',
phenomenon: 'phenomena',
foot: 'feet',
goose: 'geese',
tooth: 'teeth',
antenna: 'antennae',
formula: 'formulae',
nebula: 'nebulae',
vertebra: 'vertebrae',
vita: 'vitae'
},
singular;
for (singular in irregulars) {
this.plural(singular, irregulars[singular]);
this.singular(irregulars[singular], singular);
}
});

521
OfficeWeb/vendor/touch/src/util/JSON.js vendored Normal file
View File

@@ -0,0 +1,521 @@
/*
http://www.JSON.org/json2.js
2010-03-20
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
this.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
return v;
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
/**
* @class Ext.util.JSON
* Modified version of Douglas Crockford"s json.js that doesn"t
* mess with the Object prototype
* http://www.json.org/js.html
* @singleton
* @ignore
*/
Ext.util.JSON = {
encode: function(o) {
return JSON.stringify(o);
},
decode: function(s) {
return JSON.parse(s);
}
};
/**
* Shorthand for {@link Ext.util.JSON#encode}
* @param {Mixed} o The variable to encode
* @return {String} The JSON string
* @member Ext
* @method encode
* @ignore
*/
Ext.encode = Ext.util.JSON.encode;
/**
* Shorthand for {@link Ext.util.JSON#decode}
* @param {String} json The JSON string
* @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
* @return {Object} The resulting object
* @member Ext
* @method decode
* @ignore
*/
Ext.decode = Ext.util.JSON.decode;

View File

@@ -0,0 +1,66 @@
/**
* @class Ext.util.LineSegment
*
* Utility class that represents a line segment, constructed by two {@link Ext.util.Point}
*/
Ext.define('Ext.util.LineSegment', {
requires: ['Ext.util.Point'],
/**
* 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);
},
/**
* 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();
}
});

View File

@@ -0,0 +1,194 @@
/**
* 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} [direction=ASC] '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);
});
}
});

View File

@@ -0,0 +1,60 @@
/**
* @private
*/
Ext.define('Ext.util.Offset', {
/* Begin Definitions */
statics: {
fromObject: function(obj) {
return new this(obj.x, obj.y);
}
},
/* End Definitions */
constructor: function(x, y) {
this.x = (x != null && !isNaN(x)) ? x : 0;
this.y = (y != null && !isNaN(y)) ? y : 0;
return this;
},
copy: function() {
return new Ext.util.Offset(this.x, this.y);
},
copyFrom: function(p) {
this.x = p.x;
this.y = p.y;
},
toString: function() {
return "Offset[" + this.x + "," + this.y + "]";
},
equals: function(offset) {
//<debug>
if(!(offset instanceof this.statics())) {
Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
}
//</debug>
return (this.x == offset.x && this.y == offset.y);
},
round: function(to) {
if (!isNaN(to)) {
var factor = Math.pow(10, to);
this.x = Math.round(this.x * factor) / factor;
this.y = Math.round(this.y * factor) / factor;
} else {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
},
isZero: function() {
return this.x == 0 && this.y == 0;
}
});

View File

@@ -0,0 +1,18 @@
/**
*
*/
Ext.define('Ext.util.PaintMonitor', {
requires: [
'Ext.util.paintmonitor.CssAnimation',
'Ext.util.paintmonitor.OverflowChange'
],
constructor: function(config) {
if (Ext.browser.engineVersion.gtEq('536')) {
return new Ext.util.paintmonitor.OverflowChange(config);
}
else {
return new Ext.util.paintmonitor.CssAnimation(config);
}
}
});

191
OfficeWeb/vendor/touch/src/util/Point.js vendored Normal file
View File

@@ -0,0 +1,191 @@
/**
* 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
* @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} 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} 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) {
return (Math.round(this.x) === Math.round(point.x) &&
Math.round(this.y) === Math.round(point.y));
},
getDistanceTo: function(point) {
var deltaX = this.x - point.x,
deltaY = this.y - point.y;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
},
getAngleTo: function(point) {
var deltaX = this.x - point.x,
deltaY = this.y - point.y;
return Math.atan2(deltaY, deltaX) * this.radianToDegreeConstant;
}
});

View File

@@ -0,0 +1,127 @@
/**
* @private
*/
Ext.define('Ext.util.PositionMap', {
config: {
minimumHeight: 50
},
constructor: function(config) {
this.map = [];
this.adjustments = {};
this.offset = 0;
this.initConfig(config);
},
populate: function(count, offset) {
var map = this.map = this.map || [],
minimumHeight = this.getMinimumHeight(),
i, previousIndex, ln;
// We add 1 item to the count so that we can get the height of the bottom item
count++;
map.length = count;
map[0] = 0;
for (i = offset + 1, ln = count - 1; i <= ln; i++) {
previousIndex = i - 1;
map[i] = map[previousIndex] + minimumHeight;
}
this.adjustments = {
indices: [],
heights: {}
};
this.offset = 0;
for (i = 1, ln = count - 1; i <= ln; i++) {
previousIndex = i - 1;
this.offset += map[i] - map[previousIndex] - minimumHeight;
}
},
setItemHeight: function(index, height) {
height = Math.max(height, this.getMinimumHeight());
if (height !== this.getItemHeight(index)) {
var adjustments = this.adjustments;
adjustments.indices.push(parseInt(index, 10));
adjustments.heights[index] = height;
}
},
update: function() {
var adjustments = this.adjustments,
indices = adjustments.indices,
heights = adjustments.heights,
map = this.map,
ln = indices.length,
minimumHeight = this.getMinimumHeight(),
difference = 0,
i, j, height, index, nextIndex, currentHeight;
if (!adjustments.indices.length) {
return false;
}
Ext.Array.sort(indices, function(a, b) {
return a - b;
});
for (i = 0; i < ln; i++) {
index = indices[i];
nextIndex = indices[i + 1] || map.length - 1;
currentHeight = (map[index + 1] !== undefined) ? (map[index + 1] - map[index] + difference) : minimumHeight;
height = heights[index];
difference += height - currentHeight;
for (j = index + 1; j <= nextIndex; j++) {
map[j] += difference;
}
}
this.offset += difference;
this.adjustments = {
indices: [],
heights: {}
};
return true;
},
getItemHeight: function(index) {
return this.map[index + 1] - this.map[index];
},
getTotalHeight: function() {
return ((this.map.length - 1) * this.getMinimumHeight()) + this.offset;
},
findIndex: function(pos) {
return this.map.length ? this.binarySearch(this.map, pos) : 0;
},
binarySearch: function(sorted, value) {
var start = 0,
end = sorted.length;
if (value < sorted[0]) {
return 0;
}
if (value > sorted[end - 1]) {
return end - 1;
}
while (start + 1 < end) {
var mid = (start + end) >> 1,
val = sorted[mid];
if (val == value) {
return mid;
} else if (val < value) {
start = mid;
} else {
end = mid;
}
}
return start;
}
});

View File

@@ -0,0 +1,371 @@
/**
* Represents a rectangular region and provides a number of utility methods
* to compare regions.
*/
Ext.define('Ext.util.Region', {
requires: ['Ext.util.Offset'],
statics: {
/**
* @static
* Retrieves an Ext.util.Region for a particular element.
* @param {String/HTMLElement/Ext.Element} el The element or its ID.
* @return {Ext.util.Region} region
*/
getRegion: function(el) {
return Ext.fly(el).getPageBox(true);
},
/**
* @static
* Creates new Region from an object:
*
* Ext.util.Region.from({top: 0, right: 5, bottom: 3, left: -1});
* // the above is equivalent to:
* new Ext.util.Region(0, 5, 3, -1);
*
* @param {Object} o An object with `top`, `right`, `bottom`, and `left` properties.
* @param {Number} o.top
* @param {Number} o.right
* @param {Number} o.bottom
* @param {Number} o.left
* @return {Ext.util.Region} The region constructed based on the passed object.
*/
from: function(o) {
return new this(o.top, o.right, o.bottom, o.left);
}
},
/**
* Creates new Region.
* @param {Number} top Top
* @param {Number} right Right
* @param {Number} bottom Bottom
* @param {Number} left Left
*/
constructor: function(top, right, bottom, left) {
var me = this;
me.top = top;
me[1] = top;
me.right = right;
me.bottom = bottom;
me.left = left;
me[0] = left;
},
/**
* Checks if this region completely contains the region that is passed in.
* @param {Ext.util.Region} region
* @return {Boolean}
*/
contains: function(region) {
var me = this;
return (region.left >= me.left &&
region.right <= me.right &&
region.top >= me.top &&
region.bottom <= me.bottom);
},
/**
* Checks if this region intersects the region passed in.
* @param {Ext.util.Region} region
* @return {Ext.util.Region/Boolean} Returns the intersected region or `false` if there is no intersection.
*/
intersect: function(region) {
var me = this,
t = Math.max(me.top, region.top),
r = Math.min(me.right, region.right),
b = Math.min(me.bottom, region.bottom),
l = Math.max(me.left, region.left);
if (b > t && r > l) {
return new Ext.util.Region(t, r, b, l);
}
else {
return false;
}
},
/**
* Returns the smallest region that contains the current AND `targetRegion`.
* @param {Ext.util.Region} region
* @return {Ext.util.Region}
*/
union: function(region) {
var me = this,
t = Math.min(me.top, region.top),
r = Math.max(me.right, region.right),
b = Math.max(me.bottom, region.bottom),
l = Math.min(me.left, region.left);
return new Ext.util.Region(t, r, b, l);
},
/**
* Modifies the current region to be constrained to the `targetRegion`.
* @param {Ext.util.Region} targetRegion
* @return {Ext.util.Region} this
*/
constrainTo: function(targetRegion) {
var me = this,
constrain = Ext.util.Numbers.constrain;
me.top = constrain(me.top, targetRegion.top, targetRegion.bottom);
me.bottom = constrain(me.bottom, targetRegion.top, targetRegion.bottom);
me.left = constrain(me.left, targetRegion.left, targetRegion.right);
me.right = constrain(me.right, targetRegion.left, targetRegion.right);
return me;
},
/**
* Modifies the current region to be adjusted by offsets.
* @param {Number} top Top offset
* @param {Number} right Right offset
* @param {Number} bottom Bottom offset
* @param {Number} left Left offset
* @return {Ext.util.Region} this
* @chainable
*/
adjust: function(top, right, bottom, left) {
var me = this;
me.top += top;
me.left += left;
me.right += right;
me.bottom += bottom;
return me;
},
/**
* Get the offset amount of a point outside the region.
* @param {String/Object} axis optional.
* @param {Ext.util.Point} p The point.
* @return {Ext.util.Region}
*/
getOutOfBoundOffset: function(axis, p) {
if (!Ext.isObject(axis)) {
if (axis == 'x') {
return this.getOutOfBoundOffsetX(p);
} else {
return this.getOutOfBoundOffsetY(p);
}
} else {
var d = new Ext.util.Offset();
d.x = this.getOutOfBoundOffsetX(axis.x);
d.y = this.getOutOfBoundOffsetY(axis.y);
return d;
}
},
/**
* Get the offset amount on the x-axis.
* @param {Number} p The offset.
* @return {Number}
*/
getOutOfBoundOffsetX: function(p) {
if (p <= this.left) {
return this.left - p;
} else if (p >= this.right) {
return this.right - p;
}
return 0;
},
/**
* Get the offset amount on the y-axis.
* @param {Number} p The offset.
* @return {Number}
*/
getOutOfBoundOffsetY: function(p) {
if (p <= this.top) {
return this.top - p;
} else if (p >= this.bottom) {
return this.bottom - p;
}
return 0;
},
/**
* Check whether the point / offset is out of bounds.
* @param {String} axis optional
* @param {Ext.util.Point/Number} p The point / offset.
* @return {Boolean}
*/
isOutOfBound: function(axis, p) {
if (!Ext.isObject(axis)) {
if (axis == 'x') {
return this.isOutOfBoundX(p);
} else {
return this.isOutOfBoundY(p);
}
} else {
p = axis;
return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
}
},
/**
* Check whether the offset is out of bound in the x-axis.
* @param {Number} p The offset.
* @return {Boolean}
*/
isOutOfBoundX: function(p) {
return (p < this.left || p > this.right);
},
/**
* Check whether the offset is out of bound in the y-axis.
* @param {Number} p The offset.
* @return {Boolean}
*/
isOutOfBoundY: function(p) {
return (p < this.top || p > this.bottom);
},
/*
* Restrict a point within the region by a certain factor.
* @param {String} axis Optional
* @param {Ext.util.Point/Ext.util.Offset/Object} p
* @param {Number} factor
* @return {Ext.util.Point/Ext.util.Offset/Object/Number}
*/
restrict: function(axis, p, factor) {
if (Ext.isObject(axis)) {
var newP;
factor = p;
p = axis;
if (p.copy) {
newP = p.copy();
}
else {
newP = {
x: p.x,
y: p.y
};
}
newP.x = this.restrictX(p.x, factor);
newP.y = this.restrictY(p.y, factor);
return newP;
} else {
if (axis == 'x') {
return this.restrictX(p, factor);
} else {
return this.restrictY(p, factor);
}
}
},
/*
* Restrict an offset within the region by a certain factor, on the x-axis.
* @param {Number} p
* @param {Number} [factor=1] (optional) The factor.
* @return {Number}
*/
restrictX: function(p, factor) {
if (!factor) {
factor = 1;
}
if (p <= this.left) {
p -= (p - this.left) * factor;
}
else if (p >= this.right) {
p -= (p - this.right) * factor;
}
return p;
},
/*
* Restrict an offset within the region by a certain factor, on the y-axis.
* @param {Number} p
* @param {Number} [factor=1] (optional) The factor.
* @return {Number}
*/
restrictY: function(p, factor) {
if (!factor) {
factor = 1;
}
if (p <= this.top) {
p -= (p - this.top) * factor;
}
else if (p >= this.bottom) {
p -= (p - this.bottom) * factor;
}
return p;
},
/*
* Get the width / height of this region.
* @return {Object} An object with `width` and `height` properties.
* @return {Number} return.width
* @return {Number} return.height
*/
getSize: function() {
return {
width: this.right - this.left,
height: this.bottom - this.top
};
},
/**
* Copy a new instance.
* @return {Ext.util.Region}
*/
copy: function() {
return new Ext.util.Region(this.top, this.right, this.bottom, this.left);
},
/**
* Dump this to an eye-friendly string, great for debugging.
* @return {String} For example `Region[0,1,3,2]`.
*/
toString: function() {
return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
},
/**
* Translate this region by the given offset amount.
* @param {Object} offset
* @return {Ext.util.Region} This Region.
* @chainable
*/
translateBy: function(offset) {
this.left += offset.x;
this.right += offset.x;
this.top += offset.y;
this.bottom += offset.y;
return this;
},
/**
* Round all the properties of this region.
* @return {Ext.util.Region} This Region.
* @chainable
*/
round: function() {
this.top = Math.round(this.top);
this.right = Math.round(this.right);
this.bottom = Math.round(this.bottom);
this.left = Math.round(this.left);
return this;
},
/**
* Check whether this region is equivalent to the given region.
* @param {Ext.util.Region} region The region to compare with.
* @return {Boolean}
*/
equals: function(region) {
return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left)
}
});

View File

@@ -0,0 +1,18 @@
/**
*
*/
Ext.define('Ext.util.SizeMonitor', {
requires: [
'Ext.util.sizemonitor.Scroll',
'Ext.util.sizemonitor.OverflowChange'
],
constructor: function(config) {
if (Ext.browser.engineVersion.gtEq('535')) {
return new Ext.util.sizemonitor.OverflowChange(config);
}
else {
return new Ext.util.sizemonitor.Scroll(config);
}
}
});

View File

@@ -0,0 +1,249 @@
/**
* @docauthor Tommy Maintz <tommy@sencha.com>
*
* 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",
requires: [
'Ext.util.Sorter'
],
/**
* @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);
}
});

View File

@@ -0,0 +1,194 @@
/**
* 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: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
* 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);
},
// <debug>
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;
},
// </debug>
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) {
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"));
}
});

View File

@@ -0,0 +1,135 @@
/**
* A wrapper class which can be applied to any element. Fires a "tap" event while
* touching the device. The interval between firings may be specified in the config but
* defaults to 20 milliseconds.
*/
Ext.define('Ext.util.TapRepeater', {
requires: ['Ext.DateExtras'],
mixins: {
observable: 'Ext.mixin.Observable'
},
/**
* @event touchstart
* Fires when the touch is started.
* @param {Ext.util.TapRepeater} this
* @param {Ext.event.Event} e
*/
/**
* @event tap
* Fires on a specified interval during the time the element is pressed.
* @param {Ext.util.TapRepeater} this
* @param {Ext.event.Event} e
*/
/**
* @event touchend
* Fires when the touch is ended.
* @param {Ext.util.TapRepeater} this
* @param {Ext.event.Event} e
*/
config: {
el: null,
accelerate: true,
interval: 10,
delay: 250,
preventDefault: true,
stopDefault: false,
timer: 0,
pressCls: null
},
/**
* Creates new TapRepeater.
* @param {Mixed} el The element to listen on
* @param {Object} config
*/
constructor: function(config) {
var me = this;
//<debug warn>
for (var configName in config) {
if (me.self.prototype.config && !(configName in me.self.prototype.config)) {
me[configName] = config[configName];
Ext.Logger.warn('Applied config as instance property: "' + configName + '"', me);
}
}
//</debug>
me.initConfig(config);
},
updateEl: function(newEl, oldEl) {
var eventCfg = {
touchstart: 'onTouchStart',
touchend: 'onTouchEnd',
tap: 'eventOptions',
scope: this
};
if (oldEl) {
oldEl.un(eventCfg)
}
newEl.on(eventCfg);
},
// @private
eventOptions: function(e) {
if (this.getPreventDefault()) {
e.preventDefault();
}
if (this.getStopDefault()) {
e.stopEvent();
}
},
// @private
destroy: function() {
this.clearListeners();
Ext.destroy(this.el);
},
// @private
onTouchStart: function(e) {
var me = this,
pressCls = me.getPressCls();
clearTimeout(me.getTimer());
if (pressCls) {
me.getEl().addCls(pressCls);
}
me.tapStartTime = new Date();
me.fireEvent('touchstart', me, e);
me.fireEvent('tap', me, e);
// Do not honor delay or interval if acceleration wanted.
if (me.getAccelerate()) {
me.delay = 400;
}
me.setTimer(Ext.defer(me.tap, me.getDelay() || me.getInterval(), me, [e]));
},
// @private
tap: function(e) {
var me = this;
me.fireEvent('tap', me, e);
me.setTimer(Ext.defer(me.tap, me.getAccelerate() ? me.easeOutExpo(Ext.Date.getElapsed(me.tapStartTime),
400,
-390,
12000) : me.getInterval(), me, [e]));
},
// Easing calculation
// @private
easeOutExpo: function(t, b, c, d) {
return (t == d) ? b + c : c * ( - Math.pow(2, -10 * t / d) + 1) + b;
},
// @private
onTouchEnd: function(e) {
var me = this;
clearTimeout(me.getTimer());
me.getEl().removeCls(me.getPressCls());
me.fireEvent('touchend', me, e);
}
});

View File

@@ -0,0 +1,39 @@
/**
* 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', {
requires: [
'Ext.util.translatable.CssTransform',
'Ext.util.translatable.ScrollPosition'
],
constructor: function(config) {
var namespace = Ext.util.translatable,
CssTransform = namespace.CssTransform,
ScrollPosition = namespace.ScrollPosition,
classReference;
if (typeof config == 'object' && 'translationMethod' in config) {
if (config.translationMethod === 'scrollposition') {
classReference = ScrollPosition;
}
else if (config.translationMethod === 'csstransform') {
classReference = CssTransform;
}
}
if (!classReference) {
if (Ext.os.is.Android2 || Ext.browser.is.ChromeMobile) {
classReference = ScrollPosition;
}
else {
classReference = CssTransform;
}
}
return new classReference(config);
}
});

View File

@@ -0,0 +1,50 @@
/**
* @private
*/
Ext.define('Ext.util.TranslatableGroup', {
extend: 'Ext.util.translatable.Abstract',
config: {
items: [],
activeIndex: 0,
itemLength: {
x: 0,
y: 0
}
},
applyItems: function(items) {
return Ext.Array.from(items);
},
doTranslate: function(x, y) {
var items = this.getItems(),
activeIndex = this.getActiveIndex(),
itemLength = this.getItemLength(),
itemLengthX = itemLength.x,
itemLengthY = itemLength.y,
useX = typeof x == 'number',
useY = typeof y == 'number',
offset, i, ln, item, translateX, translateY;
for (i = 0, ln = items.length; i < ln; i++) {
item = items[i];
if (item) {
offset = (i - activeIndex);
if (useX) {
translateX = x + offset * itemLengthX;
}
if (useY) {
translateY = y + offset * itemLengthY;
}
item.translate(translateX, translateY);
}
}
}
});

View File

@@ -0,0 +1,30 @@
/**
* @private
*/
Ext.define('Ext.util.TranslatableList', {
extend: 'Ext.util.translatable.Abstract',
config: {
items: []
},
applyItems: function(items) {
return Ext.Array.from(items);
},
doTranslate: function(x, y) {
var items = this.getItems(),
offset = 0,
i, ln, item, translateY;
for (i = 0, ln = items.length; i < ln; i++) {
item = items[i];
if (item && !item._list_hidden) {
translateY = y + offset;
offset += item.$height;
item.translate(0, translateY);
}
}
}
});

View File

@@ -0,0 +1,81 @@
/**
*
*/
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();
}
});

View File

@@ -0,0 +1,66 @@
/**
* @private
*/
Ext.define('Ext.util.paintmonitor.Abstract', {
config: {
element: null,
callback: Ext.emptyFn,
scope: null,
args: []
},
eventName: '',
monitorClass: '',
constructor: function(config) {
this.onElementPainted = Ext.Function.bind(this.onElementPainted, this);
this.initConfig(config);
},
bindListeners: function(bind) {
this.monitorElement[bind ? 'addEventListener' : 'removeEventListener'](this.eventName, this.onElementPainted, true);
},
applyElement: function(element) {
if (element) {
return Ext.get(element);
}
},
updateElement: function(element) {
this.monitorElement = Ext.Element.create({
classList: ['x-paint-monitor', this.monitorClass]
}, true);
element.appendChild(this.monitorElement);
element.addCls('x-paint-monitored');
this.bindListeners(true);
},
onElementPainted: function() {},
destroy: function() {
var monitorElement = this.monitorElement,
parentNode = monitorElement.parentNode,
element = this.getElement();
this.bindListeners(false);
delete this.monitorElement;
if (element && !element.isDestroyed) {
element.removeCls('x-paint-monitored');
delete this._element;
}
if (parentNode) {
parentNode.removeChild(monitorElement);
}
this.callSuper();
}
});

View File

@@ -0,0 +1,17 @@
/**
* @private
*/
Ext.define('Ext.util.paintmonitor.CssAnimation', {
extend: 'Ext.util.paintmonitor.Abstract',
eventName: 'webkitAnimationEnd',
monitorClass: 'cssanimation',
onElementPainted: function(e) {
if (e.animationName === 'x-paint-monitor-helper') {
this.getCallback().apply(this.getScope(), this.getArgs());
}
}
});

View File

@@ -0,0 +1,15 @@
/**
* @private
*/
Ext.define('Ext.util.paintmonitor.OverflowChange', {
extend: 'Ext.util.paintmonitor.Abstract',
eventName: 'overflowchanged',
monitorClass: 'overflowchange',
onElementPainted: function(e) {
this.getCallback().apply(this.getScope(), this.getArgs());
}
});

View File

@@ -0,0 +1,129 @@
/**
* @private
*/
Ext.define('Ext.util.sizemonitor.Abstract', {
mixins: ['Ext.mixin.Templatable'],
requires: [
'Ext.TaskQueue'
],
config: {
element: null,
callback: Ext.emptyFn,
scope: null,
args: []
},
width: 0,
height: 0,
contentWidth: 0,
contentHeight: 0,
constructor: function(config) {
this.refresh = Ext.Function.bind(this.refresh, this);
this.info = {
width: 0,
height: 0,
contentWidth: 0,
contentHeight: 0,
flag: 0
};
this.initElement();
this.initConfig(config);
this.bindListeners(true);
},
bindListeners: Ext.emptyFn,
applyElement: function(element) {
if (element) {
return Ext.get(element);
}
},
updateElement: function(element) {
element.append(this.detectorsContainer);
element.addCls('x-size-monitored');
},
applyArgs: function(args) {
return args.concat([this.info]);
},
refreshMonitors: Ext.emptyFn,
forceRefresh: function() {
Ext.TaskQueue.requestRead('refresh', this);
},
refreshSize: function() {
var element = this.getElement();
if (!element || element.isDestroyed) {
return false;
}
var width = element.getWidth(),
height = element.getHeight(),
contentElement = this.detectorsContainer,
contentWidth = contentElement.offsetWidth,
contentHeight = contentElement.offsetHeight,
currentContentWidth = this.contentWidth,
currentContentHeight = this.contentHeight,
info = this.info,
resized = false,
flag;
this.width = width;
this.height = height;
this.contentWidth = contentWidth;
this.contentHeight = contentHeight;
flag = ((currentContentWidth !== contentWidth ? 1 : 0) + (currentContentHeight !== contentHeight ? 2 : 0));
if (flag > 0) {
info.width = width;
info.height = height;
info.contentWidth = contentWidth;
info.contentHeight = contentHeight;
info.flag = flag;
resized = true;
this.getCallback().apply(this.getScope(), this.getArgs());
}
return resized;
},
refresh: function(force) {
if (this.refreshSize() || force) {
Ext.TaskQueue.requestWrite('refreshMonitors', this);
}
},
destroy: function() {
var element = this.getElement();
this.bindListeners(false);
if (element && !element.isDestroyed) {
element.removeCls('x-size-monitored');
}
delete this._element;
this.callSuper();
}
});

View File

@@ -0,0 +1,78 @@
/**
* @private
*/
Ext.define('Ext.util.sizemonitor.OverflowChange', {
extend: 'Ext.util.sizemonitor.Abstract',
constructor: function(config) {
this.onExpand = Ext.Function.bind(this.onExpand, this);
this.onShrink = Ext.Function.bind(this.onShrink, this);
this.callSuper(arguments);
},
getElementConfig: function() {
return {
reference: 'detectorsContainer',
classList: ['x-size-monitors', 'overflowchanged'],
children: [
{
reference: 'expandMonitor',
className: 'expand',
children: [{
reference: 'expandHelper'
}]
},
{
reference: 'shrinkMonitor',
className: 'shrink',
children: [{
reference: 'shrinkHelper'
}]
}
]
}
},
bindListeners: function(bind) {
var method = bind ? 'addEventListener' : 'removeEventListener';
this.expandMonitor[method]('overflowchanged', this.onExpand, true);
this.shrinkMonitor[method]('overflowchanged', this.onShrink, true);
},
onExpand: function(e) {
if (e.horizontalOverflow && e.verticalOverflow) {
return;
}
Ext.TaskQueue.requestRead('refresh', this);
},
onShrink: function(e) {
if (!e.horizontalOverflow && !e.verticalOverflow) {
return;
}
Ext.TaskQueue.requestRead('refresh', this);
},
refreshMonitors: function() {
var expandHelper = this.expandHelper,
shrinkHelper = this.shrinkHelper,
width = this.contentWidth,
height = this.contentHeight;
if (expandHelper && !expandHelper.isDestroyed) {
expandHelper.style.width = (width + 1) + 'px';
expandHelper.style.height = (height + 1) + 'px';
}
if (shrinkHelper && !shrinkHelper.isDestroyed) {
shrinkHelper.style.width = width + 'px';
shrinkHelper.style.height = height + 'px';
}
Ext.TaskQueue.requestRead('refresh', this);
}
});

View File

@@ -0,0 +1,61 @@
/**
* @private
*/
Ext.define('Ext.util.sizemonitor.Scroll', {
extend: 'Ext.util.sizemonitor.Abstract',
getElementConfig: function() {
return {
reference: 'detectorsContainer',
classList: ['x-size-monitors', 'scroll'],
children: [
{
reference: 'expandMonitor',
className: 'expand'
},
{
reference: 'shrinkMonitor',
className: 'shrink'
}
]
}
},
constructor: function(config) {
this.onScroll = Ext.Function.bind(this.onScroll, this);
this.callSuper(arguments);
},
bindListeners: function(bind) {
var method = bind ? 'addEventListener' : 'removeEventListener';
this.expandMonitor[method]('scroll', this.onScroll, true);
this.shrinkMonitor[method]('scroll', this.onScroll, true);
},
forceRefresh: function() {
Ext.TaskQueue.requestRead('refresh', this, [true]);
},
onScroll: function() {
Ext.TaskQueue.requestRead('refresh', this);
},
refreshMonitors: function() {
var expandMonitor = this.expandMonitor,
shrinkMonitor = this.shrinkMonitor,
end = 1000000;
if (expandMonitor && !expandMonitor.isDestroyed) {
expandMonitor.scrollLeft = end;
expandMonitor.scrollTop = end;
}
if (shrinkMonitor && !shrinkMonitor.isDestroyed) {
shrinkMonitor.scrollLeft = end;
shrinkMonitor.scrollTop = end;
}
}
});

View File

@@ -0,0 +1,257 @@
/**
* @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',
requires: ['Ext.fx.easing.Linear'],
config: {
easing: null,
easingX: null,
easingY: null,
fps: Ext.os.is.Android4 ? 50 : 60
},
/**
* @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.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);
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);
},
updateFps: function(fps) {
this.animationInterval = 1000 / fps;
},
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;
this.animationFrameId = requestAnimationFrame(this.doAnimationFrame);
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;
this.animationFrameId = requestAnimationFrame(this.doAnimationFrame);
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;
cancelAnimationFrame(this.animationFrameId);
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);
}
});

View File

@@ -0,0 +1,29 @@
/**
* @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);
}
});

View File

@@ -0,0 +1,22 @@
/**
* @private
*
* CSS Transform implementation
*/
Ext.define('Ext.util.translatable.CssTransform', {
extend: 'Ext.util.translatable.Dom',
doTranslate: function() {
this.getElement().dom.style.webkitTransform = 'translate3d(' + this.x + 'px, ' + this.y + 'px, 0px)';
},
destroy: function() {
var element = this.getElement();
if (element && !element.isDestroyed) {
element.dom.style.webkitTransform = null;
}
this.callSuper();
}
});

View File

@@ -0,0 +1,22 @@
/**
* @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();
}
});

View File

@@ -0,0 +1,97 @@
/**
* @private
*
* Scroll position implementation
*/
Ext.define('Ext.util.translatable.ScrollPosition', {
extend: 'Ext.util.translatable.Dom',
wrapperWidth: 0,
wrapperHeight: 0,
config: {
useWrapper: true
},
getWrapper: function() {
var wrapper = this.wrapper,
element = this.getElement(),
container;
if (!wrapper) {
container = element.getParent();
if (!container) {
return null;
}
if (this.getUseWrapper()) {
wrapper = element.wrap();
}
else {
wrapper = container;
}
element.addCls('x-translatable');
wrapper.addCls('x-translatable-container');
this.wrapper = wrapper;
wrapper.on('resize', 'onWrapperResize', this);
wrapper.on('painted', '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 = this.wrapperWidth - x;
}
if (typeof y == 'number') {
dom.scrollTop = this.wrapperHeight - y;
}
}
},
onWrapperResize: function(wrapper, info) {
this.wrapperWidth = info.width;
this.wrapperHeight = info.height;
this.refresh();
},
destroy: function() {
var element = this.getElement(),
wrapper = this.wrapper;
if (wrapper) {
if (!element.isDestroyed) {
if (this.getUseWrapper()) {
wrapper.doReplaceWith(element);
}
element.removeCls('x-translatable');
}
wrapper.removeCls('x-translatable-container');
wrapper.un('resize', 'onWrapperResize', this);
wrapper.un('painted', 'refresh', this);
delete this.wrapper;
delete this._element;
}
this.callSuper();
}
});