init repo

This commit is contained in:
nikolay ivanov
2014-07-05 18:22:49 +00:00
commit a8be6b9e72
17348 changed files with 9229832 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Base Manager class
*/
Ext.define('Ext.AbstractManager', {
/* Begin Definitions */
requires: ['Ext.util.HashMap'],
/* End Definitions */
typeName: 'type',
constructor: function(config) {
Ext.apply(this, config || {});
/**
* @property {Ext.util.HashMap} all
* Contains all of the items currently managed
*/
this.all = Ext.create('Ext.util.HashMap');
this.types = {};
},
/**
* Returns an item by id.
* For additional details see {@link Ext.util.HashMap#get}.
* @param {String} id The id of the item
* @return {Object} The item, undefined if not found.
*/
get : function(id) {
return this.all.get(id);
},
/**
* Registers an item to be managed
* @param {Object} item The item to register
*/
register: function(item) {
//<debug>
var all = this.all,
key = all.getKey(item);
if (all.containsKey(key)) {
Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
}
//</debug>
this.all.add(item);
},
/**
* Unregisters an item by removing it from this manager
* @param {Object} item The item to unregister
*/
unregister: function(item) {
this.all.remove(item);
},
/**
* Registers a new item constructor, keyed by a type key.
* @param {String} type The mnemonic string by which the class may be looked up.
* @param {Function} cls The new instance class.
*/
registerType : function(type, cls) {
this.types[type] = cls;
cls[this.typeName] = type;
},
/**
* Checks if an item type is registered.
* @param {String} type The mnemonic string by which the class may be looked up
* @return {Boolean} Whether the type is registered.
*/
isRegistered : function(type){
return this.types[type] !== undefined;
},
/**
* Creates and returns an instance of whatever this manager manages, based on the supplied type and
* config object.
* @param {Object} config The config object
* @param {String} defaultType If no type is discovered in the config object, we fall back to this type
* @return {Object} The instance of whatever this manager is managing
*/
create: function(config, defaultType) {
var type = config[this.typeName] || config.type || defaultType,
Constructor = this.types[type];
//<debug>
if (Constructor === undefined) {
Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
}
//</debug>
return new Constructor(config);
},
/**
* Registers a function that will be called when an item with the specified id is added to the manager.
* This will happen on instantiation.
* @param {String} id The item id
* @param {Function} fn The callback function. Called with a single parameter, the item.
* @param {Object} scope The scope (this reference) in which the callback is executed.
* Defaults to the item.
*/
onAvailable : function(id, fn, scope){
var all = this.all,
item;
if (all.containsKey(id)) {
item = all.get(id);
fn.call(scope || item, item);
} else {
all.on('add', function(map, key, item){
if (key == id) {
fn.call(scope || item, item);
all.un('add', fn, scope);
}
});
}
},
/**
* Executes the specified function once for each item in the collection.
* @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 collection
* @param {Boolean} fn.return False to cease iteration.
* @param {Object} scope The scope to execute in. Defaults to `this`.
*/
each: function(fn, scope){
this.all.each(fn, scope || this);
},
/**
* Gets the number of items in the collection.
* @return {Number} The number of items in the collection.
*/
getCount: function(){
return this.all.getCount();
}
});

View File

@@ -0,0 +1,86 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
*
* This class defines the essential API of plugins as used by Components by defining the following methods:
*
* - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
*
* The Component passes itself as the sole parameter.
*
* Subclasses should set up bidirectional links between the plugin and its client Component here.
*
* - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
*
* Use this method to break links between the plugin and the Component and to free any allocated resources.
*
* - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
*
* - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
*/
Ext.define('Ext.AbstractPlugin', {
disabled: false,
constructor: function(config) {
//<debug>
if (!config.cmp && Ext.global.console) {
Ext.global.console.warn("Attempted to attach a plugin ");
}
//</debug>
Ext.apply(this, config);
},
getCmp: function() {
return this.cmp;
},
/**
* @method
* The init method is invoked after initComponent method has been run for the client Component.
*
* The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
* links between the plugin and its client Component in their own implementation of this method.
* @param {Ext.Component} client The client Component which owns this plugin.
*/
init: Ext.emptyFn,
/**
* @method
* The destroy method is invoked by the owning Component at the time the Component is being destroyed.
*
* The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
* this method.
*/
destroy: Ext.emptyFn,
/**
* The base implementation just sets the plugin's `disabled` flag to `false`
*
* Plugin subclasses which need more complex processing may implement an overriding implementation.
*/
enable: function() {
this.disabled = false;
},
/**
* The base implementation just sets the plugin's `disabled` flag to `true`
*
* Plugin subclasses which need more complex processing may implement an overriding implementation.
*/
disable: function() {
this.disabled = true;
}
});

283
OfficeWeb/3rdparty/extjs/src/Action.js vendored Normal file
View File

@@ -0,0 +1,283 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Action
* <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
* can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI
* updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
* and {@link Ext.menu.Menu} components).</p>
* <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
* Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
* called at once through a single call to the Action.</p>
* <p>Any Component that is to be configured with an Action must also support
* the following methods:<ul>
* <li><code>setText(string)</code></li>
* <li><code>setIconCls(string)</code></li>
* <li><code>setDisabled(boolean)</code></li>
* <li><code>setVisible(boolean)</code></li>
* <li><code>setHandler(function)</code></li></ul></p>
* <p>This allows the Action to control its associated Components.</p>
* Example usage:<br>
* <pre><code>
// Define the shared Action. Each Component below will have the same
// display text and icon, and will display the same message on click.
var action = new Ext.Action({
{@link #text}: 'Do something',
{@link #handler}: function(){
Ext.Msg.alert('Click', 'You did something.');
},
{@link #iconCls}: 'do-something',
{@link #itemId}: 'myAction'
});
var panel = new Ext.panel.Panel({
title: 'Actions',
width: 500,
height: 300,
tbar: [
// Add the Action directly to a toolbar as a menu button
action,
{
text: 'Action Menu',
// Add the Action to a menu as a text item
menu: [action]
}
],
items: [
// Add the Action to the panel body as a standard button
new Ext.button.Button(action)
],
renderTo: Ext.getBody()
});
// Change the text for all components using the Action
action.setText('Something else');
// Reference an Action through a container using the itemId
var btn = panel.getComponent('myAction');
var aRef = btn.baseAction;
aRef.setText('New text');
</code></pre>
*/
Ext.define('Ext.Action', {
/* Begin Definitions */
/* End Definitions */
/**
* @cfg {String} [text='']
* The text to set for all components configured by this Action.
*/
/**
* @cfg {String} [iconCls='']
* The CSS class selector that specifies a background image to be used as the header icon for
* all components configured by this Action.
* <p>An example of specifying a custom icon class would be something like:
* </p><pre><code>
// specify the property in the config for the class:
...
iconCls: 'do-something'
// css class that specifies background image to be used as the icon image:
.do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
</code></pre>
*/
/**
* @cfg {Boolean} [disabled=false]
* True to disable all components configured by this Action, false to enable them.
*/
/**
* @cfg {Boolean} [hidden=false]
* True to hide all components configured by this Action, false to show them.
*/
/**
* @cfg {Function} handler
* The function that will be invoked by each component tied to this Action
* when the component's primary event is triggered.
*/
/**
* @cfg {String} itemId
* See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
*/
/**
* @cfg {Object} scope
* The scope (this reference) in which the {@link #handler} is executed.
* Defaults to the browser window.
*/
/**
* Creates new Action.
* @param {Object} config Config object.
*/
constructor : function(config){
this.initialConfig = config;
this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
this.items = [];
},
// private
isAction : true,
/**
* Sets the text to be displayed by all components configured by this Action.
* @param {String} text The text to display
*/
setText : function(text){
this.initialConfig.text = text;
this.callEach('setText', [text]);
},
/**
* Gets the text currently displayed by all components configured by this Action.
*/
getText : function(){
return this.initialConfig.text;
},
/**
* Sets the icon CSS class for all components configured by this Action. The class should supply
* a background image that will be used as the icon image.
* @param {String} cls The CSS class supplying the icon image
*/
setIconCls : function(cls){
this.initialConfig.iconCls = cls;
this.callEach('setIconCls', [cls]);
},
/**
* Gets the icon CSS class currently used by all components configured by this Action.
*/
getIconCls : function(){
return this.initialConfig.iconCls;
},
/**
* Sets the disabled state of all components configured by this Action. Shortcut method
* for {@link #enable} and {@link #disable}.
* @param {Boolean} disabled True to disable the component, false to enable it
*/
setDisabled : function(v){
this.initialConfig.disabled = v;
this.callEach('setDisabled', [v]);
},
/**
* Enables all components configured by this Action.
*/
enable : function(){
this.setDisabled(false);
},
/**
* Disables all components configured by this Action.
*/
disable : function(){
this.setDisabled(true);
},
/**
* Returns true if the components using this Action are currently disabled, else returns false.
*/
isDisabled : function(){
return this.initialConfig.disabled;
},
/**
* Sets the hidden state of all components configured by this Action. Shortcut method
* for <code>{@link #hide}</code> and <code>{@link #show}</code>.
* @param {Boolean} hidden True to hide the component, false to show it
*/
setHidden : function(v){
this.initialConfig.hidden = v;
this.callEach('setVisible', [!v]);
},
/**
* Shows all components configured by this Action.
*/
show : function(){
this.setHidden(false);
},
/**
* Hides all components configured by this Action.
*/
hide : function(){
this.setHidden(true);
},
/**
* Returns true if the components configured by this Action are currently hidden, else returns false.
*/
isHidden : function(){
return this.initialConfig.hidden;
},
/**
* Sets the function that will be called by each Component using this action when its primary event is triggered.
* @param {Function} fn The function that will be invoked by the action's components. The function
* will be called with no arguments.
* @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
*/
setHandler : function(fn, scope){
this.initialConfig.handler = fn;
this.initialConfig.scope = scope;
this.callEach('setHandler', [fn, scope]);
},
/**
* Executes the specified function once for each Component currently tied to this Action. The function passed
* in should accept a single argument that will be an object that supports the basic Action config/method interface.
* @param {Function} fn The function to execute for each component
* @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component.
*/
each : function(fn, scope){
Ext.each(this.items, fn, scope);
},
// private
callEach : function(fnName, args){
var items = this.items,
i = 0,
len = items.length;
for(; i < len; i++){
items[i][fnName].apply(items[i], args);
}
},
// private
addComponent : function(comp){
this.items.push(comp);
comp.on('destroy', this.removeComponent, this);
},
// private
removeComponent : function(comp){
Ext.Array.remove(this.items, comp);
},
/**
* Executes this Action manually using the handler function specified in the original config object
* or the handler function set with <code>{@link #setHandler}</code>. Any arguments passed to this
* function will be passed on to the handler function.
* @param {Object...} args (optional) Variable number of arguments passed to the handler function
*/
execute : function(){
this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
}
});

118
OfficeWeb/3rdparty/extjs/src/Ajax.js vendored Normal file
View File

@@ -0,0 +1,118 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Ajax
* @singleton
* @markdown
* @extends Ext.data.Connection
A singleton instance of an {@link Ext.data.Connection}. This class
is used to communicate with your server side code. It can be used as follows:
Ext.Ajax.request({
url: 'page.php',
params: {
id: 1
},
success: function(response){
var text = response.responseText;
// process server response here
}
});
Default options for all requests can be set by changing a property on the Ext.Ajax class:
Ext.Ajax.timeout = 60000; // 60 seconds
Any options specified in the request method for the Ajax request will override any
defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
request will be 60 seconds.
Ext.Ajax.timeout = 120000; // 120 seconds
Ext.Ajax.request({
url: 'page.aspx',
timeout: 60000
});
In general, this class will be used for all Ajax requests in your application.
The main reason for creating a separate {@link Ext.data.Connection} is for a
series of requests that share common settings that are different to all other
requests in the application.
*/
Ext.define('Ext.Ajax', {
extend: 'Ext.data.Connection',
singleton: true,
/**
* @cfg {String} url @hide
*/
/**
* @cfg {Object} extraParams @hide
*/
/**
* @cfg {Object} defaultHeaders @hide
*/
/**
* @cfg {String} method (Optional) @hide
*/
/**
* @cfg {Number} timeout (Optional) @hide
*/
/**
* @cfg {Boolean} autoAbort (Optional) @hide
*/
/**
* @cfg {Boolean} disableCaching (Optional) @hide
*/
/**
* @property {Boolean} disableCaching
* True to add a unique cache-buster param to GET requests. Defaults to true.
*/
/**
* @property {String} url
* The default URL to be used for requests to the server.
* If the server receives all requests through one URL, setting this once is easier than
* entering it on every request.
*/
/**
* @property {Object} extraParams
* An object containing properties which are used as extra parameters to each request made
* by this object. Session information and other data that you need
* to pass with each request are commonly put here.
*/
/**
* @property {Object} defaultHeaders
* An object containing request headers which are added to each request made by this object.
*/
/**
* @property {String} method
* The default HTTP method to be used for requests. Note that this is case-sensitive and
* should be all caps (if not set but params are present will use
* <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
*/
/**
* @property {Number} timeout
* The timeout in milliseconds to be used for requests. Defaults to 30000.
*/
/**
* @property {Boolean} autoAbort
* Whether a new request should abort any pending requests.
*/
autoAbort : false
});

1168
OfficeWeb/3rdparty/extjs/src/Component.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.ComponentLoader
* @extends Ext.ElementLoader
*
* This class is used to load content via Ajax into a {@link Ext.Component}. In general
* this class will not be instanced directly, rather a loader configuration will be passed to the
* constructor of the {@link Ext.Component}.
*
* ## HTML Renderer
* By default, the content loaded will be processed as raw html. The response text
* from the request is taken and added to the component. This can be used in
* conjunction with the {@link #scripts} option to execute any inline scripts in
* the resulting content. Using this renderer has the same effect as passing the
* {@link Ext.Component#html} configuration option.
*
* ## Data Renderer
* This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
* The content received from the response is passed to the {@link Ext.Component#update} method.
* This content is run through the attached {@link Ext.Component#tpl} and the data is added to
* the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
* configuration in conjunction with a {@link Ext.Component#tpl}.
*
* ## Component Renderer
* This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
* Components to be loaded remotely into a Container. The response is expected to be a single/series of
* {@link Ext.Component} configuration objects. When the response is received, the data is decoded
* and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
* the {@link Ext.container.Container#items} configuration on a Container.
*
* ## Custom Renderer
* A custom function can be passed to handle any other special case, see the {@link #renderer} option.
*
* ## Example Usage
* new Ext.Component({
* tpl: '{firstName} - {lastName}',
* loader: {
* url: 'myPage.php',
* renderer: 'data',
* params: {
* userId: 1
* }
* }
* });
*/
Ext.define('Ext.ComponentLoader', {
/* Begin Definitions */
extend: 'Ext.ElementLoader',
statics: {
Renderer: {
Data: function(loader, response, active){
var success = true;
try {
loader.getTarget().update(Ext.decode(response.responseText));
} catch (e) {
success = false;
}
return success;
},
Component: function(loader, response, active){
var success = true,
target = loader.getTarget(),
items = [];
//<debug>
if (!target.isContainer) {
Ext.Error.raise({
target: target,
msg: 'Components can only be loaded into a container'
});
}
//</debug>
try {
items = Ext.decode(response.responseText);
} catch (e) {
success = false;
}
if (success) {
if (active.removeAll) {
target.removeAll();
}
target.add(items);
}
return success;
}
}
},
/* End Definitions */
/**
* @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
* If a string is passed it will be looked up via the id.
*/
target: null,
/**
* @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
*/
loadMask: false,
/**
* @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
* {@link #renderer}.
*/
/**
* @cfg {String/Function} renderer
The type of content that is to be loaded into, which can be one of 3 types:
+ **html** : Loads raw html content, see {@link Ext.Component#html}
+ **data** : Loads raw html content, see {@link Ext.Component#data}
+ **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
Alternatively, you can pass a function which is called with the following parameters.
+ loader - Loader instance
+ response - The server response
+ active - The active request
The function must return false is loading is not successful. Below is a sample of using a custom renderer:
new Ext.Component({
loader: {
url: 'myPage.php',
renderer: function(loader, response, active) {
var text = response.responseText;
loader.getTarget().update('The response is ' + text);
return true;
}
}
});
*/
renderer: 'html',
/**
* Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
* any active requests will be aborted.
* @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
* it will be looked up via its id.
*/
setTarget: function(target){
var me = this;
if (Ext.isString(target)) {
target = Ext.getCmp(target);
}
if (me.target && me.target != target) {
me.abort();
}
me.target = target;
},
// inherit docs
removeMask: function(){
this.target.setLoading(false);
},
/**
* Add the mask on the target
* @private
* @param {Boolean/Object} mask The mask configuration
*/
addMask: function(mask){
this.target.setLoading(mask);
},
/**
* Get the target of this loader.
* @return {Ext.Component} target The target, null if none exists.
*/
setOptions: function(active, options){
active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
},
/**
* Gets the renderer to use
* @private
* @param {String/Function} renderer The renderer to use
* @return {Function} A rendering function to use.
*/
getRenderer: function(renderer){
if (Ext.isFunction(renderer)) {
return renderer;
}
var renderers = this.statics().Renderer;
switch (renderer) {
case 'component':
return renderers.Component;
case 'data':
return renderers.Data;
default:
return Ext.ElementLoader.Renderer.Html;
}
}
});

View File

@@ -0,0 +1,67 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.ComponentManager
* @extends Ext.AbstractManager
* <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
* thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
* {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
* <p>This object also provides a registry of available Component <i>classes</i>
* indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
* The <code>xtype</code> provides a way to avoid instantiating child Components
* when creating a full, nested config object for a complete Ext page.</p>
* <p>A child Component may be specified simply as a <i>config object</i>
* as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
* needs rendering, the correct type can be looked up for lazy instantiation.</p>
* <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
* @singleton
*/
Ext.define('Ext.ComponentManager', {
extend: 'Ext.AbstractManager',
alternateClassName: 'Ext.ComponentMgr',
singleton: true,
typeName: 'xtype',
/**
* Creates a new Component from the specified config object using the
* config object's xtype to determine the class to instantiate.
* @param {Object} config A configuration object for the Component you wish to create.
* @param {Function} defaultType (optional) The constructor to provide the default Component type if
* the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
* @return {Ext.Component} The newly instantiated Component.
*/
create: function(component, defaultType){
if (component instanceof Ext.AbstractComponent) {
return component;
}
else if (Ext.isString(component)) {
return Ext.createByAlias('widget.' + component);
}
else {
var type = component.xtype || defaultType,
config = component;
return Ext.createByAlias('widget.' + type, config);
}
},
registerType: function(type, cls) {
this.types[type] = cls;
cls[this.typeName] = type;
cls.prototype[this.typeName] = type;
}
});

View File

@@ -0,0 +1,530 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Provides searching of Components within Ext.ComponentManager (globally) or a specific
* Ext.container.Container on the document with a similar syntax to a CSS selector.
*
* Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
*
* - `component` or `.component`
* - `gridpanel` or `.gridpanel`
*
* An itemId or id must be prefixed with a #
*
* - `#myContainer`
*
* Attributes must be wrapped in brackets
*
* - `component[autoScroll]`
* - `panel[title="Test"]`
*
* Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
* the candidate Component will be included in the query:
*
* var disabledFields = myFormPanel.query("{isDisabled()}");
*
* Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
*
* // Function receives array and returns a filtered array.
* Ext.ComponentQuery.pseudos.invalid = function(items) {
* var i = 0, l = items.length, c, result = [];
* for (; i < l; i++) {
* if (!(c = items[i]).isValid()) {
* result.push(c);
* }
* }
* return result;
* };
*
* var invalidFields = myFormPanel.query('field:invalid');
* if (invalidFields.length) {
* invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
* for (var i = 0, l = invalidFields.length; i < l; i++) {
* invalidFields[i].getEl().frame("red");
* }
* }
*
* Default pseudos include:
*
* - not
* - last
*
* Queries return an array of components.
* Here are some example queries.
*
* // retrieve all Ext.Panels in the document by xtype
* var panelsArray = Ext.ComponentQuery.query('panel');
*
* // retrieve all Ext.Panels within the container with an id myCt
* var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
*
* // retrieve all direct children which are Ext.Panels within myCt
* var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
*
* // retrieve all grids and trees
* var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
*
* For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
* {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
* {@link Ext.Component#up}.
*/
Ext.define('Ext.ComponentQuery', {
singleton: true,
uses: ['Ext.ComponentManager']
}, function() {
var cq = this,
// A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
// as a member on each item in the passed array.
filterFnPattern = [
'var r = [],',
'i = 0,',
'it = items,',
'l = it.length,',
'c;',
'for (; i < l; i++) {',
'c = it[i];',
'if (c.{0}) {',
'r.push(c);',
'}',
'}',
'return r;'
].join(''),
filterItems = function(items, operation) {
// Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
// The operation's method loops over each item in the candidate array and
// returns an array of items which match its criteria
return operation.method.apply(this, [ items ].concat(operation.args));
},
getItems = function(items, mode) {
var result = [],
i = 0,
length = items.length,
candidate,
deep = mode !== '>';
for (; i < length; i++) {
candidate = items[i];
if (candidate.getRefItems) {
result = result.concat(candidate.getRefItems(deep));
}
}
return result;
},
getAncestors = function(items) {
var result = [],
i = 0,
length = items.length,
candidate;
for (; i < length; i++) {
candidate = items[i];
while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
result.push(candidate);
}
}
return result;
},
// Filters the passed candidate array and returns only items which match the passed xtype
filterByXType = function(items, xtype, shallow) {
if (xtype === '*') {
return items.slice();
}
else {
var result = [],
i = 0,
length = items.length,
candidate;
for (; i < length; i++) {
candidate = items[i];
if (candidate.isXType(xtype, shallow)) {
result.push(candidate);
}
}
return result;
}
},
// Filters the passed candidate array and returns only items which have the passed className
filterByClassName = function(items, className) {
var EA = Ext.Array,
result = [],
i = 0,
length = items.length,
candidate;
for (; i < length; i++) {
candidate = items[i];
if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
result.push(candidate);
}
}
return result;
},
// Filters the passed candidate array and returns only items which have the specified property match
filterByAttribute = function(items, property, operator, value) {
var result = [],
i = 0,
length = items.length,
candidate;
for (; i < length; i++) {
candidate = items[i];
if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
result.push(candidate);
}
}
return result;
},
// Filters the passed candidate array and returns only items which have the specified itemId or id
filterById = function(items, id) {
var result = [],
i = 0,
length = items.length,
candidate;
for (; i < length; i++) {
candidate = items[i];
if (candidate.getItemId() === id) {
result.push(candidate);
}
}
return result;
},
// Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
filterByPseudo = function(items, name, value) {
return cq.pseudos[name](items, value);
},
// Determines leading mode
// > for direct child, and ^ to switch to ownerCt axis
modeRe = /^(\s?([>\^])\s?|\s|$)/,
// Matches a token with possibly (true|false) appended for the "shallow" parameter
tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
matchers = [{
// Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
re: /^\.([\w\-]+)(?:\((true|false)\))?/,
method: filterByXType
},{
// checks for [attribute=value]
re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
method: filterByAttribute
}, {
// checks for #cmpItemId
re: /^#([\w\-]+)/,
method: filterById
}, {
// checks for :<pseudo_class>(<selector>)
re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
method: filterByPseudo
}, {
// checks for {<member_expression>}
re: /^(?:\{([^\}]+)\})/,
method: filterFnPattern
}];
// @class Ext.ComponentQuery.Query
// This internal class is completely hidden in documentation.
cq.Query = Ext.extend(Object, {
constructor: function(cfg) {
cfg = cfg || {};
Ext.apply(this, cfg);
},
// Executes this Query upon the selected root.
// The root provides the initial source of candidate Component matches which are progressively
// filtered by iterating through this Query's operations cache.
// If no root is provided, all registered Components are searched via the ComponentManager.
// root may be a Container who's descendant Components are filtered
// root may be a Component with an implementation of getRefItems which provides some nested Components such as the
// docked items within a Panel.
// root may be an array of candidate Components to filter using this Query.
execute : function(root) {
var operations = this.operations,
i = 0,
length = operations.length,
operation,
workingItems;
// no root, use all Components in the document
if (!root) {
workingItems = Ext.ComponentManager.all.getArray();
}
// Root is a candidate Array
else if (Ext.isArray(root)) {
workingItems = root;
}
// We are going to loop over our operations and take care of them
// one by one.
for (; i < length; i++) {
operation = operations[i];
// The mode operation requires some custom handling.
// All other operations essentially filter down our current
// working items, while mode replaces our current working
// items by getting children from each one of our current
// working items. The type of mode determines the type of
// children we get. (e.g. > only gets direct children)
if (operation.mode === '^') {
workingItems = getAncestors(workingItems || [root]);
}
else if (operation.mode) {
workingItems = getItems(workingItems || [root], operation.mode);
}
else {
workingItems = filterItems(workingItems || getItems([root]), operation);
}
// If this is the last operation, it means our current working
// items are the final matched items. Thus return them!
if (i === length -1) {
return workingItems;
}
}
return [];
},
is: function(component) {
var operations = this.operations,
components = Ext.isArray(component) ? component : [component],
originalLength = components.length,
lastOperation = operations[operations.length-1],
ln, i;
components = filterItems(components, lastOperation);
if (components.length === originalLength) {
if (operations.length > 1) {
for (i = 0, ln = components.length; i < ln; i++) {
if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
return false;
}
}
}
return true;
}
return false;
}
});
Ext.apply(this, {
// private cache of selectors and matching ComponentQuery.Query objects
cache: {},
// private cache of pseudo class filter functions
pseudos: {
not: function(components, selector){
var CQ = Ext.ComponentQuery,
i = 0,
length = components.length,
results = [],
index = -1,
component;
for(; i < length; ++i) {
component = components[i];
if (!CQ.is(component, selector)) {
results[++index] = component;
}
}
return results;
},
last: function(components) {
return components[components.length - 1];
}
},
/**
* Returns an array of matched Components from within the passed root object.
*
* This method filters returned Components in a similar way to how CSS selector based DOM
* queries work using a textual selector string.
*
* See class summary for details.
*
* @param {String} selector The selector string to filter returned Components
* @param {Ext.container.Container} root The Container within which to perform the query.
* If omitted, all Components within the document are included in the search.
*
* This parameter may also be an array of Components to filter according to the selector.</p>
* @returns {Ext.Component[]} The matched Components.
*
* @member Ext.ComponentQuery
*/
query: function(selector, root) {
var selectors = selector.split(','),
length = selectors.length,
i = 0,
results = [],
noDupResults = [],
dupMatcher = {},
query, resultsLn, cmp;
for (; i < length; i++) {
selector = Ext.String.trim(selectors[i]);
query = this.cache[selector];
if (!query) {
this.cache[selector] = query = this.parse(selector);
}
results = results.concat(query.execute(root));
}
// multiple selectors, potential to find duplicates
// lets filter them out.
if (length > 1) {
resultsLn = results.length;
for (i = 0; i < resultsLn; i++) {
cmp = results[i];
if (!dupMatcher[cmp.id]) {
noDupResults.push(cmp);
dupMatcher[cmp.id] = true;
}
}
results = noDupResults;
}
return results;
},
/**
* Tests whether the passed Component matches the selector string.
* @param {Ext.Component} component The Component to test
* @param {String} selector The selector string to test against.
* @return {Boolean} True if the Component matches the selector.
* @member Ext.ComponentQuery
*/
is: function(component, selector) {
if (!selector) {
return true;
}
var query = this.cache[selector];
if (!query) {
this.cache[selector] = query = this.parse(selector);
}
return query.is(component);
},
parse: function(selector) {
var operations = [],
length = matchers.length,
lastSelector,
tokenMatch,
matchedChar,
modeMatch,
selectorMatch,
i, matcher, method;
// We are going to parse the beginning of the selector over and
// over again, slicing off the selector any portions we converted into an
// operation, until it is an empty string.
while (selector && lastSelector !== selector) {
lastSelector = selector;
// First we check if we are dealing with a token like #, * or an xtype
tokenMatch = selector.match(tokenRe);
if (tokenMatch) {
matchedChar = tokenMatch[1];
// If the token is prefixed with a # we push a filterById operation to our stack
if (matchedChar === '#') {
operations.push({
method: filterById,
args: [Ext.String.trim(tokenMatch[2])]
});
}
// If the token is prefixed with a . we push a filterByClassName operation to our stack
// FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
else if (matchedChar === '.') {
operations.push({
method: filterByClassName,
args: [Ext.String.trim(tokenMatch[2])]
});
}
// If the token is a * or an xtype string, we push a filterByXType
// operation to the stack.
else {
operations.push({
method: filterByXType,
args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
});
}
// Now we slice of the part we just converted into an operation
selector = selector.replace(tokenMatch[0], '');
}
// If the next part of the query is not a space or > or ^, it means we
// are going to check for more things that our current selection
// has to comply to.
while (!(modeMatch = selector.match(modeRe))) {
// Lets loop over each type of matcher and execute it
// on our current selector.
for (i = 0; selector && i < length; i++) {
matcher = matchers[i];
selectorMatch = selector.match(matcher.re);
method = matcher.method;
// If we have a match, add an operation with the method
// associated with this matcher, and pass the regular
// expression matches are arguments to the operation.
if (selectorMatch) {
operations.push({
method: Ext.isString(matcher.method)
// Turn a string method into a function by formatting the string with our selector matche expression
// A new method is created for different match expressions, eg {id=='textfield-1024'}
// Every expression may be different in different selectors.
? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
: matcher.method,
args: selectorMatch.slice(1)
});
selector = selector.replace(selectorMatch[0], '');
break; // Break on match
}
//<debug>
// Exhausted all matches: It's an error
if (i === (length - 1)) {
Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
}
//</debug>
}
}
// Now we are going to check for a mode change. This means a space
// or a > to determine if we are going to select all the children
// of the currently matched items, or a ^ if we are going to use the
// ownerCt axis as the candidate source.
if (modeMatch[1]) { // Assignment, and test for truthiness!
operations.push({
mode: modeMatch[2]||modeMatch[1]
});
selector = selector.replace(modeMatch[0], '');
}
}
// Now that we have all our operations in an array, we are going
// to create a new Query using these operations.
return new cq.Query({
operations: operations
});
}
});
});

500
OfficeWeb/3rdparty/extjs/src/Editor.js vendored Normal file
View File

@@ -0,0 +1,500 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Editor
* @extends Ext.Component
*
* <p>
* The Editor class is used to provide inline editing for elements on the page. The editor
* is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
* The editor is a floating Component, when the editor is shown it is automatically aligned to
* display over the top of the bound element it is editing. The Editor contains several options
* for how to handle key presses:
* <ul>
* <li>{@link #completeOnEnter}</li>
* <li>{@link #cancelOnEsc}</li>
* <li>{@link #swallowKeys}</li>
* </ul>
* It also has options for how to use the value once the editor has been activated:
* <ul>
* <li>{@link #revertInvalid}</li>
* <li>{@link #ignoreNoChange}</li>
* <li>{@link #updateEl}</li>
* </ul>
* Sample usage:
* </p>
* <pre><code>
var editor = new Ext.Editor({
updateEl: true, // update the innerHTML of the bound element when editing completes
field: {
xtype: 'textfield'
}
});
var el = Ext.get('my-text'); // The element to 'edit'
editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
* </code></pre>
* {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
*
*/
Ext.define('Ext.Editor', {
/* Begin Definitions */
extend: 'Ext.Component',
alias: 'widget.editor',
requires: ['Ext.layout.component.Editor'],
/* End Definitions */
componentLayout: 'editor',
/**
* @cfg {Ext.form.field.Field} field
* The Field object (or descendant) or config object for field
*/
/**
* @cfg {Boolean} allowBlur
* True to {@link #completeEdit complete the editing process} if in edit mode when the
* field is blurred.
*/
allowBlur: true,
/**
* @cfg {Boolean/Object} autoSize
* True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
* can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
* 'field'. If a dimension is not specified, it will use the underlying height/width specified on
* the editor object.
* Examples:
* <pre><code>
autoSize: true // The editor will be sized to the height/width of the field
height: 21,
autoSize: {
width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
}
autoSize: {
width: 'field', // Width from the field
height: 'boundEl' // Height from the boundEl
}
* </pre></code>
*/
/**
* @cfg {Boolean} revertInvalid
* True to automatically revert the field value and cancel the edit when the user completes an edit and the field
* validation fails
*/
revertInvalid: true,
/**
* @cfg {Boolean} [ignoreNoChange=false]
* True to skip the edit completion process (no save, no events fired) if the user completes an edit and
* the value has not changed. Applies only to string values - edits for other data types
* will never be ignored.
*/
/**
* @cfg {Boolean} [hideEl=true]
* False to keep the bound element visible while the editor is displayed
*/
/**
* @cfg {Object} value
* The data value of the underlying field
*/
value : '',
/**
* @cfg {String} alignment
* The position to align to (see {@link Ext.Element#alignTo} for more details).
*/
alignment: 'c-c?',
/**
* @cfg {Number[]} offsets
* The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
*/
offsets: [0, 0],
/**
* @cfg {Boolean/String} shadow
* "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
*/
shadow : 'frame',
/**
* @cfg {Boolean} constrain
* True to constrain the editor to the viewport
*/
constrain : false,
/**
* @cfg {Boolean} swallowKeys
* Handle the keydown/keypress events so they don't propagate
*/
swallowKeys : true,
/**
* @cfg {Boolean} completeOnEnter
* True to complete the edit when the enter key is pressed.
*/
completeOnEnter : true,
/**
* @cfg {Boolean} cancelOnEsc
* True to cancel the edit when the escape key is pressed.
*/
cancelOnEsc : true,
/**
* @cfg {Boolean} updateEl
* True to update the innerHTML of the bound element when the update completes
*/
updateEl : false,
/**
* @cfg {String/HTMLElement/Ext.Element} parentEl
* An element to render to. Defaults to the <tt>document.body</tt>.
*/
// private overrides
hidden: true,
baseCls: Ext.baseCSSPrefix + 'editor',
initComponent : function() {
var me = this,
field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
Ext.apply(field, {
inEditor: true,
msgTarget: field.msgTarget == 'title' ? 'title' : 'qtip'
});
me.mon(field, {
scope: me,
blur: {
fn: me.onBlur,
// slight delay to avoid race condition with startEdits (e.g. grid view refresh)
delay: 1
},
specialkey: me.onSpecialKey
});
if (field.grow) {
me.mon(field, 'autosize', me.onAutoSize, me, {delay: 1});
}
me.floating = {
constrain: me.constrain
};
me.callParent(arguments);
me.addEvents(
/**
* @event beforestartedit
* Fires when editing is initiated, but before the value changes. Editing can be canceled by returning
* false from the handler of this event.
* @param {Ext.Editor} this
* @param {Ext.Element} boundEl The underlying element bound to this editor
* @param {Object} value The field value being set
*/
'beforestartedit',
/**
* @event startedit
* Fires when this editor is displayed
* @param {Ext.Editor} this
* @param {Ext.Element} boundEl The underlying element bound to this editor
* @param {Object} value The starting field value
*/
'startedit',
/**
* @event beforecomplete
* Fires after a change has been made to the field, but before the change is reflected in the underlying
* field. Saving the change to the field can be canceled by returning false from the handler of this event.
* Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
* event will not fire since no edit actually occurred.
* @param {Ext.Editor} this
* @param {Object} value The current field value
* @param {Object} startValue The original field value
*/
'beforecomplete',
/**
* @event complete
* Fires after editing is complete and any changed value has been written to the underlying field.
* @param {Ext.Editor} this
* @param {Object} value The current field value
* @param {Object} startValue The original field value
*/
'complete',
/**
* @event canceledit
* Fires after editing has been canceled and the editor's value has been reset.
* @param {Ext.Editor} this
* @param {Object} value The user-entered field value that was discarded
* @param {Object} startValue The original field value that was set back into the editor after cancel
*/
'canceledit',
/**
* @event specialkey
* Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
* {@link Ext.EventObject#getKey} to determine which key was pressed.
* @param {Ext.Editor} this
* @param {Ext.form.field.Field} The field attached to this editor
* @param {Ext.EventObject} event The event object
*/
'specialkey'
);
},
// private
onAutoSize: function(){
this.doComponentLayout();
},
// private
onRender : function(ct, position) {
var me = this,
field = me.field,
inputEl = field.inputEl;
me.callParent(arguments);
field.render(me.el);
//field.hide();
// Ensure the field doesn't get submitted as part of any form
if (inputEl) {
inputEl.dom.name = '';
if (me.swallowKeys) {
inputEl.swallowEvent([
'keypress', // *** Opera
'keydown' // *** all other browsers
]);
}
}
},
// private
onSpecialKey : function(field, event) {
var me = this,
key = event.getKey(),
complete = me.completeOnEnter && key == event.ENTER,
cancel = me.cancelOnEsc && key == event.ESC;
if (complete || cancel) {
event.stopEvent();
// Must defer this slightly to prevent exiting edit mode before the field's own
// key nav can handle the enter key, e.g. selecting an item in a combobox list
Ext.defer(function() {
if (complete) {
me.completeEdit();
} else {
me.cancelEdit();
}
if (field.triggerBlur) {
field.triggerBlur();
}
}, 10);
}
this.fireEvent('specialkey', this, field, event);
},
/**
* Starts the editing process and shows the editor.
* @param {String/HTMLElement/Ext.Element} el The element to edit
* @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
* to the innerHTML of el.
*/
startEdit : function(el, value) {
var me = this,
field = me.field;
me.completeEdit();
me.boundEl = Ext.get(el);
value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
if (!me.rendered) {
me.render(me.parentEl || document.body);
}
if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
me.startValue = value;
me.show();
field.reset();
field.setValue(value);
me.realign(true);
field.focus(false, 10);
if (field.autoSize) {
field.autoSize();
}
me.editing = true;
}
},
/**
* Realigns the editor to the bound field based on the current alignment config value.
* @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
*/
realign : function(autoSize) {
var me = this;
if (autoSize === true) {
me.doComponentLayout();
}
me.alignTo(me.boundEl, me.alignment, me.offsets);
},
/**
* Ends the editing process, persists the changed value to the underlying field, and hides the editor.
* @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
*/
completeEdit : function(remainVisible) {
var me = this,
field = me.field,
value;
if (!me.editing) {
return;
}
// Assert combo values first
if (field.assertValue) {
field.assertValue();
}
value = me.getValue();
if (!field.isValid()) {
if (me.revertInvalid !== false) {
me.cancelEdit(remainVisible);
}
return;
}
if (String(value) === String(me.startValue) && me.ignoreNoChange) {
me.hideEdit(remainVisible);
return;
}
if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
// Grab the value again, may have changed in beforecomplete
value = me.getValue();
if (me.updateEl && me.boundEl) {
me.boundEl.update(value);
}
me.hideEdit(remainVisible);
me.fireEvent('complete', me, value, me.startValue);
}
},
// private
onShow : function() {
var me = this;
me.callParent(arguments);
if (me.hideEl !== false) {
me.boundEl.hide();
}
me.fireEvent("startedit", me.boundEl, me.startValue);
},
/**
* Cancels the editing process and hides the editor without persisting any changes. The field value will be
* reverted to the original starting value.
* @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
*/
cancelEdit : function(remainVisible) {
var me = this,
startValue = me.startValue,
value;
if (me.editing) {
value = me.getValue();
me.setValue(startValue);
me.hideEdit(remainVisible);
me.fireEvent('canceledit', me, value, startValue);
}
},
// private
hideEdit: function(remainVisible) {
if (remainVisible !== true) {
this.editing = false;
this.hide();
}
},
// private
onBlur : function() {
var me = this;
// selectSameEditor flag allows the same editor to be started without onBlur firing on itself
if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
me.completeEdit();
}
},
// private
onHide : function() {
var me = this,
field = me.field;
if (me.editing) {
me.completeEdit();
return;
}
field.blur();
if (field.collapse) {
field.collapse();
}
//field.hide();
if (me.hideEl !== false) {
me.boundEl.show();
}
me.callParent(arguments);
},
/**
* Sets the data value of the editor
* @param {Object} value Any valid value supported by the underlying field
*/
setValue : function(value) {
this.field.setValue(value);
},
/**
* Gets the data value of the editor
* @return {Object} The data value
*/
getValue : function() {
return this.field.getValue();
},
beforeDestroy : function() {
var me = this;
Ext.destroy(me.field);
delete me.field;
delete me.parentEl;
delete me.boundEl;
me.callParent(arguments);
}
});

View File

@@ -0,0 +1,414 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* A class used to load remote content to an Element. Sample usage:
*
* Ext.get('el').load({
* url: 'myPage.php',
* scripts: true,
* params: {
* id: 1
* }
* });
*
* In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
* will be used.
*/
Ext.define('Ext.ElementLoader', {
/* Begin Definitions */
mixins: {
observable: 'Ext.util.Observable'
},
uses: [
'Ext.data.Connection',
'Ext.Ajax'
],
statics: {
Renderer: {
Html: function(loader, response, active){
loader.getTarget().update(response.responseText, active.scripts === true);
return true;
}
}
},
/* End Definitions */
/**
* @cfg {String} url
* The url to retrieve the content from.
*/
url: null,
/**
* @cfg {Object} params
* Any params to be attached to the Ajax request. These parameters will
* be overridden by any params in the load options.
*/
params: null,
/**
* @cfg {Object} baseParams Params that will be attached to every request. These parameters
* will not be overridden by any params in the load options.
*/
baseParams: null,
/**
* @cfg {Boolean/Object} autoLoad
* True to have the loader make a request as soon as it is created.
* This argument can also be a set of options that will be passed to {@link #load} is called.
*/
autoLoad: false,
/**
* @cfg {HTMLElement/Ext.Element/String} target
* The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
*/
target: null,
/**
* @cfg {Boolean/String} loadMask
* True or a string to show when the element is loading.
*/
loadMask: false,
/**
* @cfg {Object} ajaxOptions
* Any additional options to be passed to the request, for example timeout or headers.
*/
ajaxOptions: null,
/**
* @cfg {Boolean} scripts
* True to parse any inline script tags in the response.
*/
scripts: false,
/**
* @cfg {Function} success
* A function to be called when a load request is successful.
* Will be called with the following config parameters:
*
* - this - The ElementLoader instance.
* - response - The response object.
* - options - Ajax options.
*/
/**
* @cfg {Function} failure A function to be called when a load request fails.
* Will be called with the following config parameters:
*
* - this - The ElementLoader instance.
* - response - The response object.
* - options - Ajax options.
*/
/**
* @cfg {Function} callback A function to be called when a load request finishes.
* Will be called with the following config parameters:
*
* - this - The ElementLoader instance.
* - success - True if successful request.
* - response - The response object.
* - options - Ajax options.
*/
/**
* @cfg {Object} scope
* The scope to execute the {@link #success} and {@link #failure} functions in.
*/
/**
* @cfg {Function} renderer
* A custom function to render the content to the element. The passed parameters are:
*
* - The loader
* - The response
* - The active request
*/
isLoader: true,
constructor: function(config) {
var me = this,
autoLoad;
config = config || {};
Ext.apply(me, config);
me.setTarget(me.target);
me.addEvents(
/**
* @event beforeload
* Fires before a load request is made to the server.
* Returning false from an event listener can prevent the load
* from occurring.
* @param {Ext.ElementLoader} this
* @param {Object} options The options passed to the request
*/
'beforeload',
/**
* @event exception
* Fires after an unsuccessful load.
* @param {Ext.ElementLoader} this
* @param {Object} response The response from the server
* @param {Object} options The options passed to the request
*/
'exception',
/**
* @event load
* Fires after a successful load.
* @param {Ext.ElementLoader} this
* @param {Object} response The response from the server
* @param {Object} options The options passed to the request
*/
'load'
);
// don't pass config because we have already applied it.
me.mixins.observable.constructor.call(me);
if (me.autoLoad) {
autoLoad = me.autoLoad;
if (autoLoad === true) {
autoLoad = {};
}
me.load(autoLoad);
}
},
/**
* Sets an {@link Ext.Element} as the target of this loader.
* Note that if the target is changed, any active requests will be aborted.
* @param {String/HTMLElement/Ext.Element} target The element or its ID.
*/
setTarget: function(target){
var me = this;
target = Ext.get(target);
if (me.target && me.target != target) {
me.abort();
}
me.target = target;
},
/**
* Returns the target of this loader.
* @return {Ext.Component} The target or null if none exists.
*/
getTarget: function(){
return this.target || null;
},
/**
* Aborts the active load request
*/
abort: function(){
var active = this.active;
if (active !== undefined) {
Ext.Ajax.abort(active.request);
if (active.mask) {
this.removeMask();
}
delete this.active;
}
},
/**
* Removes the mask on the target
* @private
*/
removeMask: function(){
this.target.unmask();
},
/**
* Adds the mask on the target
* @private
* @param {Boolean/Object} mask The mask configuration
*/
addMask: function(mask){
this.target.mask(mask === true ? null : mask);
},
/**
* Loads new data from the server.
* @param {Object} options The options for the request. They can be any configuration option that can be specified for
* the class, with the exception of the target option. Note that any options passed to the method will override any
* class defaults.
*/
load: function(options) {
//<debug>
if (!this.target) {
Ext.Error.raise('A valid target is required when loading content');
}
//</debug>
options = Ext.apply({}, options);
var me = this,
target = me.target,
mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
params = Ext.apply({}, options.params),
ajaxOptions = Ext.apply({}, options.ajaxOptions),
callback = options.callback || me.callback,
scope = options.scope || me.scope || me,
request;
Ext.applyIf(ajaxOptions, me.ajaxOptions);
Ext.applyIf(options, ajaxOptions);
Ext.applyIf(params, me.params);
Ext.apply(params, me.baseParams);
Ext.applyIf(options, {
url: me.url
});
//<debug>
if (!options.url) {
Ext.Error.raise('You must specify the URL from which content should be loaded');
}
//</debug>
Ext.apply(options, {
scope: me,
params: params,
callback: me.onComplete
});
if (me.fireEvent('beforeload', me, options) === false) {
return;
}
if (mask) {
me.addMask(mask);
}
request = Ext.Ajax.request(options);
me.active = {
request: request,
options: options,
mask: mask,
scope: scope,
callback: callback,
success: options.success || me.success,
failure: options.failure || me.failure,
renderer: options.renderer || me.renderer,
scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
};
me.setOptions(me.active, options);
},
/**
* Sets any additional options on the active request
* @private
* @param {Object} active The active request
* @param {Object} options The initial options
*/
setOptions: Ext.emptyFn,
/**
* Parses the response after the request completes
* @private
* @param {Object} options Ajax options
* @param {Boolean} success Success status of the request
* @param {Object} response The response object
*/
onComplete: function(options, success, response) {
var me = this,
active = me.active,
scope = active.scope,
renderer = me.getRenderer(active.renderer);
if (success) {
success = renderer.call(me, me, response, active);
}
if (success) {
Ext.callback(active.success, scope, [me, response, options]);
me.fireEvent('load', me, response, options);
} else {
Ext.callback(active.failure, scope, [me, response, options]);
me.fireEvent('exception', me, response, options);
}
Ext.callback(active.callback, scope, [me, success, response, options]);
if (active.mask) {
me.removeMask();
}
delete me.active;
},
/**
* Gets the renderer to use
* @private
* @param {String/Function} renderer The renderer to use
* @return {Function} A rendering function to use.
*/
getRenderer: function(renderer){
if (Ext.isFunction(renderer)) {
return renderer;
}
return this.statics().Renderer.Html;
},
/**
* Automatically refreshes the content over a specified period.
* @param {Number} interval The interval to refresh in ms.
* @param {Object} options (optional) The options to pass to the load method. See {@link #load}
*/
startAutoRefresh: function(interval, options){
var me = this;
me.stopAutoRefresh();
me.autoRefresh = setInterval(function(){
me.load(options);
}, interval);
},
/**
* Clears any auto refresh. See {@link #startAutoRefresh}.
*/
stopAutoRefresh: function(){
clearInterval(this.autoRefresh);
delete this.autoRefresh;
},
/**
* Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
* @return {Boolean} True if the loader is automatically refreshing
*/
isAutoRefreshing: function(){
return Ext.isDefined(this.autoRefresh);
},
/**
* Destroys the loader. Any active requests will be aborted.
*/
destroy: function(){
var me = this;
me.stopAutoRefresh();
delete me.target;
me.abort();
me.clearListeners();
}
});

View File

@@ -0,0 +1,868 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.FocusManager
The FocusManager is responsible for globally:
1. Managing component focus
2. Providing basic keyboard navigation
3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();. The
FocusManager is disabled by default.
To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
{@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
* @singleton
* @author Jarred Nicholls <jarred@sencha.com>
* @docauthor Jarred Nicholls <jarred@sencha.com>
*/
Ext.define('Ext.FocusManager', {
singleton: true,
alternateClassName: 'Ext.FocusMgr',
mixins: {
observable: 'Ext.util.Observable'
},
requires: [
'Ext.ComponentManager',
'Ext.ComponentQuery',
'Ext.util.HashMap',
'Ext.util.KeyNav'
],
/**
* @property {Boolean} enabled
* Whether or not the FocusManager is currently enabled
*/
enabled: false,
/**
* @property {Ext.Component} focusedCmp
* The currently focused component. Defaults to `undefined`.
*/
focusElementCls: Ext.baseCSSPrefix + 'focus-element',
focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
/**
* @property {String[]} whitelist
* A list of xtypes that should ignore certain navigation input keys and
* allow for the default browser event/behavior. These input keys include:
*
* 1. Backspace
* 2. Delete
* 3. Left
* 4. Right
* 5. Up
* 6. Down
*
* The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
* that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
* the user to move the input cursor left and right, and to delete characters, etc.
*/
whitelist: [
'textfield'
],
tabIndexWhitelist: [
'a',
'button',
'embed',
'frame',
'iframe',
'img',
'input',
'object',
'select',
'textarea'
],
constructor: function() {
var me = this,
CQ = Ext.ComponentQuery;
me.addEvents(
/**
* @event beforecomponentfocus
* Fires before a component becomes focused. Return `false` to prevent
* the component from gaining focus.
* @param {Ext.FocusManager} fm A reference to the FocusManager singleton
* @param {Ext.Component} cmp The component that is being focused
* @param {Ext.Component} previousCmp The component that was previously focused,
* or `undefined` if there was no previously focused component.
*/
'beforecomponentfocus',
/**
* @event componentfocus
* Fires after a component becomes focused.
* @param {Ext.FocusManager} fm A reference to the FocusManager singleton
* @param {Ext.Component} cmp The component that has been focused
* @param {Ext.Component} previousCmp The component that was previously focused,
* or `undefined` if there was no previously focused component.
*/
'componentfocus',
/**
* @event disable
* Fires when the FocusManager is disabled
* @param {Ext.FocusManager} fm A reference to the FocusManager singleton
*/
'disable',
/**
* @event enable
* Fires when the FocusManager is enabled
* @param {Ext.FocusManager} fm A reference to the FocusManager singleton
*/
'enable'
);
// Setup KeyNav that's bound to document to catch all
// unhandled/bubbled key events for navigation
me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
disabled: true,
scope: me,
backspace: me.focusLast,
enter: me.navigateIn,
esc: me.navigateOut,
tab: me.navigateSiblings
//space: me.navigateIn,
//del: me.focusLast,
//left: me.navigateSiblings,
//right: me.navigateSiblings,
//down: me.navigateSiblings,
//up: me.navigateSiblings
});
me.focusData = {};
me.subscribers = Ext.create('Ext.util.HashMap');
me.focusChain = {};
// Setup some ComponentQuery pseudos
Ext.apply(CQ.pseudos, {
focusable: function(cmps) {
var len = cmps.length,
results = [],
i = 0,
c,
isFocusable = function(x) {
return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
};
for (; i < len; i++) {
c = cmps[i];
if (isFocusable(c)) {
results.push(c);
}
}
return results;
},
nextFocus: function(cmps, idx, step) {
step = step || 1;
idx = parseInt(idx, 10);
var len = cmps.length,
i = idx + step,
c;
for (; i != idx; i += step) {
if (i >= len) {
i = 0;
} else if (i < 0) {
i = len - 1;
}
c = cmps[i];
if (CQ.is(c, ':focusable')) {
return [c];
} else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
return [c.placeholder];
}
}
return [];
},
prevFocus: function(cmps, idx) {
return this.nextFocus(cmps, idx, -1);
},
root: function(cmps) {
var len = cmps.length,
results = [],
i = 0,
c;
for (; i < len; i++) {
c = cmps[i];
if (!c.ownerCt) {
results.push(c);
}
}
return results;
}
});
},
/**
* Adds the specified xtype to the {@link #whitelist}.
* @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
*/
addXTypeToWhitelist: function(xtype) {
var me = this;
if (Ext.isArray(xtype)) {
Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
return;
}
if (!Ext.Array.contains(me.whitelist, xtype)) {
me.whitelist.push(xtype);
}
},
clearComponent: function(cmp) {
clearTimeout(this.cmpFocusDelay);
if (!cmp.isDestroyed) {
cmp.blur();
}
},
/**
* Disables the FocusManager by turning of all automatic focus management and keyboard navigation
*/
disable: function() {
var me = this;
if (!me.enabled) {
return;
}
delete me.options;
me.enabled = false;
Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
me.removeDOM();
// Stop handling key navigation
me.keyNav.disable();
// disable focus for all components
me.setFocusAll(false);
me.fireEvent('disable', me);
},
/**
* Enables the FocusManager by turning on all automatic focus management and keyboard navigation
* @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
- focusFrame : Boolean
`true` to show the focus frame around a component when it is focused. Defaults to `false`.
* @markdown
*/
enable: function(options) {
var me = this;
if (options === true) {
options = { focusFrame: true };
}
me.options = options = options || {};
if (me.enabled) {
return;
}
// Handle components that are newly added after we are enabled
Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
me.initDOM(options);
// Start handling key navigation
me.keyNav.enable();
// enable focus for all components
me.setFocusAll(true, options);
// Finally, let's focus our global focus el so we start fresh
me.focusEl.focus();
delete me.focusedCmp;
me.enabled = true;
me.fireEvent('enable', me);
},
focusLast: function(e) {
var me = this;
if (me.isWhitelisted(me.focusedCmp)) {
return true;
}
// Go back to last focused item
if (me.previousFocusedCmp) {
me.previousFocusedCmp.focus();
}
},
getRootComponents: function() {
var me = this,
CQ = Ext.ComponentQuery,
inline = CQ.query(':focusable:root:not([floating])'),
floating = CQ.query(':focusable:root[floating]');
// Floating items should go to the top of our root stack, and be ordered
// by their z-index (highest first)
floating.sort(function(a, b) {
return a.el.getZIndex() > b.el.getZIndex();
});
return floating.concat(inline);
},
initDOM: function(options) {
var me = this,
sp = '&#160',
cls = me.focusFrameCls;
if (!Ext.isReady) {
Ext.onReady(me.initDOM, me);
return;
}
// Create global focus element
if (!me.focusEl) {
me.focusEl = Ext.getBody().createChild({
tabIndex: '-1',
cls: me.focusElementCls,
html: sp
});
}
// Create global focus frame
if (!me.focusFrame && options.focusFrame) {
me.focusFrame = Ext.getBody().createChild({
cls: cls,
children: [
{ cls: cls + '-top' },
{ cls: cls + '-bottom' },
{ cls: cls + '-left' },
{ cls: cls + '-right' }
],
style: 'top: -100px; left: -100px;'
});
me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
me.focusFrameWidth = 2;
me.focusFrame.hide().setLeftTop(0, 0);
}
},
isWhitelisted: function(cmp) {
return cmp && Ext.Array.some(this.whitelist, function(x) {
return cmp.isXType(x);
});
},
navigateIn: function(e) {
var me = this,
focusedCmp = me.focusedCmp,
rootCmps,
firstChild;
if (!focusedCmp) {
// No focus yet, so focus the first root cmp on the page
rootCmps = me.getRootComponents();
if (rootCmps.length) {
rootCmps[0].focus();
}
} else {
// Drill into child ref items of the focused cmp, if applicable.
// This works for any Component with a getRefItems implementation.
firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
if (firstChild) {
firstChild.focus();
} else {
// Let's try to fire a click event, as if it came from the mouse
if (Ext.isFunction(focusedCmp.onClick)) {
e.button = 0;
focusedCmp.onClick(e);
focusedCmp.focus();
}
}
}
},
navigateOut: function(e) {
var me = this,
parent;
if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
me.focusEl.focus();
} else {
parent.focus();
}
// In some browsers (Chrome) FocusManager can handle this before other
// handlers. Ext Windows have their own Esc key handling, so we need to
// return true here to allow the event to bubble.
return true;
},
navigateSiblings: function(e, source, parent) {
var me = this,
src = source || me,
key = e.getKey(),
EO = Ext.EventObject,
goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
nextSelector = goBack ? 'prev' : 'next',
idx, next, focusedCmp;
focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
if (!focusedCmp && !parent) {
return;
}
if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
return true;
}
parent = parent || focusedCmp.up();
if (parent) {
idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
if (next && focusedCmp !== next) {
next.focus();
return next;
}
}
},
onComponentBlur: function(cmp, e) {
var me = this;
if (me.focusedCmp === cmp) {
me.previousFocusedCmp = cmp;
delete me.focusedCmp;
}
if (me.focusFrame) {
me.focusFrame.hide();
}
},
onComponentCreated: function(hash, id, cmp) {
this.setFocus(cmp, true, this.options);
},
onComponentDestroy: function(cmp) {
this.setFocus(cmp, false);
},
onComponentFocus: function(cmp, e) {
var me = this,
chain = me.focusChain;
if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
me.clearComponent(cmp);
// Check our focus chain, so we don't run into a never ending recursion
// If we've attempted (unsuccessfully) to focus this component before,
// then we're caught in a loop of child->parent->...->child and we
// need to cut the loop off rather than feed into it.
if (chain[cmp.id]) {
return;
}
// Try to focus the parent instead
var parent = cmp.up();
if (parent) {
// Add component to our focus chain to detect infinite focus loop
// before we fire off an attempt to focus our parent.
// See the comments above.
chain[cmp.id] = true;
parent.focus();
}
return;
}
// Clear our focus chain when we have a focusable component
me.focusChain = {};
// Defer focusing for 90ms so components can do a layout/positioning
// and give us an ability to buffer focuses
clearTimeout(me.cmpFocusDelay);
if (arguments.length !== 2) {
me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
return;
}
if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
me.clearComponent(cmp);
return;
}
me.focusedCmp = cmp;
// If we have a focus frame, show it around the focused component
if (me.shouldShowFocusFrame(cmp)) {
var cls = '.' + me.focusFrameCls + '-',
ff = me.focusFrame,
fw = me.focusFrameWidth,
box = cmp.el.getPageBox(),
// Size the focus frame's t/b/l/r according to the box
// This leaves a hole in the middle of the frame so user
// interaction w/ the mouse can continue
bt = box.top,
bl = box.left,
bw = box.width,
bh = box.height,
ft = ff.child(cls + 'top'),
fb = ff.child(cls + 'bottom'),
fl = ff.child(cls + 'left'),
fr = ff.child(cls + 'right');
ft.setWidth(bw).setLeftTop(bl, bt);
fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
ff.show();
}
me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
},
onComponentHide: function(cmp) {
var me = this,
CQ = Ext.ComponentQuery,
cmpHadFocus = false,
focusedCmp,
parent;
if (me.focusedCmp) {
focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
if (focusedCmp) {
me.clearComponent(focusedCmp);
}
}
me.clearComponent(cmp);
if (cmpHadFocus) {
parent = CQ.query('^:focusable', cmp)[0];
if (parent) {
parent.focus();
}
}
},
removeDOM: function() {
var me = this;
// If we are still enabled globally, or there are still subscribers
// then we will halt here, since our DOM stuff is still being used
if (me.enabled || me.subscribers.length) {
return;
}
Ext.destroy(
me.focusEl,
me.focusFrame
);
delete me.focusEl;
delete me.focusFrame;
delete me.focusFrameWidth;
},
/**
* Removes the specified xtype from the {@link #whitelist}.
* @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
*/
removeXTypeFromWhitelist: function(xtype) {
var me = this;
if (Ext.isArray(xtype)) {
Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
return;
}
Ext.Array.remove(me.whitelist, xtype);
},
setFocus: function(cmp, focusable, options) {
var me = this,
el, dom, data,
needsTabIndex = function(n) {
return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
&& n.tabIndex <= 0;
};
options = options || {};
// Come back and do this after the component is rendered
if (!cmp.rendered) {
cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
return;
}
el = cmp.getFocusEl();
dom = el.dom;
// Decorate the component's focus el for focus-ability
if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
if (focusable) {
data = {
focusFrame: options.focusFrame
};
// Only set -1 tabIndex if we need it
// inputs, buttons, and anchor tags do not need it,
// and neither does any DOM that has it set already
// programmatically or in markup.
if (needsTabIndex(dom)) {
data.tabIndex = dom.tabIndex;
dom.tabIndex = -1;
}
el.on({
focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
scope: me
});
cmp.on({
hide: me.onComponentHide,
close: me.onComponentHide,
beforedestroy: me.onComponentDestroy,
scope: me
});
me.focusData[cmp.id] = data;
} else {
data = me.focusData[cmp.id];
if ('tabIndex' in data) {
dom.tabIndex = data.tabIndex;
}
el.un('focus', data.focusFn, me);
el.un('blur', data.blurFn, me);
cmp.un('hide', me.onComponentHide, me);
cmp.un('close', me.onComponentHide, me);
cmp.un('beforedestroy', me.onComponentDestroy, me);
delete me.focusData[cmp.id];
}
}
},
setFocusAll: function(focusable, options) {
var me = this,
cmps = Ext.ComponentManager.all.getArray(),
len = cmps.length,
cmp,
i = 0;
for (; i < len; i++) {
me.setFocus(cmps[i], focusable, options);
}
},
setupSubscriberKeys: function(container, keys) {
var me = this,
el = container.getFocusEl(),
scope = keys.scope,
handlers = {
backspace: me.focusLast,
enter: me.navigateIn,
esc: me.navigateOut,
scope: me
},
navSiblings = function(e) {
if (me.focusedCmp === container) {
// Root the sibling navigation to this container, so that we
// can automatically dive into the container, rather than forcing
// the user to hit the enter key to dive in.
return me.navigateSiblings(e, me, container);
} else {
return me.navigateSiblings(e);
}
};
Ext.iterate(keys, function(key, cb) {
handlers[key] = function(e) {
var ret = navSiblings(e);
if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
return true;
}
return ret;
};
}, me);
return Ext.create('Ext.util.KeyNav', el, handlers);
},
shouldShowFocusFrame: function(cmp) {
var me = this,
opts = me.options || {};
if (!me.focusFrame || !cmp) {
return false;
}
// Global trumps
if (opts.focusFrame) {
return true;
}
if (me.focusData[cmp.id].focusFrame) {
return true;
}
return false;
},
/**
* Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
* @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
* @param {Boolean/Object} options An object of the following options
* @param {Array/Object} options.keys
* An array containing the string names of navigation keys to be supported. The allowed values are:
*
* - 'left'
* - 'right'
* - 'up'
* - 'down'
*
* Or, an object containing those key names as keys with `true` or a callback function as their value. A scope may also be passed. E.g.:
*
* {
* left: this.onLeftKey,
* right: this.onRightKey,
* scope: this
* }
*
* @param {Boolean} options.focusFrame
* `true` to show the focus frame around a component when it is focused. Defaults to `false`.
*/
subscribe: function(container, options) {
var me = this,
EA = Ext.Array,
data = {},
subs = me.subscribers,
// Recursively add focus ability as long as a descendent container isn't
// itself subscribed to the FocusManager, or else we'd have unwanted side
// effects for subscribing a descendent container twice.
safeSetFocus = function(cmp) {
if (cmp.isContainer && !subs.containsKey(cmp.id)) {
EA.forEach(cmp.query('>'), safeSetFocus);
me.setFocus(cmp, true, options);
cmp.on('add', data.onAdd, me);
} else if (!cmp.isContainer) {
me.setFocus(cmp, true, options);
}
};
// We only accept containers
if (!container || !container.isContainer) {
return;
}
if (!container.rendered) {
container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
return;
}
// Init the DOM, incase this is the first time it will be used
me.initDOM(options);
// Create key navigation for subscriber based on keys option
data.keyNav = me.setupSubscriberKeys(container, options.keys);
// We need to keep track of components being added to our subscriber
// and any containers nested deeply within it (omg), so let's do that.
// Components that are removed are globally handled.
// Also keep track of destruction of our container for auto-unsubscribe.
data.onAdd = function(ct, cmp, idx) {
safeSetFocus(cmp);
};
container.on('beforedestroy', me.unsubscribe, me);
// Now we setup focusing abilities for the container and all its components
safeSetFocus(container);
// Add to our subscribers list
subs.add(container.id, data);
},
/**
* Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
* @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
*/
unsubscribe: function(container) {
var me = this,
EA = Ext.Array,
subs = me.subscribers,
data,
// Recursively remove focus ability as long as a descendent container isn't
// itself subscribed to the FocusManager, or else we'd have unwanted side
// effects for unsubscribing an ancestor container.
safeSetFocus = function(cmp) {
if (cmp.isContainer && !subs.containsKey(cmp.id)) {
EA.forEach(cmp.query('>'), safeSetFocus);
me.setFocus(cmp, false);
cmp.un('add', data.onAdd, me);
} else if (!cmp.isContainer) {
me.setFocus(cmp, false);
}
};
if (!container || !subs.containsKey(container.id)) {
return;
}
data = subs.get(container.id);
data.keyNav.destroy();
container.un('beforedestroy', me.unsubscribe, me);
subs.removeAtKey(container.id);
safeSetFocus(container);
me.removeDOM();
}
});

62
OfficeWeb/3rdparty/extjs/src/Img.js vendored Normal file
View File

@@ -0,0 +1,62 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Img
* @extends Ext.Component
*
* Simple helper class for easily creating image components. This simply renders an image tag to the DOM
* with the configured src.
*
* {@img Ext.Img/Ext.Img.png Ext.Img component}
*
* ## Example usage:
*
* var changingImage = Ext.create('Ext.Img', {
* src: 'http://www.sencha.com/img/20110215-feat-html5.png',
* renderTo: Ext.getBody()
* });
*
* // change the src of the image programmatically
* changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
*/
Ext.define('Ext.Img', {
extend: 'Ext.Component',
alias: ['widget.image', 'widget.imagecomponent'],
/** @cfg {String} src The image src */
src: '',
getElConfig: function() {
return {
tag: 'img',
src: this.src
};
},
// null out this function, we can't set any html inside the image
initRenderTpl: Ext.emptyFn,
/**
* Updates the {@link #src} of the image
*/
setSrc: function(src) {
var me = this,
img = me.el;
me.src = src;
if (img) {
img.dom.src = src;
}
}
});

531
OfficeWeb/3rdparty/extjs/src/Layer.js vendored Normal file
View File

@@ -0,0 +1,531 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Layer
* @extends Ext.Element
* An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
* automatic maintaining of shadow/shim positions.
*
* @cfg {Boolean} [shim=true]
* False to disable the iframe shim in browsers which need one.
*
* @cfg {String/Boolean} [shadow=false]
* True to automatically create an {@link Ext.Shadow}, or a string indicating the
* shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
*
* @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
* DomHelper object config to create element with.
*
* @cfg {Boolean} [constrain=true]
* False to disable constrain to viewport.
*
* @cfg {String} cls
* CSS class to add to the element
*
* @cfg {Number} [zindex=11000]
* Starting z-index.
*
* @cfg {Number} [shadowOffset=4]
* Number of pixels to offset the shadow
*
* @cfg {Boolean} [useDisplay=false]
* Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
* to use css style <tt>'display:none;'</tt> to hide the Layer.
*
* @cfg {String} visibilityCls
* The CSS class name to add in order to hide this Layer if this layer
* is configured with <code>{@link #hideMode}: 'asclass'</code>
*
* @cfg {String} hideMode
* A String which specifies how this Layer will be hidden.
* Values may be<div class="mdetail-params"><ul>
* <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
* <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
* <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
* is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
* in a Component having zero dimensions.</li></ul></div>
*/
Ext.define('Ext.Layer', {
uses: ['Ext.Shadow'],
// shims are shared among layer to keep from having 100 iframes
statics: {
shims: []
},
extend: 'Ext.Element',
/**
* Creates new Layer.
* @param {Object} config (optional) An object with config options.
* @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
* If the element is not found it creates it.
*/
constructor: function(config, existingEl) {
config = config || {};
var me = this,
dh = Ext.DomHelper,
cp = config.parentEl,
pel = cp ? Ext.getDom(cp) : document.body,
hm = config.hideMode;
if (existingEl) {
me.dom = Ext.getDom(existingEl);
}
if (!me.dom) {
me.dom = dh.append(pel, config.dh || {
tag: 'div',
cls: Ext.baseCSSPrefix + 'layer'
});
} else {
me.addCls(Ext.baseCSSPrefix + 'layer');
if (!me.dom.parentNode) {
pel.appendChild(me.dom);
}
}
if (config.cls) {
me.addCls(config.cls);
}
me.constrain = config.constrain !== false;
// Allow Components to pass their hide mode down to the Layer if they are floating.
// Otherwise, allow useDisplay to override the default hiding method which is visibility.
// TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
if (hm) {
me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
if (me.visibilityMode == Ext.Element.ASCLASS) {
me.visibilityCls = config.visibilityCls;
}
} else if (config.useDisplay) {
me.setVisibilityMode(Ext.Element.DISPLAY);
} else {
me.setVisibilityMode(Ext.Element.VISIBILITY);
}
if (config.id) {
me.id = me.dom.id = config.id;
} else {
me.id = Ext.id(me.dom);
}
me.position('absolute');
if (config.shadow) {
me.shadowOffset = config.shadowOffset || 4;
me.shadow = Ext.create('Ext.Shadow', {
offset: me.shadowOffset,
mode: config.shadow
});
me.disableShadow();
} else {
me.shadowOffset = 0;
}
me.useShim = config.shim !== false && Ext.useShims;
if (config.hidden === true) {
me.hide();
} else {
me.show();
}
},
getZIndex: function() {
return parseInt((this.getShim() || this).getStyle('z-index'), 10);
},
getShim: function() {
var me = this,
shim, pn;
if (!me.useShim) {
return null;
}
if (!me.shim) {
shim = me.self.shims.shift();
if (!shim) {
shim = me.createShim();
shim.enableDisplayMode('block');
shim.hide();
}
pn = me.dom.parentNode;
if (shim.dom.parentNode != pn) {
pn.insertBefore(shim.dom, me.dom);
}
me.shim = shim;
}
return me.shim;
},
hideShim: function() {
var me = this;
if (me.shim) {
me.shim.setDisplayed(false);
me.self.shims.push(me.shim);
delete me.shim;
}
},
disableShadow: function() {
var me = this;
if (me.shadow && !me.shadowDisabled) {
me.shadowDisabled = true;
me.shadow.hide();
me.lastShadowOffset = me.shadowOffset;
me.shadowOffset = 0;
}
},
enableShadow: function(show) {
var me = this;
if (me.shadow && me.shadowDisabled) {
me.shadowDisabled = false;
me.shadowOffset = me.lastShadowOffset;
delete me.lastShadowOffset;
if (show) {
me.sync(true);
}
}
},
/**
* @private
* <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
* <p>This code can execute repeatedly in milliseconds,
* eg: dragging a Component configured liveDrag: true, or which has no ghost method
* so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
* @param {Boolean} doShow Pass true to ensure that the shadow is shown.
*/
sync: function(doShow) {
var me = this,
shadow = me.shadow,
shadowPos, shimStyle, shadowSize;
if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
var shim = me.getShim(),
l = me.getLeft(true),
t = me.getTop(true),
w = me.dom.offsetWidth,
h = me.dom.offsetHeight,
shimIndex;
if (shadow && !me.shadowDisabled) {
if (doShow && !shadow.isVisible()) {
shadow.show(me);
} else {
shadow.realign(l, t, w, h);
}
if (shim) {
// TODO: Determine how the shims zIndex is above the layer zIndex at this point
shimIndex = shim.getStyle('z-index');
if (shimIndex > me.zindex) {
me.shim.setStyle('z-index', me.zindex - 2);
}
shim.show();
// fit the shim behind the shadow, so it is shimmed too
if (shadow.isVisible()) {
shadowPos = shadow.el.getXY();
shimStyle = shim.dom.style;
shadowSize = shadow.el.getSize();
if (Ext.supports.CSS3BoxShadow) {
shadowSize.height += 6;
shadowSize.width += 4;
shadowPos[0] -= 2;
shadowPos[1] -= 4;
}
shimStyle.left = (shadowPos[0]) + 'px';
shimStyle.top = (shadowPos[1]) + 'px';
shimStyle.width = (shadowSize.width) + 'px';
shimStyle.height = (shadowSize.height) + 'px';
} else {
shim.setSize(w, h);
shim.setLeftTop(l, t);
}
}
} else if (shim) {
// TODO: Determine how the shims zIndex is above the layer zIndex at this point
shimIndex = shim.getStyle('z-index');
if (shimIndex > me.zindex) {
me.shim.setStyle('z-index', me.zindex - 2);
}
shim.show();
shim.setSize(w, h);
shim.setLeftTop(l, t);
}
}
return me;
},
remove: function() {
this.hideUnders();
this.callParent();
},
// private
beginUpdate: function() {
this.updating = true;
},
// private
endUpdate: function() {
this.updating = false;
this.sync(true);
},
// private
hideUnders: function() {
if (this.shadow) {
this.shadow.hide();
}
this.hideShim();
},
// private
constrainXY: function() {
if (this.constrain) {
var vw = Ext.Element.getViewWidth(),
vh = Ext.Element.getViewHeight(),
s = Ext.getDoc().getScroll(),
xy = this.getXY(),
x = xy[0],
y = xy[1],
so = this.shadowOffset,
w = this.dom.offsetWidth + so,
h = this.dom.offsetHeight + so,
moved = false; // only move it if it needs it
// first validate right/bottom
if ((x + w) > vw + s.left) {
x = vw - w - so;
moved = true;
}
if ((y + h) > vh + s.top) {
y = vh - h - so;
moved = true;
}
// then make sure top/left isn't negative
if (x < s.left) {
x = s.left;
moved = true;
}
if (y < s.top) {
y = s.top;
moved = true;
}
if (moved) {
Ext.Layer.superclass.setXY.call(this, [x, y]);
this.sync();
}
}
return this;
},
getConstrainOffset: function() {
return this.shadowOffset;
},
// overridden Element method
setVisible: function(visible, animate, duration, callback, easing) {
var me = this,
cb;
// post operation processing
cb = function() {
if (visible) {
me.sync(true);
}
if (callback) {
callback();
}
};
// Hide shadow and shim if hiding
if (!visible) {
me.hideUnders(true);
}
me.callParent([visible, animate, duration, callback, easing]);
if (!animate) {
cb();
}
return me;
},
// private
beforeFx: function() {
this.beforeAction();
return this.callParent(arguments);
},
// private
afterFx: function() {
this.callParent(arguments);
this.sync(this.isVisible());
},
// private
beforeAction: function() {
if (!this.updating && this.shadow) {
this.shadow.hide();
}
},
// overridden Element method
setLeft: function(left) {
this.callParent(arguments);
return this.sync();
},
setTop: function(top) {
this.callParent(arguments);
return this.sync();
},
setLeftTop: function(left, top) {
this.callParent(arguments);
return this.sync();
},
setXY: function(xy, animate, duration, callback, easing) {
var me = this;
// Callback will restore shadow state and call the passed callback
callback = me.createCB(callback);
me.fixDisplay();
me.beforeAction();
me.callParent([xy, animate, duration, callback, easing]);
if (!animate) {
callback();
}
return me;
},
// private
createCB: function(callback) {
var me = this,
showShadow = me.shadow && me.shadow.isVisible();
return function() {
me.constrainXY();
me.sync(showShadow);
if (callback) {
callback();
}
};
},
// overridden Element method
setX: function(x, animate, duration, callback, easing) {
this.setXY([x, this.getY()], animate, duration, callback, easing);
return this;
},
// overridden Element method
setY: function(y, animate, duration, callback, easing) {
this.setXY([this.getX(), y], animate, duration, callback, easing);
return this;
},
// overridden Element method
setSize: function(w, h, animate, duration, callback, easing) {
var me = this;
// Callback will restore shadow state and call the passed callback
callback = me.createCB(callback);
me.beforeAction();
me.callParent([w, h, animate, duration, callback, easing]);
if (!animate) {
callback();
}
return me;
},
// overridden Element method
setWidth: function(w, animate, duration, callback, easing) {
var me = this;
// Callback will restore shadow state and call the passed callback
callback = me.createCB(callback);
me.beforeAction();
me.callParent([w, animate, duration, callback, easing]);
if (!animate) {
callback();
}
return me;
},
// overridden Element method
setHeight: function(h, animate, duration, callback, easing) {
var me = this;
// Callback will restore shadow state and call the passed callback
callback = me.createCB(callback);
me.beforeAction();
me.callParent([h, animate, duration, callback, easing]);
if (!animate) {
callback();
}
return me;
},
// overridden Element method
setBounds: function(x, y, width, height, animate, duration, callback, easing) {
var me = this;
// Callback will restore shadow state and call the passed callback
callback = me.createCB(callback);
me.beforeAction();
if (!animate) {
Ext.Layer.superclass.setXY.call(me, [x, y]);
Ext.Layer.superclass.setSize.call(me, width, height);
callback();
} else {
me.callParent([x, y, width, height, animate, duration, callback, easing]);
}
return me;
},
/**
* <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
* incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
* <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
* element will receive the highest z-index.
* @param {Number} zindex The new z-index to set
* @return {Ext.Layer} The Layer
*/
setZIndex: function(zindex) {
var me = this;
me.zindex = zindex;
if (me.getShim()) {
me.shim.setStyle('z-index', zindex++);
}
if (me.shadow) {
me.shadow.setZIndex(zindex++);
}
return me.setStyle('z-index', zindex);
},
setOpacity: function(opacity){
if (this.shadow) {
this.shadow.setOpacity(opacity);
}
return this.callParent(arguments);
}
});

249
OfficeWeb/3rdparty/extjs/src/LoadMask.js vendored Normal file
View File

@@ -0,0 +1,249 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.LoadMask
* <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
* {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
* be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
* <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
* the Store's loading process.</p>
* <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
* object, and upon show, it will place itsef at the top of the hierarchy.</p>
* <p>Example usage:</p>
* <pre><code>
// Basic mask:
var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
myMask.show();
</code></pre>
*/
Ext.define('Ext.LoadMask', {
extend: 'Ext.Component',
alias: 'widget.loadmask',
/* Begin Definitions */
mixins: {
floating: 'Ext.util.Floating'
},
uses: ['Ext.data.StoreManager'],
/* End Definitions */
/**
* @cfg {Ext.data.Store} store
* Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
* hidden on either load success, or load fail.
*/
/**
* @cfg {String} msg
* The text to display in a centered loading message box.
*/
msg : 'Loading...',
/**
* @cfg {String} [msgCls="x-mask-loading"]
* The CSS class to apply to the loading message element.
*/
msgCls : Ext.baseCSSPrefix + 'mask-loading',
/**
* @cfg {Boolean} useMsg
* Whether or not to use a loading message class or simply mask the bound element.
*/
useMsg: true,
/**
* Read-only. True if the mask is currently disabled so that it will not be displayed
* @type Boolean
*/
disabled: false,
baseCls: Ext.baseCSSPrefix + 'mask-msg',
renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
// Private. The whole point is that there's a mask.
modal: true,
// Private. Obviously, it's floating.
floating: {
shadow: 'frame'
},
// Private. Masks are not focusable
focusOnToFront: false,
/**
* Creates new LoadMask.
* @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
* <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
* the mask will be automatically sized upon Component resize, the message box will be kept centered,
* and the mask only be visible when the Component is.</p>
* @param {Object} [config] The config object
*/
constructor : function(el, config) {
var me = this;
// If a Component passed, bind to it.
if (el.isComponent) {
me.ownerCt = el;
me.bindComponent(el);
}
// Create a dumy Component encapsulating the specified Element
else {
me.ownerCt = new Ext.Component({
el: Ext.get(el),
rendered: true,
componentLayoutCounter: 1
});
me.container = el;
}
me.callParent([config]);
if (me.store) {
me.bindStore(me.store, true);
}
me.renderData = {
msgCls: me.msgCls
};
me.renderSelectors = {
msgEl: 'div'
};
},
bindComponent: function(comp) {
this.mon(comp, {
resize: this.onComponentResize,
scope: this
});
},
afterRender: function() {
this.callParent(arguments);
this.container = this.floatParent.getContentTarget();
},
/**
* @private
* Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
*/
onComponentResize: function() {
var me = this;
if (me.rendered && me.isVisible()) {
me.toFront();
me.center();
}
},
/**
* Changes the data store bound to this LoadMask.
* @param {Ext.data.Store} store The store to bind to this LoadMask
*/
bindStore : function(store, initial) {
var me = this;
if (!initial && me.store) {
me.mun(me.store, {
scope: me,
beforeload: me.onBeforeLoad,
load: me.onLoad,
exception: me.onLoad
});
if (!store) {
me.store = null;
}
}
if (store) {
store = Ext.data.StoreManager.lookup(store);
me.mon(store, {
scope: me,
beforeload: me.onBeforeLoad,
load: me.onLoad,
exception: me.onLoad
});
}
me.store = store;
if (store && store.isLoading()) {
me.onBeforeLoad();
}
},
onDisable : function() {
this.callParent(arguments);
if (this.loading) {
this.onLoad();
}
},
// private
onBeforeLoad : function() {
var me = this,
owner = me.ownerCt || me.floatParent,
origin;
if (!this.disabled) {
// If the owning Component has not been layed out, defer so that the ZIndexManager
// gets to read its layed out size when sizing the modal mask
if (owner.componentLayoutCounter) {
Ext.Component.prototype.show.call(me);
} else {
// The code below is a 'run-once' interceptor.
origin = owner.afterComponentLayout;
owner.afterComponentLayout = function() {
owner.afterComponentLayout = origin;
origin.apply(owner, arguments);
if(me.loading) {
Ext.Component.prototype.show.call(me);
}
};
}
}
},
onHide: function(){
var me = this;
me.callParent(arguments);
me.showOnParentShow = true;
},
onShow: function() {
var me = this,
msgEl = me.msgEl;
me.callParent(arguments);
me.loading = true;
if (me.useMsg) {
msgEl.show().update(me.msg);
} else {
msgEl.parent().hide();
}
},
afterShow: function() {
this.callParent(arguments);
this.center();
},
// private
onLoad : function() {
this.loading = false;
Ext.Component.prototype.hide.call(this);
}
});

View File

@@ -0,0 +1,202 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @author Ed Spencer
* @class Ext.ModelManager
* @extends Ext.AbstractManager
The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
__Creating Model Instances__
Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
this by using the Model type directly. The following 3 snippets are equivalent:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['first', 'last']
});
// method 1, create using Ext.create (recommended)
Ext.create('User', {
first: 'Ed',
last: 'Spencer'
});
// method 2, create through the manager (deprecated)
Ext.ModelManager.create({
first: 'Ed',
last: 'Spencer'
}, 'User');
// method 3, create on the type directly
new User({
first: 'Ed',
last: 'Spencer'
});
__Accessing Model Types__
A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
are normal classes, you can access the type directly. The following snippets are equivalent:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['first', 'last']
});
// method 1, access model type through the manager
var UserType = Ext.ModelManager.getModel('User');
// method 2, reference the type directly
var UserType = User;
* @markdown
* @singleton
*/
Ext.define('Ext.ModelManager', {
extend: 'Ext.AbstractManager',
alternateClassName: 'Ext.ModelMgr',
requires: ['Ext.data.Association'],
singleton: true,
typeName: 'mtype',
/**
* Private stack of associations that must be created once their associated model has been defined
* @property {Ext.data.Association[]} associationStack
*/
associationStack: [],
/**
* Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
* immediately, as are any addition plugins defined in the model config.
* @private
*/
registerType: function(name, config) {
var proto = config.prototype,
model;
if (proto && proto.isModel) {
// registering an already defined model
model = config;
} else {
// passing in a configuration
if (!config.extend) {
config.extend = 'Ext.data.Model';
}
model = Ext.define(name, config);
}
this.types[name] = model;
return model;
},
/**
* @private
* Private callback called whenever a model has just been defined. This sets up any associations
* that were waiting for the given model to be defined
* @param {Function} model The model that was just created
*/
onModelDefined: function(model) {
var stack = this.associationStack,
length = stack.length,
create = [],
association, i, created;
for (i = 0; i < length; i++) {
association = stack[i];
if (association.associatedModel == model.modelName) {
create.push(association);
}
}
for (i = 0, length = create.length; i < length; i++) {
created = create[i];
this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
Ext.Array.remove(stack, created);
}
},
/**
* Registers an association where one of the models defined doesn't exist yet.
* The ModelManager will check when new models are registered if it can link them
* together
* @private
* @param {Ext.data.Association} association The association
*/
registerDeferredAssociation: function(association){
this.associationStack.push(association);
},
/**
* Returns the {@link Ext.data.Model} for a given model name
* @param {String/Object} id The id of the model or the model instance.
* @return {Ext.data.Model} a model class.
*/
getModel: function(id) {
var model = id;
if (typeof model == 'string') {
model = this.types[model];
}
return model;
},
/**
* Creates a new instance of a Model using the given data.
*
* This method is deprecated. Use {@link Ext#create Ext.create} instead. For example:
*
* Ext.create('User', {
* first: 'Ed',
* last: 'Spencer'
* });
*
* @param {Object} data Data to initialize the Model's fields with
* @param {String} name The name of the model to create
* @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
*/
create: function(config, name, id) {
var con = typeof name == 'function' ? name : this.types[name || config.name];
return new con(config, id);
}
}, function() {
/**
* Old way for creating Model classes. Instead use:
*
* Ext.define("MyModel", {
* extend: "Ext.data.Model",
* fields: []
* });
*
* @param {String} name Name of the Model class.
* @param {Object} config A configuration object for the Model you wish to create.
* @return {Ext.data.Model} The newly registered Model
* @member Ext
* @deprecated 4.0.0 Use {@link Ext#define} instead.
*/
Ext.regModel = function() {
//<debug>
if (Ext.isDefined(Ext.global.console)) {
Ext.global.console.warn('Ext.regModel has been deprecated. Models can now be created by extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
}
//</debug>
return this.ModelManager.registerType.apply(this.ModelManager, arguments);
};
});

View File

@@ -0,0 +1,123 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @singleton
*
* Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
*
* A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
*
* {
* ptype: 'gridviewdragdrop',
* dragText: 'Drag and drop to reorganize'
* }
*
* Or just use the ptype on its own:
*
* 'gridviewdragdrop'
*
* Alternatively you can instantiate the plugin with Ext.create:
*
* Ext.create('Ext.view.plugin.AutoComplete', {
* ptype: 'gridviewdragdrop',
* dragText: 'Drag and drop to reorganize'
* })
*/
Ext.define('Ext.PluginManager', {
extend: 'Ext.AbstractManager',
alternateClassName: 'Ext.PluginMgr',
singleton: true,
typeName: 'ptype',
/**
* Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
* instantiate.
* @param {Object} config A configuration object for the Plugin you wish to create.
* @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
* contain a `ptype`. (Optional if the config contains a `ptype`).
* @return {Ext.Component} The newly instantiated Plugin.
*/
//create: function(plugin, defaultType) {
// if (plugin instanceof this) {
// return plugin;
// } else {
// var type, config = {};
//
// if (Ext.isString(plugin)) {
// type = plugin;
// }
// else {
// type = plugin[this.typeName] || defaultType;
// config = plugin;
// }
//
// return Ext.createByAlias('plugin.' + type, config);
// }
//},
create : function(config, defaultType){
if (config.init) {
return config;
} else {
return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
}
// Prior system supported Singleton plugins.
//var PluginCls = this.types[config.ptype || defaultType];
//if (PluginCls.init) {
// return PluginCls;
//} else {
// return new PluginCls(config);
//}
},
/**
* Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
* @param {String} type The type to search for
* @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
* truthy
* @return {Ext.AbstractPlugin[]} All matching plugins
*/
findByType: function(type, defaultsOnly) {
var matches = [],
types = this.types;
for (var name in types) {
if (!types.hasOwnProperty(name)) {
continue;
}
var item = types[name];
if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
matches.push(item);
}
}
return matches;
}
}, function() {
/**
* Shorthand for {@link Ext.PluginManager#registerType}
* @param {String} ptype The ptype mnemonic string by which the Plugin class
* may be looked up.
* @param {Function} cls The new Plugin class.
* @member Ext
* @method preg
*/
Ext.preg = function() {
return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
};
});

View File

@@ -0,0 +1,346 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
*
* In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
* as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
* that has predictable points of interest at which you can update the control.
*
* In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
* operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
* itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
* for indicating intermediate progress.
*
* @example
* var p = Ext.create('Ext.ProgressBar', {
* renderTo: Ext.getBody(),
* width: 300
* });
*
* // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
* p.wait({
* interval: 500, //bar will move fast!
* duration: 50000,
* increment: 15,
* text: 'Updating...',
* scope: this,
* fn: function(){
* p.updateText('Done!');
* }
* });
*/
Ext.define('Ext.ProgressBar', {
extend: 'Ext.Component',
alias: 'widget.progressbar',
requires: [
'Ext.Template',
'Ext.CompositeElement',
'Ext.TaskManager',
'Ext.layout.component.ProgressBar'
],
uses: ['Ext.fx.Anim'],
/**
* @cfg {Number} [value=0]
* A floating point value between 0 and 1 (e.g., .5)
*/
/**
* @cfg {String} [text='']
* The progress bar text (defaults to '')
*/
/**
* @cfg {String/HTMLElement/Ext.Element} textEl
* The element to render the progress text to (defaults to the progress bar's internal text element)
*/
/**
* @cfg {String} id
* The progress bar element's id (defaults to an auto-generated id)
*/
/**
* @cfg {String} [baseCls='x-progress']
* The base CSS class to apply to the progress bar's wrapper element.
*/
baseCls: Ext.baseCSSPrefix + 'progress',
config: {
/**
* @cfg {Boolean} animate
* True to animate the progress bar during transitions
*/
animate: false,
/**
* @cfg {String} text
* The text shown in the progress bar
*/
text: ''
},
// private
waitTimer: null,
renderTpl: [
'<div class="{baseCls}-text {baseCls}-text-back">',
'<div>&#160;</div>',
'</div>',
'<div id="{id}-bar" class="{baseCls}-bar">',
'<div class="{baseCls}-text">',
'<div>&#160;</div>',
'</div>',
'</div>'
],
componentLayout: 'progressbar',
// private
initComponent: function() {
this.callParent();
this.addChildEls('bar');
this.addEvents(
/**
* @event update
* Fires after each update interval
* @param {Ext.ProgressBar} this
* @param {Number} value The current progress value
* @param {String} text The current progress text
*/
"update"
);
},
afterRender : function() {
var me = this;
// This produces a composite w/2 el's (which is why we cannot use childEls or
// renderSelectors):
me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
me.callParent(arguments);
if (me.value) {
me.updateProgress(me.value, me.text);
}
else {
me.updateText(me.text);
}
},
/**
* Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
* value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
* it will never automatically reset -- you are responsible for determining when the progress is complete and
* calling {@link #reset} to clear and/or hide the control.
* @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
* @param {String} [text=''] The string to display in the progress text element
* @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
* specified, the default for the class is used
* @return {Ext.ProgressBar} this
*/
updateProgress: function(value, text, animate) {
var me = this,
newWidth;
me.value = value || 0;
if (text) {
me.updateText(text);
}
if (me.rendered && !me.isDestroyed) {
if (me.isVisible(true)) {
newWidth = Math.floor(me.value * me.el.getWidth(true));
if (Ext.isForcedBorderBox) {
newWidth += me.bar.getBorderWidth("lr");
}
if (animate === true || (animate !== false && me.animate)) {
me.bar.stopAnimation();
me.bar.animate(Ext.apply({
to: {
width: newWidth + 'px'
}
}, me.animate));
} else {
me.bar.setWidth(newWidth);
}
} else {
// force a layout when we're visible again
me.doComponentLayout();
}
}
me.fireEvent('update', me, me.value, text);
return me;
},
/**
* Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
* display the updated text.
* @param {String} [text=''] The string to display in the progress text element
* @return {Ext.ProgressBar} this
*/
updateText: function(text) {
var me = this;
me.text = text;
if (me.rendered) {
me.textEl.update(me.text);
}
return me;
},
applyText : function(text) {
this.updateText(text);
},
/**
* Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
* automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
* duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
* {@link #reset}.
*
* Example usage:
*
* var p = new Ext.ProgressBar({
* renderTo: 'my-el'
* });
*
* //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
* var p = Ext.create('Ext.ProgressBar', {
* renderTo: Ext.getBody(),
* width: 300
* });
*
* //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
* p.wait({
* interval: 500, //bar will move fast!
* duration: 50000,
* increment: 15,
* text: 'Updating...',
* scope: this,
* fn: function(){
* p.updateText('Done!');
* }
* });
*
* //Or update indefinitely until some async action completes, then reset manually
* p.wait();
* myAction.on('complete', function(){
* p.reset();
* p.updateText('Done!');
* });
*
* @param {Object} config (optional) Configuration options
* @param {Number} config.duration The length of time in milliseconds that the progress bar should
* run before resetting itself (defaults to undefined, in which case it will run indefinitely
* until reset is called)
* @param {Number} config.interval The length of time in milliseconds between each progress update
* (defaults to 1000 ms)
* @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
* value is not specified, the default for the class is used.
* @param {Number} config.increment The number of progress update segments to display within the
* progress bar (defaults to 10). If the bar reaches the end and is still updating, it will
* automatically wrap back to the beginning.
* @param {String} config.text Optional text to display in the progress bar element (defaults to '').
* @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
* updating. The function will be called with no arguments. This function will be ignored if
* duration is not specified since in that case the progress bar can only be stopped programmatically,
* so any required function should be called by the same code after it resets the progress bar.
* @param {Object} config.scope The scope that is passed to the callback function (only applies when
* duration and fn are both passed).
* @return {Ext.ProgressBar} this
*/
wait: function(o) {
var me = this;
if (!me.waitTimer) {
scope = me;
o = o || {};
me.updateText(o.text);
me.waitTimer = Ext.TaskManager.start({
run: function(i){
var inc = o.increment || 10;
i -= 1;
me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
},
interval: o.interval || 1000,
duration: o.duration,
onStop: function(){
if (o.fn) {
o.fn.apply(o.scope || me);
}
me.reset();
},
scope: scope
});
}
return me;
},
/**
* Returns true if the progress bar is currently in a {@link #wait} operation
* @return {Boolean} True if waiting, else false
*/
isWaiting: function(){
return this.waitTimer !== null;
},
/**
* Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
* (using the {@link #hideMode} property internally).
* @param {Boolean} [hide=false] True to hide the progress bar.
* @return {Ext.ProgressBar} this
*/
reset: function(hide){
var me = this;
me.updateProgress(0);
me.clearTimer();
if (hide === true) {
me.hide();
}
return me;
},
// private
clearTimer: function(){
var me = this;
if (me.waitTimer) {
me.waitTimer.onStop = null; //prevent recursion
Ext.TaskManager.stop(me.waitTimer);
me.waitTimer = null;
}
},
onDestroy: function(){
var me = this;
me.clearTimer();
if (me.rendered) {
if (me.textEl.isComposite) {
me.textEl.clear();
}
Ext.destroyMembers(me, 'textEl', 'progressBar');
}
me.callParent();
}
});

241
OfficeWeb/3rdparty/extjs/src/Shadow.js vendored Normal file
View File

@@ -0,0 +1,241 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Shadow
* Simple class that can provide a shadow effect for any element. Note that the element MUST be absolutely positioned,
* and the shadow does not provide any shimming. This should be used only in simple cases -- for more advanced
* functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
*/
Ext.define('Ext.Shadow', {
requires: ['Ext.ShadowPool'],
/**
* Creates new Shadow.
* @param {Object} config (optional) Config object.
*/
constructor: function(config) {
var me = this,
adjusts = {
h: 0
},
offset,
rad;
Ext.apply(me, config);
if (!Ext.isString(me.mode)) {
me.mode = me.defaultMode;
}
offset = me.offset;
rad = Math.floor(offset / 2);
me.opacity = 50;
switch (me.mode.toLowerCase()) {
// all this hideous nonsense calculates the various offsets for shadows
case "drop":
if (Ext.supports.CSS3BoxShadow) {
adjusts.w = adjusts.h = -offset;
adjusts.l = adjusts.t = offset;
} else {
adjusts.w = 0;
adjusts.l = adjusts.t = offset;
adjusts.t -= 1;
if (Ext.isIE) {
adjusts.l -= offset + rad;
adjusts.t -= offset + rad;
adjusts.w -= rad;
adjusts.h -= rad;
adjusts.t += 1;
}
}
break;
case "sides":
if (Ext.supports.CSS3BoxShadow) {
adjusts.h -= offset;
adjusts.t = offset;
adjusts.l = adjusts.w = 0;
} else {
adjusts.w = (offset * 2);
adjusts.l = -offset;
adjusts.t = offset - 1;
if (Ext.isIE) {
adjusts.l -= (offset - rad);
adjusts.t -= offset + rad;
adjusts.l += 1;
adjusts.w -= (offset - rad) * 2;
adjusts.w -= rad + 1;
adjusts.h -= 1;
}
}
break;
case "frame":
if (Ext.supports.CSS3BoxShadow) {
adjusts.l = adjusts.w = adjusts.t = 0;
} else {
adjusts.w = adjusts.h = (offset * 2);
adjusts.l = adjusts.t = -offset;
adjusts.t += 1;
adjusts.h -= 2;
if (Ext.isIE) {
adjusts.l -= (offset - rad);
adjusts.t -= (offset - rad);
adjusts.l += 1;
adjusts.w -= (offset + rad + 1);
adjusts.h -= (offset + rad);
adjusts.h += 1;
}
break;
}
}
me.adjusts = adjusts;
},
/**
* @cfg {String} mode
* The shadow display mode. Supports the following options:<div class="mdetail-params"><ul>
* <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
* <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
* <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
* </ul></div>
*/
/**
* @cfg {Number} offset
* The number of pixels to offset the shadow from the element
*/
offset: 4,
// private
defaultMode: "drop",
/**
* Displays the shadow under the target element
* @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
*/
show: function(target) {
var me = this,
index;
target = Ext.get(target);
if (!me.el) {
me.el = Ext.ShadowPool.pull();
if (me.el.dom.nextSibling != target.dom) {
me.el.insertBefore(target);
}
}
index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
me.el.setStyle("z-index", me.zIndex || index);
if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
}
me.realign(
target.getLeft(true),
target.getTop(true),
target.dom.offsetWidth,
target.dom.offsetHeight
);
me.el.dom.style.display = "block";
},
/**
* Returns true if the shadow is visible, else false
*/
isVisible: function() {
return this.el ? true: false;
},
/**
* Direct alignment when values are already available. Show must be called at least once before
* calling this method to ensure it is initialized.
* @param {Number} left The target element left position
* @param {Number} top The target element top position
* @param {Number} width The target element width
* @param {Number} height The target element height
*/
realign: function(l, t, targetWidth, targetHeight) {
if (!this.el) {
return;
}
var adjusts = this.adjusts,
d = this.el.dom,
targetStyle = d.style,
shadowWidth,
shadowHeight,
cn,
sww,
sws,
shs;
targetStyle.left = (l + adjusts.l) + "px";
targetStyle.top = (t + adjusts.t) + "px";
shadowWidth = Math.max(targetWidth + adjusts.w, 0);
shadowHeight = Math.max(targetHeight + adjusts.h, 0);
sws = shadowWidth + "px";
shs = shadowHeight + "px";
if (targetStyle.width != sws || targetStyle.height != shs) {
targetStyle.width = sws;
targetStyle.height = shs;
if (Ext.supports.CSS3BoxShadow) {
targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
} else {
// Adjust the 9 point framed element to poke out on the required sides
if (!Ext.isIE) {
cn = d.childNodes;
sww = Math.max(0, (shadowWidth - 12)) + "px";
cn[0].childNodes[1].style.width = sww;
cn[1].childNodes[1].style.width = sww;
cn[2].childNodes[1].style.width = sww;
cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
}
}
}
},
/**
* Hides this shadow
*/
hide: function() {
var me = this;
if (me.el) {
me.el.dom.style.display = "none";
Ext.ShadowPool.push(me.el);
delete me.el;
}
},
/**
* Adjust the z-index of this shadow
* @param {Number} zindex The new z-index
*/
setZIndex: function(z) {
this.zIndex = z;
if (this.el) {
this.el.setStyle("z-index", z);
}
},
/**
* Sets the opacity of the shadow
* @param {Number} opacity The opacity
*/
setOpacity: function(opacity){
if (this.el) {
if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
opacity = Math.floor(opacity * 100 / 2) / 100;
}
this.opacity = opacity;
this.el.setOpacity(opacity);
}
}
});

View File

@@ -0,0 +1,70 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Private utility class that manages the internal Shadow cache
* @private
*/
Ext.define('Ext.ShadowPool', {
singleton: true,
requires: ['Ext.DomHelper'],
markup: function() {
if (Ext.supports.CSS3BoxShadow) {
return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
} else if (Ext.isIE) {
return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
} else {
return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
'<div class="xst" role="presentation">' +
'<div class="xstl" role="presentation"></div>' +
'<div class="xstc" role="presentation"></div>' +
'<div class="xstr" role="presentation"></div>' +
'</div>' +
'<div class="xsc" role="presentation">' +
'<div class="xsml" role="presentation"></div>' +
'<div class="xsmc" role="presentation"></div>' +
'<div class="xsmr" role="presentation"></div>' +
'</div>' +
'<div class="xsb" role="presentation">' +
'<div class="xsbl" role="presentation"></div>' +
'<div class="xsbc" role="presentation"></div>' +
'<div class="xsbr" role="presentation"></div>' +
'</div>' +
'</div>';
}
}(),
shadows: [],
pull: function() {
var sh = this.shadows.shift();
if (!sh) {
sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
sh.autoBoxAdjust = false;
}
return sh;
},
push: function(sh) {
this.shadows.push(sh);
},
reset: function() {
Ext.Array.each(this.shadows, function(shadow) {
shadow.remove();
});
this.shadows = [];
}
});

318
OfficeWeb/3rdparty/extjs/src/Template.js vendored Normal file
View File

@@ -0,0 +1,318 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
*
* An instance of this class may be created by passing to the constructor either a single argument, or multiple
* arguments:
*
* # Single argument: String/Array
*
* The single argument may be either a String or an Array:
*
* - String:
*
* var t = new Ext.Template("<div>Hello {0}.</div>");
* t.{@link #append}('some-element', ['foo']);
*
* - Array:
*
* An Array will be combined with `join('')`.
*
* var t = new Ext.Template([
* '<div name="{id}">',
* '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
* '</div>',
* ]);
* t.{@link #compile}();
* t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
*
* # Multiple arguments: String, Object, Array, ...
*
* Multiple arguments will be combined with `join('')`.
*
* var t = new Ext.Template(
* '<div name="{id}">',
* '<span class="{cls}">{name} {value}</span>',
* '</div>',
* // a configuration object:
* {
* compiled: true, // {@link #compile} immediately
* }
* );
*
* # Notes
*
* - For a list of available format functions, see {@link Ext.util.Format}.
* - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
*/
Ext.define('Ext.Template', {
/* Begin Definitions */
requires: ['Ext.DomHelper', 'Ext.util.Format'],
inheritableStatics: {
/**
* Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
* @param {String/HTMLElement} el A DOM element or its id
* @param {Object} config (optional) Config object
* @return {Ext.Template} The created template
* @static
* @inheritable
*/
from: function(el, config) {
el = Ext.getDom(el);
return new this(el.value || el.innerHTML, config || '');
}
},
/* End Definitions */
/**
* Creates new template.
*
* @param {String...} html List of strings to be concatenated into template.
* Alternatively an array of strings can be given, but then no config object may be passed.
* @param {Object} config (optional) Config object
*/
constructor: function(html) {
var me = this,
args = arguments,
buffer = [],
i = 0,
length = args.length,
value;
me.initialConfig = {};
if (length > 1) {
for (; i < length; i++) {
value = args[i];
if (typeof value == 'object') {
Ext.apply(me.initialConfig, value);
Ext.apply(me, value);
} else {
buffer.push(value);
}
}
html = buffer.join('');
} else {
if (Ext.isArray(html)) {
buffer.push(html.join(''));
} else {
buffer.push(html);
}
}
// @private
me.html = buffer.join('');
if (me.compiled) {
me.compile();
}
},
isTemplate: true,
/**
* @cfg {Boolean} compiled
* True to immediately compile the template. Defaults to false.
*/
/**
* @cfg {Boolean} disableFormats
* True to disable format functions in the template. If the template doesn't contain
* format functions, setting disableFormats to true will reduce apply time. Defaults to false.
*/
disableFormats: false,
re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
/**
* Returns an HTML fragment of this template with the specified values applied.
*
* @param {Object/Array} values The template values. Can be an array if your params are numeric:
*
* var tpl = new Ext.Template('Name: {0}, Age: {1}');
* tpl.applyTemplate(['John', 25]);
*
* or an object:
*
* var tpl = new Ext.Template('Name: {name}, Age: {age}');
* tpl.applyTemplate({name: 'John', age: 25});
*
* @return {String} The HTML fragment
*/
applyTemplate: function(values) {
var me = this,
useFormat = me.disableFormats !== true,
fm = Ext.util.Format,
tpl = me;
if (me.compiled) {
return me.compiled(values);
}
function fn(m, name, format, args) {
if (format && useFormat) {
if (args) {
args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
} else {
args = [values[name]];
}
if (format.substr(0, 5) == "this.") {
return tpl[format.substr(5)].apply(tpl, args);
}
else {
return fm[format].apply(fm, args);
}
}
else {
return values[name] !== undefined ? values[name] : "";
}
}
return me.html.replace(me.re, fn);
},
/**
* Sets the HTML used as the template and optionally compiles it.
* @param {String} html
* @param {Boolean} compile (optional) True to compile the template.
* @return {Ext.Template} this
*/
set: function(html, compile) {
var me = this;
me.html = html;
me.compiled = null;
return compile ? me.compile() : me;
},
compileARe: /\\/g,
compileBRe: /(\r\n|\n)/g,
compileCRe: /'/g,
/**
* Compiles the template into an internal function, eliminating the RegEx overhead.
* @return {Ext.Template} this
*/
compile: function() {
var me = this,
fm = Ext.util.Format,
useFormat = me.disableFormats !== true,
body, bodyReturn;
function fn(m, name, format, args) {
if (format && useFormat) {
args = args ? ',' + args: "";
if (format.substr(0, 5) != "this.") {
format = "fm." + format + '(';
}
else {
format = 'this.' + format.substr(5) + '(';
}
}
else {
args = '';
format = "(values['" + name + "'] == undefined ? '' : ";
}
return "'," + format + "values['" + name + "']" + args + ") ,'";
}
bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
eval(body);
return me;
},
/**
* Applies the supplied values to the template and inserts the new node(s) as the first child of el.
*
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.Element} The new node or Element
*/
insertFirst: function(el, values, returnElement) {
return this.doInsert('afterBegin', el, values, returnElement);
},
/**
* Applies the supplied values to the template and inserts the new node(s) before el.
*
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.Element} The new node or Element
*/
insertBefore: function(el, values, returnElement) {
return this.doInsert('beforeBegin', el, values, returnElement);
},
/**
* Applies the supplied values to the template and inserts the new node(s) after el.
*
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.Element} The new node or Element
*/
insertAfter: function(el, values, returnElement) {
return this.doInsert('afterEnd', el, values, returnElement);
},
/**
* Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
*
* For example usage see {@link Ext.Template Ext.Template class docs}.
*
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return an Ext.Element.
* @return {HTMLElement/Ext.Element} The new node or Element
*/
append: function(el, values, returnElement) {
return this.doInsert('beforeEnd', el, values, returnElement);
},
doInsert: function(where, el, values, returnEl) {
el = Ext.getDom(el);
var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
return returnEl ? Ext.get(newNode, true) : newNode;
},
/**
* Applies the supplied values to the template and overwrites the content of el with the new node(s).
*
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.Element} The new node or Element
*/
overwrite: function(el, values, returnElement) {
el = Ext.getDom(el);
el.innerHTML = this.applyTemplate(values);
return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
}
}, function() {
/**
* @method apply
* @member Ext.Template
* Alias for {@link #applyTemplate}.
* @alias Ext.Template#applyTemplate
*/
this.createAlias('apply', 'applyTemplate');
});

View File

@@ -0,0 +1,460 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* A template class that supports advanced functionality like:
*
* - Autofilling arrays using templates and sub-templates
* - Conditional processing with basic comparison operators
* - Basic math function support
* - Execute arbitrary inline code with special built-in template variables
* - Custom member functions
* - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
*
* XTemplate provides the templating mechanism built into:
*
* - {@link Ext.view.View}
*
* The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
* demonstrate all of the supported features.
*
* # Sample Data
*
* This is the data object used for reference in each code example:
*
* var data = {
* name: 'Tommy Maintz',
* title: 'Lead Developer',
* company: 'Sencha Inc.',
* email: 'tommy@sencha.com',
* address: '5 Cups Drive',
* city: 'Palo Alto',
* state: 'CA',
* zip: '44102',
* drinks: ['Coffee', 'Soda', 'Water'],
* kids: [
* {
* name: 'Joshua',
* age:3
* },
* {
* name: 'Matthew',
* age:2
* },
* {
* name: 'Solomon',
* age:0
* }
* ]
* };
*
* # Auto filling of arrays
*
* The **tpl** tag and the **for** operator are used to process the provided data object:
*
* - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
* tag for each item in the array.
* - If for="." is specified, the data object provided is examined.
* - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
*
* Examples:
*
* <tpl for=".">...</tpl> // loop through array at root node
* <tpl for="foo">...</tpl> // loop through array at foo node
* <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
*
* Using the sample data above:
*
* var tpl = new Ext.XTemplate(
* '<p>Kids: ',
* '<tpl for=".">', // process the data.kids node
* '<p>{#}. {name}</p>', // use current array index to autonumber
* '</tpl></p>'
* );
* tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
*
* An example illustrating how the **for** property can be leveraged to access specified members of the provided data
* object to populate the template:
*
* var tpl = new Ext.XTemplate(
* '<p>Name: {name}</p>',
* '<p>Title: {title}</p>',
* '<p>Company: {company}</p>',
* '<p>Kids: ',
* '<tpl for="kids">', // interrogate the kids property within the data
* '<p>{name}</p>',
* '</tpl></p>'
* );
* tpl.overwrite(panel.body, data); // pass the root node of the data object
*
* Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
* loop. This variable will represent the value of the array at the current index:
*
* var tpl = new Ext.XTemplate(
* '<p>{name}\'s favorite beverages:</p>',
* '<tpl for="drinks">',
* '<div> - {.}</div>',
* '</tpl>'
* );
* tpl.overwrite(panel.body, data);
*
* When processing a sub-template, for example while looping through a child array, you can access the parent object's
* members via the **parent** object:
*
* var tpl = new Ext.XTemplate(
* '<p>Name: {name}</p>',
* '<p>Kids: ',
* '<tpl for="kids">',
* '<tpl if="age &gt; 1">',
* '<p>{name}</p>',
* '<p>Dad: {parent.name}</p>',
* '</tpl>',
* '</tpl></p>'
* );
* tpl.overwrite(panel.body, data);
*
* # Conditional processing with basic comparison operators
*
* The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
* specific parts of the template. Notes:
*
* - Double quotes must be encoded if used within the conditional
* - There is no else operator -- if needed, two opposite if statements should be used.
*
* Examples:
*
* <tpl if="age > 1 && age < 10">Child</tpl>
* <tpl if="age >= 10 && age < 18">Teenager</tpl>
* <tpl if="this.isGirl(name)">...</tpl>
* <tpl if="id==\'download\'">...</tpl>
* <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
* // no good:
* <tpl if="name == "Tommy"">Hello</tpl>
* // encode " if it is part of the condition, e.g.
* <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
*
* Using the sample data above:
*
* var tpl = new Ext.XTemplate(
* '<p>Name: {name}</p>',
* '<p>Kids: ',
* '<tpl for="kids">',
* '<tpl if="age &gt; 1">',
* '<p>{name}</p>',
* '</tpl>',
* '</tpl></p>'
* );
* tpl.overwrite(panel.body, data);
*
* # Basic math support
*
* The following basic math operators may be applied directly on numeric data values:
*
* + - * /
*
* For example:
*
* var tpl = new Ext.XTemplate(
* '<p>Name: {name}</p>',
* '<p>Kids: ',
* '<tpl for="kids">',
* '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
* '<p>{#}: {name}</p>', // <-- Auto-number each item
* '<p>In 5 Years: {age+5}</p>', // <-- Basic math
* '<p>Dad: {parent.name}</p>',
* '</tpl>',
* '</tpl></p>'
* );
* tpl.overwrite(panel.body, data);
*
* # Execute arbitrary inline code with special built-in template variables
*
* Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
* variables available in that code:
*
* - **values**: The values in the current scope. If you are using scope changing sub-templates,
* you can change what values is.
* - **parent**: The scope (values) of the ancestor template.
* - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
* - **xcount**: If you are in a looping template, the total length of the array you are looping.
*
* This example demonstrates basic row striping using an inline code block and the xindex variable:
*
* var tpl = new Ext.XTemplate(
* '<p>Name: {name}</p>',
* '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
* '<p>Kids: ',
* '<tpl for="kids">',
* '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
* '{name}',
* '</div>',
* '</tpl></p>'
* );
* tpl.overwrite(panel.body, data);
*
* # Template member functions
*
* One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
* more complex processing:
*
* var tpl = new Ext.XTemplate(
* '<p>Name: {name}</p>',
* '<p>Kids: ',
* '<tpl for="kids">',
* '<tpl if="this.isGirl(name)">',
* '<p>Girl: {name} - {age}</p>',
* '</tpl>',
* // use opposite if statement to simulate 'else' processing:
* '<tpl if="this.isGirl(name) == false">',
* '<p>Boy: {name} - {age}</p>',
* '</tpl>',
* '<tpl if="this.isBaby(age)">',
* '<p>{name} is a baby!</p>',
* '</tpl>',
* '</tpl></p>',
* {
* // XTemplate configuration:
* disableFormats: true,
* // member functions:
* isGirl: function(name){
* return name == 'Sara Grace';
* },
* isBaby: function(age){
* return age < 1;
* }
* }
* );
* tpl.overwrite(panel.body, data);
*/
Ext.define('Ext.XTemplate', {
/* Begin Definitions */
extend: 'Ext.Template',
/* End Definitions */
argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
constructor: function() {
this.callParent(arguments);
var me = this,
html = me.html,
argsRe = me.argsRe,
nameRe = me.nameRe,
ifRe = me.ifRe,
execRe = me.execRe,
id = 0,
tpls = [],
VALUES = 'values',
PARENT = 'parent',
XINDEX = 'xindex',
XCOUNT = 'xcount',
RETURN = 'return ',
WITHVALUES = 'with(values){ ',
m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
html = ['<tpl>', html, '</tpl>'].join('');
while ((m = html.match(argsRe))) {
exp = null;
fn = null;
exec = null;
matchName = m[0].match(nameRe);
matchIf = m[0].match(ifRe);
matchExec = m[0].match(execRe);
exp = matchIf ? matchIf[1] : null;
if (exp) {
fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
}
exp = matchExec ? matchExec[1] : null;
if (exp) {
exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
}
name = matchName ? matchName[1] : null;
if (name) {
if (name === '.') {
name = VALUES;
} else if (name === '..') {
name = PARENT;
}
name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
}
tpls.push({
id: id,
target: name,
exec: exec,
test: fn,
body: m[1] || ''
});
html = html.replace(m[0], '{xtpl' + id + '}');
id = id + 1;
}
for (i = tpls.length - 1; i >= 0; --i) {
me.compileTpl(tpls[i]);
}
me.master = tpls[tpls.length - 1];
me.tpls = tpls;
},
// @private
applySubTemplate: function(id, values, parent, xindex, xcount) {
var me = this, t = me.tpls[id];
return t.compiled.call(me, values, parent, xindex, xcount);
},
/**
* @cfg {RegExp} codeRe
* The regular expression used to match code variables. Default: matches {[expression]}.
*/
codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
/**
* @cfg {Boolean} compiled
* Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
*/
re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
// @private
compileTpl: function(tpl) {
var fm = Ext.util.Format,
me = this,
useFormat = me.disableFormats !== true,
body, bodyReturn, evaluatedFn;
function fn(m, name, format, args, math) {
var v;
// name is what is inside the {}
// Name begins with xtpl, use a Sub Template
if (name.substr(0, 4) == 'xtpl') {
return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
}
// name = "." - Just use the values object.
if (name == '.') {
// filter to not include arrays/objects/nulls
v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
}
// name = "#" - Use the xindex
else if (name == '#') {
v = 'xindex';
}
else if (name.substr(0, 7) == "parent.") {
v = name;
}
// name has a . in it - Use object literal notation, starting from values
else if (name.indexOf('.') != -1) {
v = "values." + name;
}
// name is a property of values
else {
v = "values['" + name + "']";
}
if (math) {
v = '(' + v + math + ')';
}
if (format && useFormat) {
args = args ? ',' + args : "";
if (format.substr(0, 5) != "this.") {
format = "fm." + format + '(';
}
else {
format = 'this.' + format.substr(5) + '(';
}
}
else {
args = '';
format = "(" + v + " === undefined ? '' : ";
}
return "'," + format + v + args + "),'";
}
function codeFn(m, code) {
// Single quotes get escaped when the template is compiled, however we want to undo this when running code.
return "',(" + code.replace(me.compileARe, "'") + "),'";
}
bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
eval(body);
tpl.compiled = function(values, parent, xindex, xcount) {
var vs,
length,
buffer,
i;
if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
return '';
}
vs = tpl.target ? tpl.target.call(me, values, parent) : values;
if (!vs) {
return '';
}
parent = tpl.target ? values : parent;
if (tpl.target && Ext.isArray(vs)) {
buffer = [];
length = vs.length;
if (tpl.exec) {
for (i = 0; i < length; i++) {
buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
tpl.exec.call(me, vs[i], parent, i + 1, length);
}
} else {
for (i = 0; i < length; i++) {
buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
}
}
return buffer.join('');
}
if (tpl.exec) {
tpl.exec.call(me, vs, parent, xindex, xcount);
}
return evaluatedFn.call(me, vs, parent, xindex, xcount);
};
return this;
},
// inherit docs from Ext.Template
applyTemplate: function(values) {
return this.master.compiled.call(this, values, {}, 1, 1);
},
/**
* Does nothing. XTemplates are compiled automatically, so this function simply returns this.
* @return {Ext.XTemplate} this
*/
compile: function() {
return this;
}
}, function() {
// re-create the alias, inheriting it from Ext.Template doesn't work as intended.
this.createAlias('apply', 'applyTemplate');
});

View File

@@ -0,0 +1,450 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.ZIndexManager
* <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
* and Component activation behavior, including masking below the active (topmost) Component.</p>
* <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
* {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
* <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
* (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
* are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
* guaranteed to be correct, relative to the Window.</p>
*/
Ext.define('Ext.ZIndexManager', {
alternateClassName: 'Ext.WindowGroup',
statics: {
zBase : 9000
},
constructor: function(container) {
var me = this;
me.list = {};
me.zIndexStack = [];
me.front = null;
if (container) {
// This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
if (container.isContainer) {
container.on('resize', me._onContainerResize, me);
me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
// The containing element we will be dealing with (eg masking) is the content target
me.targetEl = container.getTargetEl();
me.container = container;
}
// This is the ZIndexManager for a DOM element
else {
Ext.EventManager.onWindowResize(me._onContainerResize, me);
me.zseed = me.getNextZSeed();
me.targetEl = Ext.get(container);
}
}
// No container passed means we are the global WindowManager. Our target is the doc body.
// DOM must be ready to collect that ref.
else {
Ext.EventManager.onWindowResize(me._onContainerResize, me);
me.zseed = me.getNextZSeed();
Ext.onDocumentReady(function() {
me.targetEl = Ext.getBody();
});
}
},
getNextZSeed: function() {
return (Ext.ZIndexManager.zBase += 10000);
},
setBase: function(baseZIndex) {
this.zseed = baseZIndex;
return this.assignZIndices();
},
// private
assignZIndices: function() {
var a = this.zIndexStack,
len = a.length,
i = 0,
zIndex = this.zseed,
comp;
for (; i < len; i++) {
comp = a[i];
if (comp && !comp.hidden) {
// Setting the zIndex of a Component returns the topmost zIndex consumed by
// that Component.
// If it's just a plain floating Component such as a BoundList, then the
// return value is the passed value plus 10, ready for the next item.
// If a floating *Container* has its zIndex set, it re-orders its managed
// floating children, starting from that new base, and returns a value 10000 above
// the highest zIndex which it allocates.
zIndex = comp.setZIndex(zIndex);
}
}
this._activateLast();
return zIndex;
},
// private
_setActiveChild: function(comp) {
if (comp !== this.front) {
if (this.front) {
this.front.setActive(false, comp);
}
this.front = comp;
if (comp) {
comp.setActive(true);
if (comp.modal) {
this._showModalMask(comp);
}
}
}
},
// private
_activateLast: function(justHidden) {
var comp,
lastActivated = false,
i;
// Go down through the z-index stack.
// Activate the next visible one down.
// Keep going down to find the next visible modal one to shift the modal mask down under
for (i = this.zIndexStack.length-1; i >= 0; --i) {
comp = this.zIndexStack[i];
if (!comp.hidden) {
if (!lastActivated) {
this._setActiveChild(comp);
lastActivated = true;
}
// Move any modal mask down to just under the next modal floater down the stack
if (comp.modal) {
this._showModalMask(comp);
return;
}
}
}
// none to activate, so there must be no modal mask.
// And clear the currently active property
this._hideModalMask();
if (!lastActivated) {
this._setActiveChild(null);
}
},
_showModalMask: function(comp) {
var zIndex = comp.el.getStyle('zIndex') - 4,
maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
parentBox;
if (!maskTarget) {
//<debug>
Ext.global.console && Ext.global.console.warn && Ext.global.console.warn('mask target could not be found. Mask cannot be shown');
//</debug>
return;
}
parentBox = maskTarget.getBox();
if (!this.mask) {
this.mask = Ext.getBody().createChild({
cls: Ext.baseCSSPrefix + 'mask'
});
this.mask.setVisibilityMode(Ext.Element.DISPLAY);
this.mask.on('click', this._onMaskClick, this);
}
if (maskTarget.dom === document.body) {
parentBox.height = Ext.Element.getViewHeight();
}
maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
this.mask.setBox(parentBox);
this.mask.setStyle('zIndex', zIndex);
this.mask.show();
},
_hideModalMask: function() {
if (this.mask && this.mask.dom.parentNode) {
Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
this.mask.hide();
}
},
_onMaskClick: function() {
if (this.front) {
this.front.focus();
}
},
_onContainerResize: function() {
if (this.mask && this.mask.isVisible()) {
this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
}
},
/**
* <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
* need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
* with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
* <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
* to bring the Ext.MessageBox dialog under the same manager as the Desktop's
* ZIndexManager in the desktop sample app:</p><code><pre>
MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
</pre></code>
* @param {Ext.Component} comp The Component to register.
*/
register : function(comp) {
if (comp.zIndexManager) {
comp.zIndexManager.unregister(comp);
}
comp.zIndexManager = this;
this.list[comp.id] = comp;
this.zIndexStack.push(comp);
comp.on('hide', this._activateLast, this);
},
/**
* <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
* need to be called. Components are automatically unregistered upon destruction.
* See {@link #register}.</p>
* @param {Ext.Component} comp The Component to unregister.
*/
unregister : function(comp) {
delete comp.zIndexManager;
if (this.list && this.list[comp.id]) {
delete this.list[comp.id];
comp.un('hide', this._activateLast);
Ext.Array.remove(this.zIndexStack, comp);
// Destruction requires that the topmost visible floater be activated. Same as hiding.
this._activateLast(comp);
}
},
/**
* Gets a registered Component by id.
* @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
* @return {Ext.Component}
*/
get : function(id) {
return typeof id == "object" ? id : this.list[id];
},
/**
* Brings the specified Component to the front of any other active Components in this ZIndexManager.
* @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
* @return {Boolean} True if the dialog was brought to the front, else false
* if it was already in front
*/
bringToFront : function(comp) {
comp = this.get(comp);
if (comp !== this.front) {
Ext.Array.remove(this.zIndexStack, comp);
this.zIndexStack.push(comp);
this.assignZIndices();
return true;
}
if (comp.modal) {
this._showModalMask(comp);
}
return false;
},
/**
* Sends the specified Component to the back of other active Components in this ZIndexManager.
* @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
* @return {Ext.Component} The Component
*/
sendToBack : function(comp) {
comp = this.get(comp);
Ext.Array.remove(this.zIndexStack, comp);
this.zIndexStack.unshift(comp);
this.assignZIndices();
return comp;
},
/**
* Hides all Components managed by this ZIndexManager.
*/
hideAll : function() {
for (var id in this.list) {
if (this.list[id].isComponent && this.list[id].isVisible()) {
this.list[id].hide();
}
}
},
/**
* @private
* Temporarily hides all currently visible managed Components. This is for when
* dragging a Window which may manage a set of floating descendants in its ZIndexManager;
* they should all be hidden just for the duration of the drag.
*/
hide: function() {
var i = 0,
ln = this.zIndexStack.length,
comp;
this.tempHidden = [];
for (; i < ln; i++) {
comp = this.zIndexStack[i];
if (comp.isVisible()) {
this.tempHidden.push(comp);
comp.hide();
}
}
},
/**
* @private
* Restores temporarily hidden managed Components to visibility.
*/
show: function() {
var i = 0,
ln = this.tempHidden.length,
comp,
x,
y;
for (; i < ln; i++) {
comp = this.tempHidden[i];
x = comp.x;
y = comp.y;
comp.show();
comp.setPosition(x, y);
}
delete this.tempHidden;
},
/**
* Gets the currently-active Component in this ZIndexManager.
* @return {Ext.Component} The active Component
*/
getActive : function() {
return this.front;
},
/**
* Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
* The function should accept a single {@link Ext.Component} reference as its only argument and should
* return true if the Component matches the search criteria, otherwise it should return false.
* @param {Function} fn The search function
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
* that gets passed to the function if not specified)
* @return {Array} An array of zero or more matching windows
*/
getBy : function(fn, scope) {
var r = [],
i = 0,
len = this.zIndexStack.length,
comp;
for (; i < len; i++) {
comp = this.zIndexStack[i];
if (fn.call(scope||comp, comp) !== false) {
r.push(comp);
}
}
return r;
},
/**
* Executes the specified function once for every Component in this ZIndexManager, passing each
* Component as the only parameter. Returning false from the function will stop the iteration.
* @param {Function} fn The function to execute for each item
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
*/
each : function(fn, scope) {
var comp;
for (var id in this.list) {
comp = this.list[id];
if (comp.isComponent && fn.call(scope || comp, comp) === false) {
return;
}
}
},
/**
* Executes the specified function once for every Component in this ZIndexManager, passing each
* Component as the only parameter. Returning false from the function will stop the iteration.
* The components are passed to the function starting at the bottom and proceeding to the top.
* @param {Function} fn The function to execute for each item
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
* is executed. Defaults to the current Component in the iteration.
*/
eachBottomUp: function (fn, scope) {
var comp,
stack = this.zIndexStack,
i, n;
for (i = 0, n = stack.length ; i < n; i++) {
comp = stack[i];
if (comp.isComponent && fn.call(scope || comp, comp) === false) {
return;
}
}
},
/**
* Executes the specified function once for every Component in this ZIndexManager, passing each
* Component as the only parameter. Returning false from the function will stop the iteration.
* The components are passed to the function starting at the top and proceeding to the bottom.
* @param {Function} fn The function to execute for each item
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
* is executed. Defaults to the current Component in the iteration.
*/
eachTopDown: function (fn, scope) {
var comp,
stack = this.zIndexStack,
i;
for (i = stack.length ; i-- > 0; ) {
comp = stack[i];
if (comp.isComponent && fn.call(scope || comp, comp) === false) {
return;
}
}
},
destroy: function() {
this.each(function(c) {
c.destroy();
});
delete this.zIndexStack;
delete this.list;
delete this.container;
delete this.targetEl;
}
}, function() {
/**
* @class Ext.WindowManager
* @extends Ext.ZIndexManager
* <p>The default global floating Component group that is available automatically.</p>
* <p>This manages instances of floating Components which were rendered programatically without
* being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
* <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
* there are managed by that ZIndexManager.</p>
* @singleton
*/
Ext.WindowManager = Ext.WindowMgr = new this();
});

View File

@@ -0,0 +1,258 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
* A typical Ext.app.Application might look like this:
*
* Ext.application({
* name: 'MyApp',
* launch: function() {
* Ext.create('Ext.container.Viewport', {
* items: {
* html: 'My App'
* }
* });
* }
* });
*
* This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
* as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
* of colliding global variables.
*
* When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
* at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
* the example above.
*
* # Telling Application about the rest of the app
*
* Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
* the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
* might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
* Here's how we'd tell our Application about all these things:
*
* Ext.application({
* name: 'Blog',
* models: ['Post', 'Comment'],
* controllers: ['Posts', 'Comments'],
*
* launch: function() {
* ...
* }
* });
*
* Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
* Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
* Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
* in this case expecting the controllers to reside in `app/controller/Posts.js` and
* `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
* automatically loaded. Here's how our Posts controller like be defined:
*
* Ext.define('MyApp.controller.Posts', {
* extend: 'Ext.app.Controller',
* views: ['posts.List', 'posts.Edit'],
*
* //the rest of the Controller here
* });
*
* Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
* automatically load all of our app files for us. This means we don't have to manually add script tags into our html
* files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
* application using the Ext JS 4 SDK Tools.
*
* For more information about writing Ext JS 4 applications, please see the
* [application architecture guide][mvc].
*
* [mvc]: #!/guide/application_architecture
*
* @docauthor Ed Spencer
*/
Ext.define('Ext.app.Application', {
extend: 'Ext.app.Controller',
requires: [
'Ext.ModelManager',
'Ext.data.Model',
'Ext.data.StoreManager',
'Ext.tip.QuickTipManager',
'Ext.ComponentManager',
'Ext.app.EventBus'
],
/**
* @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
* models and stores. Don't use spaces or special characters in the name.
*/
/**
* @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
* instance.
*/
scope: undefined,
/**
* @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
*/
enableQuickTips: true,
/**
* @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
*/
/**
* @cfg {String} appFolder The path to the directory which contains all application's classes.
* This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
*/
appFolder: 'app',
/**
* @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
* before firing the launch function.
*/
autoCreateViewport: false,
/**
* Creates new Application.
* @param {Object} [config] Config object.
*/
constructor: function(config) {
config = config || {};
Ext.apply(this, config);
var requires = config.requires || [];
Ext.Loader.setPath(this.name, this.appFolder);
if (this.paths) {
Ext.Object.each(this.paths, function(key, value) {
Ext.Loader.setPath(key, value);
});
}
this.callParent(arguments);
this.eventbus = Ext.create('Ext.app.EventBus');
var controllers = Ext.Array.from(this.controllers),
ln = controllers && controllers.length,
i, controller;
this.controllers = Ext.create('Ext.util.MixedCollection');
if (this.autoCreateViewport) {
requires.push(this.getModuleClassName('Viewport', 'view'));
}
for (i = 0; i < ln; i++) {
requires.push(this.getModuleClassName(controllers[i], 'controller'));
}
Ext.require(requires);
Ext.onReady(function() {
for (i = 0; i < ln; i++) {
controller = this.getController(controllers[i]);
controller.init(this);
}
this.onBeforeLaunch.call(this);
}, this);
},
control: function(selectors, listeners, controller) {
this.eventbus.control(selectors, listeners, controller);
},
/**
* Called automatically when the page has completely loaded. This is an empty function that should be
* overridden by each application that needs to take action on page load
* @property launch
* @type Function
* @param {String} profile The detected {@link #profiles application profile}
* @return {Boolean} By default, the Application will dispatch to the configured startup controller and
* action immediately after running the launch function. Return false to prevent this behavior.
*/
launch: Ext.emptyFn,
/**
* @private
*/
onBeforeLaunch: function() {
if (this.enableQuickTips) {
Ext.tip.QuickTipManager.init();
}
if (this.autoCreateViewport) {
this.getView('Viewport').create();
}
this.launch.call(this.scope || this);
this.launched = true;
this.fireEvent('launch', this);
this.controllers.each(function(controller) {
controller.onLaunch(this);
}, this);
},
getModuleClassName: function(name, type) {
var namespace = Ext.Loader.getPrefix(name);
if (namespace.length > 0 && namespace !== name) {
return name;
}
return this.name + '.' + type + '.' + name;
},
getController: function(name) {
var controller = this.controllers.get(name);
if (!controller) {
controller = Ext.create(this.getModuleClassName(name, 'controller'), {
application: this,
id: name
});
this.controllers.add(controller);
}
return controller;
},
getStore: function(name) {
var store = Ext.StoreManager.get(name);
if (!store) {
store = Ext.create(this.getModuleClassName(name, 'store'), {
storeId: name
});
}
return store;
},
getModel: function(model) {
model = this.getModuleClassName(model, 'model');
return Ext.ModelManager.getModel(model);
},
getView: function(view) {
view = this.getModuleClassName(view, 'view');
return Ext.ClassManager.get(view);
}
});

View File

@@ -0,0 +1,425 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.app.Controller
*
* Controllers are the glue that binds an application together. All they really do is listen for events (usually from
* views) and take some action. Here's how we might create a Controller to manage Users:
*
* Ext.define('MyApp.controller.Users', {
* extend: 'Ext.app.Controller',
*
* init: function() {
* console.log('Initialized Users! This happens before the Application launch function is called');
* }
* });
*
* The init function is a special method that is called when your application boots. It is called before the
* {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
* your Viewport is created.
*
* The init function is a great place to set up how your controller interacts with the view, and is usually used in
* conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
* makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
* our Users controller to tell us when the panel is rendered:
*
* Ext.define('MyApp.controller.Users', {
* extend: 'Ext.app.Controller',
*
* init: function() {
* this.control({
* 'viewport > panel': {
* render: this.onPanelRendered
* }
* });
* },
*
* onPanelRendered: function() {
* console.log('The panel was rendered');
* }
* });
*
* We've updated the init function to use this.control to set up listeners on views in our application. The control
* function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
* are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
* it allows us to pass a CSS-like selector that will find every matching component on the page.
*
* In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
* child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
* functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
* onPanelRendered function is called.
*
* <u>Using refs</u>
*
* One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
* make it really easy to get references to Views on your page. Let's look at an example of this now:
*
* Ext.define('MyApp.controller.Users', {
* extend: 'Ext.app.Controller',
*
* refs: [
* {
* ref: 'list',
* selector: 'grid'
* }
* ],
*
* init: function() {
* this.control({
* 'button': {
* click: this.refreshGrid
* }
* });
* },
*
* refreshGrid: function() {
* this.getList().store.load();
* }
* });
*
* This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
* refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
* the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
* assigns it to the reference 'list'.
*
* By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
* the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
* was capitalized and prepended with get to go from 'list' to 'getList'.
*
* The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
* first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
* use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
* match a single View in your application (in the case above our selector will match any grid on the page).
*
* Bringing it all together, our init function is called when the application boots, at which time we call this.control
* to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
* match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
* simplicity). When the button is clicked we use out getList function to refresh the grid.
*
* You can create any number of refs and control any number of components this way, simply adding more functions to
* your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
* examples/app/feed-viewer folder in the SDK download.
*
* <u>Generated getter methods</u>
*
* Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
* Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
*
* Ext.define('MyApp.controller.Users', {
* extend: 'Ext.app.Controller',
*
* models: ['User'],
* stores: ['AllUsers', 'AdminUsers'],
*
* init: function() {
* var User = this.getUserModel(),
* allUsers = this.getAllUsersStore();
*
* var ed = new User({name: 'Ed'});
* allUsers.add(ed);
* }
* });
*
* By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
* locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
* functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
* Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
* functionality.
*
* <u>Further Reading</u>
*
* For more information about writing Ext JS 4 applications, please see the
* [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
*
* @docauthor Ed Spencer
*/
Ext.define('Ext.app.Controller', {
mixins: {
observable: 'Ext.util.Observable'
},
/**
* @cfg {String} id The id of this controller. You can use this id when dispatching.
*/
/**
* @cfg {String[]} models
* Array of models to require from AppName.model namespace. For example:
*
* Ext.define("MyApp.controller.Foo", {
* extend: "Ext.app.Controller",
* models: ['User', 'Vehicle']
* });
*
* This is equivalent of:
*
* Ext.define("MyApp.controller.Foo", {
* extend: "Ext.app.Controller",
* requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
* });
*
*/
/**
* @cfg {String[]} views
* Array of views to require from AppName.view namespace. For example:
*
* Ext.define("MyApp.controller.Foo", {
* extend: "Ext.app.Controller",
* views: ['List', 'Detail']
* });
*
* This is equivalent of:
*
* Ext.define("MyApp.controller.Foo", {
* extend: "Ext.app.Controller",
* requires: ['MyApp.view.List', 'MyApp.view.Detail']
* });
*
*/
/**
* @cfg {String[]} stores
* Array of stores to require from AppName.store namespace. For example:
*
* Ext.define("MyApp.controller.Foo", {
* extend: "Ext.app.Controller",
* stores: ['Users', 'Vehicles']
* });
*
* This is equivalent of:
*
* Ext.define("MyApp.controller.Foo", {
* extend: "Ext.app.Controller",
* requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
* });
*
*/
onClassExtended: function(cls, data) {
var className = Ext.getClassName(cls),
match = className.match(/^(.*)\.controller\./);
if (match !== null) {
var namespace = Ext.Loader.getPrefix(className) || match[1],
onBeforeClassCreated = data.onBeforeClassCreated,
requires = [],
modules = ['model', 'view', 'store'],
prefix;
data.onBeforeClassCreated = function(cls, data) {
var i, ln, module,
items, j, subLn, item;
for (i = 0,ln = modules.length; i < ln; i++) {
module = modules[i];
items = Ext.Array.from(data[module + 's']);
for (j = 0,subLn = items.length; j < subLn; j++) {
item = items[j];
prefix = Ext.Loader.getPrefix(item);
if (prefix === '' || prefix === item) {
requires.push(namespace + '.' + module + '.' + item);
}
else {
requires.push(item);
}
}
}
Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
};
}
},
/**
* Creates new Controller.
* @param {Object} config (optional) Config object.
*/
constructor: function(config) {
this.mixins.observable.constructor.call(this, config);
Ext.apply(this, config || {});
this.createGetters('model', this.models);
this.createGetters('store', this.stores);
this.createGetters('view', this.views);
if (this.refs) {
this.ref(this.refs);
}
},
/**
* A template method that is called when your application boots. It is called before the
* {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
* your Viewport is created.
*
* @param {Ext.app.Application} application
* @template
*/
init: function(application) {},
/**
* A template method like {@link #init}, but called after the viewport is created.
* This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
*
* @param {Ext.app.Application} application
* @template
*/
onLaunch: function(application) {},
createGetters: function(type, refs) {
type = Ext.String.capitalize(type);
Ext.Array.each(refs, function(ref) {
var fn = 'get',
parts = ref.split('.');
// Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
Ext.Array.each(parts, function(part) {
fn += Ext.String.capitalize(part);
});
fn += type;
if (!this[fn]) {
this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
}
// Execute it right away
this[fn](ref);
},
this);
},
ref: function(refs) {
var me = this;
refs = Ext.Array.from(refs);
Ext.Array.each(refs, function(info) {
var ref = info.ref,
fn = 'get' + Ext.String.capitalize(ref);
if (!me[fn]) {
me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
}
});
},
getRef: function(ref, info, config) {
this.refCache = this.refCache || {};
info = info || {};
config = config || {};
Ext.apply(info, config);
if (info.forceCreate) {
return Ext.ComponentManager.create(info, 'component');
}
var me = this,
selector = info.selector,
cached = me.refCache[ref];
if (!cached) {
me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
if (!cached && info.autoCreate) {
me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
}
if (cached) {
cached.on('beforedestroy', function() {
me.refCache[ref] = null;
});
}
}
return cached;
},
/**
* Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
* object containing component paths mapped to a hash of listener functions.
*
* In the following example the `updateUser` function is mapped to to the `click`
* event on a button component, which is a child of the `useredit` component.
*
* Ext.define('AM.controller.Users', {
* init: function() {
* this.control({
* 'useredit button[action=save]': {
* click: this.updateUser
* }
* });
* },
*
* updateUser: function(button) {
* console.log('clicked the Save button');
* }
* });
*
* See {@link Ext.ComponentQuery} for more information on component selectors.
*
* @param {String/Object} selectors If a String, the second argument is used as the
* listeners, otherwise an object of selectors -> listeners is assumed
* @param {Object} listeners
*/
control: function(selectors, listeners) {
this.application.control(selectors, listeners, this);
},
/**
* Returns instance of a {@link Ext.app.Controller controller} with the given name.
* When controller doesn't exist yet, it's created.
* @param {String} name
* @return {Ext.app.Controller} a controller instance.
*/
getController: function(name) {
return this.application.getController(name);
},
/**
* Returns instance of a {@link Ext.data.Store Store} with the given name.
* When store doesn't exist yet, it's created.
* @param {String} name
* @return {Ext.data.Store} a store instance.
*/
getStore: function(name) {
return this.application.getStore(name);
},
/**
* Returns a {@link Ext.data.Model Model} class with the given name.
* A shorthand for using {@link Ext.ModelManager#getModel}.
* @param {String} name
* @return {Ext.data.Model} a model class.
*/
getModel: function(model) {
return this.application.getModel(model);
},
/**
* Returns a View class with the given name. To create an instance of the view,
* you can use it like it's used by Application to create the Viewport:
*
* this.getView('Viewport').create();
*
* @param {String} name
* @return {Ext.Base} a view class.
*/
getView: function(view) {
return this.application.getView(view);
}
});

View File

@@ -0,0 +1,110 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.app.EventBus
* @private
*/
Ext.define('Ext.app.EventBus', {
requires: [
'Ext.util.Event'
],
mixins: {
observable: 'Ext.util.Observable'
},
constructor: function() {
this.mixins.observable.constructor.call(this);
this.bus = {};
var me = this;
Ext.override(Ext.Component, {
fireEvent: function(ev) {
if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
return me.dispatch.call(me, ev, this, arguments);
}
return false;
}
});
},
dispatch: function(ev, target, args) {
var bus = this.bus,
selectors = bus[ev],
selector, controllers, id, events, event, i, ln;
if (selectors) {
// Loop over all the selectors that are bound to this event
for (selector in selectors) {
// Check if the target matches the selector
if (target.is(selector)) {
// Loop over all the controllers that are bound to this selector
controllers = selectors[selector];
for (id in controllers) {
// Loop over all the events that are bound to this selector on this controller
events = controllers[id];
for (i = 0, ln = events.length; i < ln; i++) {
event = events[i];
// Fire the event!
if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
return false;
};
}
}
}
}
}
},
control: function(selectors, listeners, controller) {
var bus = this.bus,
selector, fn;
if (Ext.isString(selectors)) {
selector = selectors;
selectors = {};
selectors[selector] = listeners;
this.control(selectors, null, controller);
return;
}
Ext.Object.each(selectors, function(selector, listeners) {
Ext.Object.each(listeners, function(ev, listener) {
var options = {},
scope = controller,
event = Ext.create('Ext.util.Event', controller, ev);
// Normalize the listener
if (Ext.isObject(listener)) {
options = listener;
listener = options.fn;
scope = options.scope || controller;
delete options.fn;
delete options.scope;
}
event.addListener(listener, scope, options);
// Create the bus tree if it is not there yet
bus[ev] = bus[ev] || {};
bus[ev][selector] = bus[ev][selector] || {};
bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
// Push our listener in our bus
bus[ev][selector][controller.id].push(event);
});
});
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
* cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
* {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
* button displays the dropdown menu just like a normal SplitButton. Example usage:
*
* @example
* Ext.create('Ext.button.Cycle', {
* showText: true,
* prependText: 'View as ',
* renderTo: Ext.getBody(),
* menu: {
* id: 'view-type-menu',
* items: [{
* text: 'text only',
* iconCls: 'view-text',
* checked: true
* },{
* text: 'HTML',
* iconCls: 'view-html'
* }]
* },
* changeHandler: function(cycleBtn, activeItem) {
* Ext.Msg.alert('Change View', activeItem.text);
* }
* });
*/
Ext.define('Ext.button.Cycle', {
/* Begin Definitions */
alias: 'widget.cycle',
extend: 'Ext.button.Split',
alternateClassName: 'Ext.CycleButton',
/* End Definitions */
/**
* @cfg {Object[]} items
* An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
* `{text:'Foo', iconCls:'foo-icon'}`)
*
* @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
* {@link Ext.menu.CheckItem CheckItems}.
*/
/**
* @cfg {Boolean} [showText=false]
* True to display the active item's text as the button text. The Button will show its
* configured {@link #text} if this config is omitted.
*/
/**
* @cfg {String} [prependText='']
* A static string to prepend before the active item's text when displayed as the button's text (only applies when
* showText = true).
*/
/**
* @cfg {Function} changeHandler
* A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
* callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
* changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
* item)
*/
/**
* @cfg {String} forceIcon
* A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
* regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
* button's icon to match the selected item's icon on change.
*/
/**
* @property {Ext.menu.Menu} menu
* The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
* available choices.
*/
// private
getButtonText: function(item) {
var me = this,
text = '';
if (item && me.showText === true) {
if (me.prependText) {
text += me.prependText;
}
text += item.text;
return text;
}
return me.text;
},
/**
* Sets the button's active menu item.
* @param {Ext.menu.CheckItem} item The item to activate
* @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
*/
setActiveItem: function(item, suppressEvent) {
var me = this;
if (!Ext.isObject(item)) {
item = me.menu.getComponent(item);
}
if (item) {
if (!me.rendered) {
me.text = me.getButtonText(item);
me.iconCls = item.iconCls;
} else {
me.setText(me.getButtonText(item));
me.setIconCls(item.iconCls);
}
me.activeItem = item;
if (!item.checked) {
item.setChecked(true, false);
}
if (me.forceIcon) {
me.setIconCls(me.forceIcon);
}
if (!suppressEvent) {
me.fireEvent('change', me, item);
}
}
},
/**
* Gets the currently active menu item.
* @return {Ext.menu.CheckItem} The active item
*/
getActiveItem: function() {
return this.activeItem;
},
// private
initComponent: function() {
var me = this,
checked = 0,
items;
me.addEvents(
/**
* @event change
* Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
* set on this CycleButton, it will be called instead on active item change and this change event will not
* be fired.
* @param {Ext.button.Cycle} this
* @param {Ext.menu.CheckItem} item The menu item that was selected
*/
"change"
);
if (me.changeHandler) {
me.on('change', me.changeHandler, me.scope || me);
delete me.changeHandler;
}
// Allow them to specify a menu config which is a standard Button config.
// Remove direct use of "items" in 5.0.
items = (me.menu.items||[]).concat(me.items||[]);
me.menu = Ext.applyIf({
cls: Ext.baseCSSPrefix + 'cycle-menu',
items: []
}, me.menu);
// Convert all items to CheckItems
Ext.each(items, function(item, i) {
item = Ext.applyIf({
group: me.id,
itemIndex: i,
checkHandler: me.checkHandler,
scope: me,
checked: item.checked || false
}, item);
me.menu.items.push(item);
if (item.checked) {
checked = i;
}
});
me.itemCount = me.menu.items.length;
me.callParent(arguments);
me.on('click', me.toggleSelected, me);
me.setActiveItem(checked, me);
// If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
if (me.width && me.showText) {
me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
}
},
// private
checkHandler: function(item, pressed) {
if (pressed) {
this.setActiveItem(item);
}
},
/**
* This is normally called internally on button click, but can be called externally to advance the button's active
* item programmatically to the next one in the menu. If the current item is the last one in the menu the active
* item will be set to the first item in the menu.
*/
toggleSelected: function() {
var me = this,
m = me.menu,
checkItem;
checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
checkItem.setChecked(true);
}
});

View File

@@ -0,0 +1,111 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
* of the button. Typically this would be used to display a dropdown menu that provides additional options to the
* primary button action, but any custom handler can provide the arrowclick implementation. Example usage:
*
* @example
* // display a dropdown menu:
* Ext.create('Ext.button.Split', {
* renderTo: Ext.getBody(),
* text: 'Options',
* // handle a click on the button itself
* handler: function() {
* alert("The button was clicked");
* },
* menu: new Ext.menu.Menu({
* items: [
* // these will render as dropdown menu items when the arrow is clicked:
* {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
* {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
* ]
* })
* });
*
* Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
* arrow is clicked:
*
* Ext.create('Ext.button.Split', {
* renderTo: 'button-ct',
* text: 'Options',
* handler: optionsHandler,
* arrowHandler: myCustomHandler
* });
*
*/
Ext.define('Ext.button.Split', {
/* Begin Definitions */
alias: 'widget.splitbutton',
extend: 'Ext.button.Button',
alternateClassName: 'Ext.SplitButton',
/* End Definitions */
/**
* @cfg {Function} arrowHandler
* A function called when the arrow button is clicked (can be used instead of click event)
*/
/**
* @cfg {String} arrowTooltip
* The title attribute of the arrow
*/
// private
arrowCls : 'split',
split : true,
// private
initComponent : function(){
this.callParent();
/**
* @event arrowclick
* Fires when this button's arrow is clicked.
* @param {Ext.button.Split} this
* @param {Event} e The click event
*/
this.addEvents("arrowclick");
},
/**
* Sets this button's arrow click handler.
* @param {Function} handler The function to call when the arrow is clicked
* @param {Object} scope (optional) Scope for the function passed above
*/
setArrowHandler : function(handler, scope){
this.arrowHandler = handler;
this.scope = scope;
},
// private
onClick : function(e, t) {
var me = this;
e.preventDefault();
if (!me.disabled) {
if (me.overMenuTrigger) {
me.maybeShowMenu();
me.fireEvent("arrowclick", me, e);
if (me.arrowHandler) {
me.arrowHandler.call(me.scope || me, me, e);
}
} else {
me.doToggle();
me.fireHandler();
}
}
}
});

View File

@@ -0,0 +1,152 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Callout
* A mixin providing callout functionality for Ext.chart.series.Series.
*/
Ext.define('Ext.chart.Callout', {
/* Begin Definitions */
/* End Definitions */
constructor: function(config) {
if (config.callouts) {
config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
color: "#000",
font: "11px Helvetica, sans-serif"
});
this.callouts = Ext.apply(this.callouts || {}, config.callouts);
this.calloutsArray = [];
}
},
renderCallouts: function() {
if (!this.callouts) {
return;
}
var me = this,
items = me.items,
animate = me.chart.animate,
config = me.callouts,
styles = config.styles,
group = me.calloutsArray,
store = me.chart.store,
len = store.getCount(),
ratio = items.length / len,
previouslyPlacedCallouts = [],
i,
count,
j,
p;
for (i = 0, count = 0; i < len; i++) {
for (j = 0; j < ratio; j++) {
var item = items[count],
label = group[count],
storeItem = store.getAt(i),
display;
display = config.filter(storeItem);
if (!display && !label) {
count++;
continue;
}
if (!label) {
group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
}
for (p in label) {
if (label[p] && label[p].setAttributes) {
label[p].setAttributes(styles, true);
}
}
if (!display) {
for (p in label) {
if (label[p]) {
if (label[p].setAttributes) {
label[p].setAttributes({
hidden: true
}, true);
} else if(label[p].setVisible) {
label[p].setVisible(false);
}
}
}
}
config.renderer(label, storeItem);
me.onPlaceCallout(label, storeItem, item, i, display, animate,
j, count, previouslyPlacedCallouts);
previouslyPlacedCallouts.push(label);
count++;
}
}
this.hideCallouts(count);
},
onCreateCallout: function(storeItem, item, i, display) {
var me = this,
group = me.calloutsGroup,
config = me.callouts,
styles = config.styles,
width = styles.width,
height = styles.height,
chart = me.chart,
surface = chart.surface,
calloutObj = {
//label: false,
//box: false,
lines: false
};
calloutObj.lines = surface.add(Ext.apply({},
{
type: 'path',
path: 'M0,0',
stroke: me.getLegendColor() || '#555'
},
styles));
if (config.items) {
calloutObj.panel = Ext.create('widget.panel', {
style: "position: absolute;",
width: width,
height: height,
items: config.items,
renderTo: chart.el
});
}
return calloutObj;
},
hideCallouts: function(index) {
var calloutsArray = this.calloutsArray,
len = calloutsArray.length,
co,
p;
while (len-->index) {
co = calloutsArray[len];
for (p in co) {
if (co[p]) {
co[p].hide(true);
}
}
}
}
});

View File

@@ -0,0 +1,909 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Charts provide a flexible way to achieve a wide range of data visualization capablitities.
* Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
* updates its display whenever data in the Store changes. In addition, the look and feel
* of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
*
* ## Creating a Simple Chart
*
* Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
* an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
* and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
*
* ### 1. Creating a Store
*
* The first step is to create a {@link Ext.data.Model Model} that represents the type of
* data that will be displayed in the Chart. For example the data for a chart that displays
* a weather forecast could be represented as a series of "WeatherPoint" data points with
* two fields - "temperature", and "date":
*
* Ext.define('WeatherPoint', {
* extend: 'Ext.data.Model',
* fields: ['temperature', 'date']
* });
*
* Next a {@link Ext.data.Store Store} must be created. The store contains a collection of "WeatherPoint" Model instances.
* The data could be loaded dynamically, but for sake of ease this example uses inline data:
*
* var store = Ext.create('Ext.data.Store', {
* model: 'WeatherPoint',
* data: [
* { temperature: 58, date: new Date(2011, 1, 1, 8) },
* { temperature: 63, date: new Date(2011, 1, 1, 9) },
* { temperature: 73, date: new Date(2011, 1, 1, 10) },
* { temperature: 78, date: new Date(2011, 1, 1, 11) },
* { temperature: 81, date: new Date(2011, 1, 1, 12) }
* ]
* });
*
* For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
*
* ### 2. Creating the Chart object
*
* Now that a Store has been created it can be used in a Chart:
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 400,
* height: 300,
* store: store
* });
*
* That's all it takes to create a Chart instance that is backed by a Store.
* However, if the above code is run in a browser, a blank screen will be displayed.
* This is because the two pieces that are responsible for the visual display,
* the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
*
* ### 3. Configuring the Axes
*
* {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
* This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
*
* Ext.create('Ext.chart.Chart', {
* ...
* axes: [
* {
* title: 'Temperature',
* type: 'Numeric',
* position: 'left',
* fields: ['temperature'],
* minimum: 0,
* maximum: 100
* },
* {
* title: 'Time',
* type: 'Time',
* position: 'bottom',
* fields: ['date'],
* dateFormat: 'ga'
* }
* ]
* });
*
* The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
* It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
* defined above. The minimum value for this axis is "0", and the maximum is "100".
*
* The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
* It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
* The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
* configuration tells the Time Axis how to format it's labels.
*
* Here's what the Chart looks like now that it has its Axes configured:
*
* {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
*
* ### 4. Configuring the Series
*
* The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
* Series are responsible for the visual representation of the data points contained in the Store.
* This example only has one Series:
*
* Ext.create('Ext.chart.Chart', {
* ...
* axes: [
* ...
* ],
* series: [
* {
* type: 'line',
* xField: 'date',
* yField: 'temperature'
* }
* ]
* });
*
* This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
* from the "WeatherPoint" Models in the Store to plot its data points:
*
* {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
*
* See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
*
* ## Themes
*
* The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
*
* Ext.create('Ext.chart.Chart', {
* ...
* theme: 'Green',
* ...
* });
*
* {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
*
* For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
*
*/
Ext.define('Ext.chart.Chart', {
/* Begin Definitions */
alias: 'widget.chart',
extend: 'Ext.draw.Component',
mixins: {
themeManager: 'Ext.chart.theme.Theme',
mask: 'Ext.chart.Mask',
navigation: 'Ext.chart.Navigation'
},
requires: [
'Ext.util.MixedCollection',
'Ext.data.StoreManager',
'Ext.chart.Legend',
'Ext.util.DelayedTask'
],
/* End Definitions */
// @private
viewBox: false,
/**
* @cfg {String} theme
* The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
* on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
* 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
* is 'Base'.
*/
/**
* @cfg {Boolean/Object} animate
* True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
* object to be used for default chart animations. Defaults to false.
*/
animate: false,
/**
* @cfg {Boolean/Object} legend
* True for the default legend display or a legend config object. Defaults to false.
*/
legend: false,
/**
* @cfg {Number} insetPadding
* The amount of inset padding in pixels for the chart. Defaults to 10.
*/
insetPadding: 10,
/**
* @cfg {String[]} enginePriority
* Defines the priority order for which Surface implementation to use. The first one supported by the current
* environment will be used. Defaults to `['Svg', 'Vml']`.
*/
enginePriority: ['Svg', 'Vml'],
/**
* @cfg {Object/Boolean} background
* The chart background. This can be a gradient object, image, or color. Defaults to false for no
* background. For example, if `background` were to be a color we could set the object as
*
* background: {
* //color string
* fill: '#ccc'
* }
*
* You can specify an image by using:
*
* background: {
* image: 'http://path.to.image/'
* }
*
* Also you can specify a gradient by using the gradient object syntax:
*
* background: {
* gradient: {
* id: 'gradientId',
* angle: 45,
* stops: {
* 0: {
* color: '#555'
* }
* 100: {
* color: '#ddd'
* }
* }
* }
* }
*/
background: false,
/**
* @cfg {Object[]} gradients
* Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
* array of objects with the following properties:
*
* - **id** - string - The unique name of the gradient.
* - **angle** - number, optional - The angle of the gradient in degrees.
* - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
*
* For example:
*
* gradients: [{
* id: 'gradientId',
* angle: 45,
* stops: {
* 0: {
* color: '#555'
* },
* 100: {
* color: '#ddd'
* }
* }
* }, {
* id: 'gradientId2',
* angle: 0,
* stops: {
* 0: {
* color: '#590'
* },
* 20: {
* color: '#599'
* },
* 100: {
* color: '#ddd'
* }
* }
* }]
*
* Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
*
* sprite.setAttributes({
* fill: 'url(#gradientId)'
* }, true);
*/
/**
* @cfg {Ext.data.Store} store
* The store that supplies data to this chart.
*/
/**
* @cfg {Ext.chart.series.Series[]} series
* Array of {@link Ext.chart.series.Series Series} instances or config objects. For example:
*
* series: [{
* type: 'column',
* axis: 'left',
* listeners: {
* 'afterrender': function() {
* console('afterrender');
* }
* },
* xField: 'category',
* yField: 'data1'
* }]
*/
/**
* @cfg {Ext.chart.axis.Axis[]} axes
* Array of {@link Ext.chart.axis.Axis Axis} instances or config objects. For example:
*
* axes: [{
* type: 'Numeric',
* position: 'left',
* fields: ['data1'],
* title: 'Number of Hits',
* minimum: 0,
* //one minor tick between two major ticks
* minorTickSteps: 1
* }, {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Month of the Year'
* }]
*/
constructor: function(config) {
var me = this,
defaultAnim;
config = Ext.apply({}, config);
me.initTheme(config.theme || me.theme);
if (me.gradients) {
Ext.apply(config, { gradients: me.gradients });
}
if (me.background) {
Ext.apply(config, { background: me.background });
}
if (config.animate) {
defaultAnim = {
easing: 'ease',
duration: 500
};
if (Ext.isObject(config.animate)) {
config.animate = Ext.applyIf(config.animate, defaultAnim);
}
else {
config.animate = defaultAnim;
}
}
me.mixins.mask.constructor.call(me, config);
me.mixins.navigation.constructor.call(me, config);
me.callParent([config]);
},
getChartStore: function(){
return this.substore || this.store;
},
initComponent: function() {
var me = this,
axes,
series;
me.callParent();
me.addEvents(
'itemmousedown',
'itemmouseup',
'itemmouseover',
'itemmouseout',
'itemclick',
'itemdoubleclick',
'itemdragstart',
'itemdrag',
'itemdragend',
/**
* @event beforerefresh
* Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
* {@link #refresh} action will be cancelled.
* @param {Ext.chart.Chart} this
*/
'beforerefresh',
/**
* @event refresh
* Fires after the chart data has been refreshed.
* @param {Ext.chart.Chart} this
*/
'refresh'
);
Ext.applyIf(me, {
zoom: {
width: 1,
height: 1,
x: 0,
y: 0
}
});
me.maxGutter = [0, 0];
me.store = Ext.data.StoreManager.lookup(me.store);
axes = me.axes;
me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
if (axes) {
me.axes.addAll(axes);
}
series = me.series;
me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
if (series) {
me.series.addAll(series);
}
if (me.legend !== false) {
me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
}
me.on({
mousemove: me.onMouseMove,
mouseleave: me.onMouseLeave,
mousedown: me.onMouseDown,
mouseup: me.onMouseUp,
scope: me
});
},
// @private overrides the component method to set the correct dimensions to the chart.
afterComponentLayout: function(width, height) {
var me = this;
if (Ext.isNumber(width) && Ext.isNumber(height)) {
me.curWidth = width;
me.curHeight = height;
me.redraw(true);
}
this.callParent(arguments);
},
/**
* Redraws the chart. If animations are set this will animate the chart too.
* @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
*/
redraw: function(resize) {
var me = this,
chartBBox = me.chartBBox = {
x: 0,
y: 0,
height: me.curHeight,
width: me.curWidth
},
legend = me.legend;
me.surface.setSize(chartBBox.width, chartBBox.height);
// Instantiate Series and Axes
me.series.each(me.initializeSeries, me);
me.axes.each(me.initializeAxis, me);
//process all views (aggregated data etc) on stores
//before rendering.
me.axes.each(function(axis) {
axis.processView();
});
me.axes.each(function(axis) {
axis.drawAxis(true);
});
// Create legend if not already created
if (legend !== false) {
legend.create();
}
// Place axes properly, including influence from each other
me.alignAxes();
// Reposition legend based on new axis alignment
if (me.legend !== false) {
legend.updatePosition();
}
// Find the max gutter
me.getMaxGutter();
// Draw axes and series
me.resizing = !!resize;
me.axes.each(me.drawAxis, me);
me.series.each(me.drawCharts, me);
me.resizing = false;
},
// @private set the store after rendering the chart.
afterRender: function() {
var ref,
me = this;
this.callParent();
if (me.categoryNames) {
me.setCategoryNames(me.categoryNames);
}
if (me.tipRenderer) {
ref = me.getFunctionRef(me.tipRenderer);
me.setTipRenderer(ref.fn, ref.scope);
}
me.bindStore(me.store, true);
me.refresh();
},
// @private get x and y position of the mouse cursor.
getEventXY: function(e) {
var me = this,
box = this.surface.getRegion(),
pageXY = e.getXY(),
x = pageXY[0] - box.left,
y = pageXY[1] - box.top;
return [x, y];
},
// @private wrap the mouse down position to delegate the event to the series.
onClick: function(e) {
var me = this,
position = me.getEventXY(e),
item;
// Ask each series if it has an item corresponding to (not necessarily exactly
// on top of) the current mouse coords. Fire itemclick event.
me.series.each(function(series) {
if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
if (series.getItemForPoint) {
item = series.getItemForPoint(position[0], position[1]);
if (item) {
series.fireEvent('itemclick', item);
}
}
}
}, me);
},
// @private wrap the mouse down position to delegate the event to the series.
onMouseDown: function(e) {
var me = this,
position = me.getEventXY(e),
item;
if (me.mask) {
me.mixins.mask.onMouseDown.call(me, e);
}
// Ask each series if it has an item corresponding to (not necessarily exactly
// on top of) the current mouse coords. Fire mousedown event.
me.series.each(function(series) {
if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
if (series.getItemForPoint) {
item = series.getItemForPoint(position[0], position[1]);
if (item) {
series.fireEvent('itemmousedown', item);
}
}
}
}, me);
},
// @private wrap the mouse up event to delegate it to the series.
onMouseUp: function(e) {
var me = this,
position = me.getEventXY(e),
item;
if (me.mask) {
me.mixins.mask.onMouseUp.call(me, e);
}
// Ask each series if it has an item corresponding to (not necessarily exactly
// on top of) the current mouse coords. Fire mousedown event.
me.series.each(function(series) {
if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
if (series.getItemForPoint) {
item = series.getItemForPoint(position[0], position[1]);
if (item) {
series.fireEvent('itemmouseup', item);
}
}
}
}, me);
},
// @private wrap the mouse move event so it can be delegated to the series.
onMouseMove: function(e) {
var me = this,
position = me.getEventXY(e),
item, last, storeItem, storeField;
if (me.mask) {
me.mixins.mask.onMouseMove.call(me, e);
}
// Ask each series if it has an item corresponding to (not necessarily exactly
// on top of) the current mouse coords. Fire itemmouseover/out events.
me.series.each(function(series) {
if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
if (series.getItemForPoint) {
item = series.getItemForPoint(position[0], position[1]);
last = series._lastItemForPoint;
storeItem = series._lastStoreItem;
storeField = series._lastStoreField;
if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
if (last) {
series.fireEvent('itemmouseout', last);
delete series._lastItemForPoint;
delete series._lastStoreField;
delete series._lastStoreItem;
}
if (item) {
series.fireEvent('itemmouseover', item);
series._lastItemForPoint = item;
series._lastStoreItem = item.storeItem;
series._lastStoreField = item.storeField;
}
}
}
} else {
last = series._lastItemForPoint;
if (last) {
series.fireEvent('itemmouseout', last);
delete series._lastItemForPoint;
delete series._lastStoreField;
delete series._lastStoreItem;
}
}
}, me);
},
// @private handle mouse leave event.
onMouseLeave: function(e) {
var me = this;
if (me.mask) {
me.mixins.mask.onMouseLeave.call(me, e);
}
me.series.each(function(series) {
delete series._lastItemForPoint;
});
},
// @private buffered refresh for when we update the store
delayRefresh: function() {
var me = this;
if (!me.refreshTask) {
me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
}
me.refreshTask.delay(me.refreshBuffer);
},
// @private
refresh: function() {
var me = this;
if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
if (me.fireEvent('beforerefresh', me) !== false) {
me.redraw();
me.fireEvent('refresh', me);
}
}
},
/**
* Changes the data store bound to this chart and refreshes it.
* @param {Ext.data.Store} store The store to bind to this chart
*/
bindStore: function(store, initial) {
var me = this;
if (!initial && me.store) {
if (store !== me.store && me.store.autoDestroy) {
me.store.destroyStore();
}
else {
me.store.un('datachanged', me.refresh, me);
me.store.un('add', me.delayRefresh, me);
me.store.un('remove', me.delayRefresh, me);
me.store.un('update', me.delayRefresh, me);
me.store.un('clear', me.refresh, me);
}
}
if (store) {
store = Ext.data.StoreManager.lookup(store);
store.on({
scope: me,
datachanged: me.refresh,
add: me.delayRefresh,
remove: me.delayRefresh,
update: me.delayRefresh,
clear: me.refresh
});
}
me.store = store;
if (store && !initial) {
me.refresh();
}
},
// @private Create Axis
initializeAxis: function(axis) {
var me = this,
chartBBox = me.chartBBox,
w = chartBBox.width,
h = chartBBox.height,
x = chartBBox.x,
y = chartBBox.y,
themeAttrs = me.themeAttrs,
config = {
chart: me
};
if (themeAttrs) {
config.axisStyle = Ext.apply({}, themeAttrs.axis);
config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
}
switch (axis.position) {
case 'top':
Ext.apply(config, {
length: w,
width: h,
x: x,
y: y
});
break;
case 'bottom':
Ext.apply(config, {
length: w,
width: h,
x: x,
y: h
});
break;
case 'left':
Ext.apply(config, {
length: h,
width: w,
x: x,
y: h
});
break;
case 'right':
Ext.apply(config, {
length: h,
width: w,
x: w,
y: h
});
break;
}
if (!axis.chart) {
Ext.apply(config, axis);
axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
}
else {
Ext.apply(axis, config);
}
},
/**
* @private Adjust the dimensions and positions of each axis and the chart body area after accounting
* for the space taken up on each side by the axes and legend.
*/
alignAxes: function() {
var me = this,
axes = me.axes,
legend = me.legend,
edges = ['top', 'right', 'bottom', 'left'],
chartBBox,
insetPadding = me.insetPadding,
insets = {
top: insetPadding,
right: insetPadding,
bottom: insetPadding,
left: insetPadding
};
function getAxis(edge) {
var i = axes.findIndex('position', edge);
return (i < 0) ? null : axes.getAt(i);
}
// Find the space needed by axes and legend as a positive inset from each edge
Ext.each(edges, function(edge) {
var isVertical = (edge === 'left' || edge === 'right'),
axis = getAxis(edge),
bbox;
// Add legend size if it's on this edge
if (legend !== false) {
if (legend.position === edge) {
bbox = legend.getBBox();
insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
}
}
// Add axis size if there's one on this edge only if it has been
//drawn before.
if (axis && axis.bbox) {
bbox = axis.bbox;
insets[edge] += (isVertical ? bbox.width : bbox.height);
}
});
// Build the chart bbox based on the collected inset values
chartBBox = {
x: insets.left,
y: insets.top,
width: me.curWidth - insets.left - insets.right,
height: me.curHeight - insets.top - insets.bottom
};
me.chartBBox = chartBBox;
// Go back through each axis and set its length and position based on the
// corresponding edge of the chartBBox
axes.each(function(axis) {
var pos = axis.position,
isVertical = (pos === 'left' || pos === 'right');
axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
axis.width = (isVertical ? chartBBox.width : chartBBox.height);
axis.length = (isVertical ? chartBBox.height : chartBBox.width);
});
},
// @private initialize the series.
initializeSeries: function(series, idx) {
var me = this,
themeAttrs = me.themeAttrs,
seriesObj, markerObj, seriesThemes, st,
markerThemes, colorArrayStyle = [],
i = 0, l,
config = {
chart: me,
seriesId: series.seriesId
};
if (themeAttrs) {
seriesThemes = themeAttrs.seriesThemes;
markerThemes = themeAttrs.markerThemes;
seriesObj = Ext.apply({}, themeAttrs.series);
markerObj = Ext.apply({}, themeAttrs.marker);
config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
if (themeAttrs.colors) {
config.colorArrayStyle = themeAttrs.colors;
} else {
colorArrayStyle = [];
for (l = seriesThemes.length; i < l; i++) {
st = seriesThemes[i];
if (st.fill || st.stroke) {
colorArrayStyle.push(st.fill || st.stroke);
}
}
if (colorArrayStyle.length) {
config.colorArrayStyle = colorArrayStyle;
}
}
config.seriesIdx = idx;
}
if (series instanceof Ext.chart.series.Series) {
Ext.apply(series, config);
} else {
Ext.applyIf(config, series);
series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
}
if (series.initialize) {
series.initialize();
}
},
// @private
getMaxGutter: function() {
var me = this,
maxGutter = [0, 0];
me.series.each(function(s) {
var gutter = s.getGutters && s.getGutters() || [0, 0];
maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
});
me.maxGutter = maxGutter;
},
// @private draw axis.
drawAxis: function(axis) {
axis.drawAxis();
},
// @private draw series.
drawCharts: function(series) {
series.triggerafterrender = false;
series.drawSeries();
if (!this.animate) {
series.fireEvent('afterrender');
}
},
// @private remove gently.
destroy: function() {
Ext.destroy(this.surface);
this.bindStore(null);
this.callParent(arguments);
}
});

View File

@@ -0,0 +1,191 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Highlight
* A mixin providing highlight functionality for Ext.chart.series.Series.
*/
Ext.define('Ext.chart.Highlight', {
/* Begin Definitions */
requires: ['Ext.fx.Anim'],
/* End Definitions */
/**
* Highlight the given series item.
* @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius)
* or just use default styles per series by setting highlight = true.
*/
highlight: false,
highlightCfg : null,
constructor: function(config) {
if (config.highlight) {
if (config.highlight !== true) { //is an object
this.highlightCfg = Ext.apply({}, config.highlight);
}
else {
this.highlightCfg = {
fill: '#fdd',
radius: 20,
lineWidth: 5,
stroke: '#f55'
};
}
}
},
/**
* Highlight the given series item.
* @param {Object} item Info about the item; same format as returned by #getItemForPoint.
*/
highlightItem: function(item) {
if (!item) {
return;
}
var me = this,
sprite = item.sprite,
opts = me.highlightCfg,
surface = me.chart.surface,
animate = me.chart.animate,
p, from, to, pi;
if (!me.highlight || !sprite || sprite._highlighted) {
return;
}
if (sprite._anim) {
sprite._anim.paused = true;
}
sprite._highlighted = true;
if (!sprite._defaults) {
sprite._defaults = Ext.apply({}, sprite.attr);
from = {};
to = {};
for (p in opts) {
if (! (p in sprite._defaults)) {
sprite._defaults[p] = surface.availableAttrs[p];
}
from[p] = sprite._defaults[p];
to[p] = opts[p];
if (Ext.isObject(opts[p])) {
from[p] = {};
to[p] = {};
Ext.apply(sprite._defaults[p], sprite.attr[p]);
Ext.apply(from[p], sprite._defaults[p]);
for (pi in sprite._defaults[p]) {
if (! (pi in opts[p])) {
to[p][pi] = from[p][pi];
} else {
to[p][pi] = opts[p][pi];
}
}
for (pi in opts[p]) {
if (! (pi in to[p])) {
to[p][pi] = opts[p][pi];
}
}
}
}
sprite._from = from;
sprite._to = to;
sprite._endStyle = to;
}
if (animate) {
sprite._anim = Ext.create('Ext.fx.Anim', {
target: sprite,
from: sprite._from,
to: sprite._to,
duration: 150
});
} else {
sprite.setAttributes(sprite._to, true);
}
},
/**
* Un-highlight any existing highlights
*/
unHighlightItem: function() {
if (!this.highlight || !this.items) {
return;
}
var me = this,
items = me.items,
len = items.length,
opts = me.highlightCfg,
animate = me.chart.animate,
i = 0,
obj, p, sprite;
for (; i < len; i++) {
if (!items[i]) {
continue;
}
sprite = items[i].sprite;
if (sprite && sprite._highlighted) {
if (sprite._anim) {
sprite._anim.paused = true;
}
obj = {};
for (p in opts) {
if (Ext.isObject(sprite._defaults[p])) {
obj[p] = {};
Ext.apply(obj[p], sprite._defaults[p]);
}
else {
obj[p] = sprite._defaults[p];
}
}
if (animate) {
//sprite._to = obj;
sprite._endStyle = obj;
sprite._anim = Ext.create('Ext.fx.Anim', {
target: sprite,
to: obj,
duration: 150
});
}
else {
sprite.setAttributes(obj, true);
}
delete sprite._highlighted;
//delete sprite._defaults;
}
}
},
cleanHighlights: function() {
if (!this.highlight) {
return;
}
var group = this.group,
markerGroup = this.markerGroup,
i = 0,
l;
for (l = group.getCount(); i < l; i++) {
delete group.getAt(i)._defaults;
}
if (markerGroup) {
for (l = markerGroup.getCount(); i < l; i++) {
delete markerGroup.getAt(i)._defaults;
}
}
}
});

View File

@@ -0,0 +1,238 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Label
*
* Labels is a mixin to the Series class. Labels methods are implemented
* in each of the Series (Pie, Bar, etc) for label creation and placement.
*
* The methods implemented by the Series are:
*
* - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
* The arguments of the method are:
* - *`storeItem`* The element of the store that is related to the label sprite.
* - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
* used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
* - *`i`* The index of the element created (i.e the first created label, second created label, etc)
* - *`display`* The display type. May be <b>false</b> if the label is hidden
*
* - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
* The arguments of the method are:
* - *`label`* The sprite label.</li>
* - *`storeItem`* The element of the store that is related to the label sprite</li>
* - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
* used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
* - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
* - *`display`* The display type. May be <b>false</b> if the label is hidden.
* - *`animate`* A boolean value to set or unset animations for the labels.
*/
Ext.define('Ext.chart.Label', {
/* Begin Definitions */
requires: ['Ext.draw.Color'],
/* End Definitions */
/**
* @cfg {Object} label
* Object with the following properties:
*
* - **display** : String
*
* Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
* "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
* Default value: 'none'.
*
* - **color** : String
*
* The color of the label text.
* Default value: '#000' (black).
*
* - **contrast** : Boolean
*
* True to render the label in contrasting color with the backround.
* Default value: false.
*
* - **field** : String
*
* The name of the field to be displayed in the label.
* Default value: 'name'.
*
* - **minMargin** : Number
*
* Specifies the minimum distance from a label to the origin of the visualization.
* This parameter is useful when using PieSeries width variable pie slice lengths.
* Default value: 50.
*
* - **font** : String
*
* The font used for the labels.
* Default value: "11px Helvetica, sans-serif".
*
* - **orientation** : String
*
* Either "horizontal" or "vertical".
* Dafault value: "horizontal".
*
* - **renderer** : Function
*
* Optional function for formatting the label into a displayable value.
* Default value: function(v) { return v; }
*/
//@private a regex to parse url type colors.
colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
//@private the mixin constructor. Used internally by Series.
constructor: function(config) {
var me = this;
me.label = Ext.applyIf(me.label || {},
{
display: "none",
color: "#000",
field: "name",
minMargin: 50,
font: "11px Helvetica, sans-serif",
orientation: "horizontal",
renderer: function(v) {
return v;
}
});
if (me.label.display !== 'none') {
me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
}
},
//@private a method to render all labels in the labelGroup
renderLabels: function() {
var me = this,
chart = me.chart,
gradients = chart.gradients,
items = me.items,
animate = chart.animate,
config = me.label,
display = config.display,
color = config.color,
field = [].concat(config.field),
group = me.labelsGroup,
groupLength = (group || 0) && group.length,
store = me.chart.store,
len = store.getCount(),
itemLength = (items || 0) && items.length,
ratio = itemLength / len,
gradientsCount = (gradients || 0) && gradients.length,
Color = Ext.draw.Color,
hides = [],
gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
if (display == 'none') {
return;
}
// no items displayed, hide all labels
if(itemLength == 0){
while(groupLength--)
hides.push(groupLength);
}else{
for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
index = 0;
for (j = 0; j < ratio; j++) {
item = items[count];
label = group.getAt(groupIndex);
storeItem = store.getAt(i);
//check the excludes
while(this.__excludes && this.__excludes[index] && ratio > 1) {
if(field[j]){
hides.push(groupIndex);
}
index++;
}
if (!item && label) {
label.hide(true);
groupIndex++;
}
if (item && field[j]) {
if (!label) {
label = me.onCreateLabel(storeItem, item, i, display, j, index);
}
me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
groupIndex++;
//set contrast
if (config.contrast && item.sprite) {
sprite = item.sprite;
//set the color string to the color to be set.
if (sprite._endStyle) {
colorString = sprite._endStyle.fill;
}
else if (sprite._to) {
colorString = sprite._to.fill;
}
else {
colorString = sprite.attr.fill;
}
colorString = colorString || sprite.attr.fill;
spriteColor = Color.fromString(colorString);
//color wasn't parsed property maybe because it's a gradient id
if (colorString && !spriteColor) {
colorString = colorString.match(me.colorStringRe)[1];
for (k = 0; k < gradientsCount; k++) {
gradient = gradients[k];
if (gradient.id == colorString) {
//avg color stops
colorStop = 0; colorStopTotal = 0;
for (colorStopIndex in gradient.stops) {
colorStop++;
colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
}
spriteBrightness = (colorStopTotal / colorStop) / 255;
break;
}
}
}
else {
spriteBrightness = spriteColor.getGrayscale() / 255;
}
if (label.isOutside) {
spriteBrightness = 1;
}
labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
label.setAttributes({
fill: String(Color.fromHSL.apply({}, labelColor))
}, true);
}
}
count++;
index++;
}
}
}
me.hideLabels(hides);
},
hideLabels: function(hides){
var labelsGroup = this.labelsGroup,
hlen = hides.length;
while(hlen--)
labelsGroup.getAt(hides[hlen]).hide(true);
}
});

View File

@@ -0,0 +1,400 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Legend
*
* Defines a legend for a chart's series.
* The 'chart' member must be set prior to rendering.
* The legend class displays a list of legend items each of them related with a
* series being rendered. In order to render the legend item of the proper series
* the series configuration object must have `showInSeries` set to true.
*
* The legend configuration object accepts a `position` as parameter.
* The `position` parameter can be `left`, `right`
* `top` or `bottom`. For example:
*
* legend: {
* position: 'right'
* },
*
* ## Example
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
* { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
* { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
* { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
* { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* store: store,
* shadow: true,
* theme: 'Category1',
* legend: {
* position: 'top'
* },
* axes: [
* {
* type: 'Numeric',
* grid: true,
* position: 'left',
* fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
* title: 'Sample Values',
* grid: {
* odd: {
* opacity: 1,
* fill: '#ddd',
* stroke: '#bbb',
* 'stroke-width': 1
* }
* },
* minimum: 0,
* adjustMinimumByMajorUnit: 0
* },
* {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics',
* grid: true,
* label: {
* rotate: {
* degrees: 315
* }
* }
* }
* ],
* series: [{
* type: 'area',
* highlight: false,
* axis: 'left',
* xField: 'name',
* yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
* style: {
* opacity: 0.93
* }
* }]
* });
*/
Ext.define('Ext.chart.Legend', {
/* Begin Definitions */
requires: ['Ext.chart.LegendItem'],
/* End Definitions */
/**
* @cfg {Boolean} visible
* Whether or not the legend should be displayed.
*/
visible: true,
/**
* @cfg {String} position
* The position of the legend in relation to the chart. One of: "top",
* "bottom", "left", "right", or "float". If set to "float", then the legend
* box will be positioned at the point denoted by the x and y parameters.
*/
position: 'bottom',
/**
* @cfg {Number} x
* X-position of the legend box. Used directly if position is set to "float", otherwise
* it will be calculated dynamically.
*/
x: 0,
/**
* @cfg {Number} y
* Y-position of the legend box. Used directly if position is set to "float", otherwise
* it will be calculated dynamically.
*/
y: 0,
/**
* @cfg {String} labelFont
* Font to be used for the legend labels, eg '12px Helvetica'
*/
labelFont: '12px Helvetica, sans-serif',
/**
* @cfg {String} boxStroke
* Style of the stroke for the legend box
*/
boxStroke: '#000',
/**
* @cfg {String} boxStrokeWidth
* Width of the stroke for the legend box
*/
boxStrokeWidth: 1,
/**
* @cfg {String} boxFill
* Fill style for the legend box
*/
boxFill: '#FFF',
/**
* @cfg {Number} itemSpacing
* Amount of space between legend items
*/
itemSpacing: 10,
/**
* @cfg {Number} padding
* Amount of padding between the legend box's border and its items
*/
padding: 5,
// @private
width: 0,
// @private
height: 0,
/**
* @cfg {Number} boxZIndex
* Sets the z-index for the legend. Defaults to 100.
*/
boxZIndex: 100,
/**
* Creates new Legend.
* @param {Object} config (optional) Config object.
*/
constructor: function(config) {
var me = this;
if (config) {
Ext.apply(me, config);
}
me.items = [];
/**
* Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
* @type {Boolean}
*/
me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
// cache these here since they may get modified later on
me.origX = me.x;
me.origY = me.y;
},
/**
* @private Create all the sprites for the legend
*/
create: function() {
var me = this;
me.createBox();
me.createItems();
if (!me.created && me.isDisplayed()) {
me.created = true;
// Listen for changes to series titles to trigger regeneration of the legend
me.chart.series.each(function(series) {
series.on('titlechange', function() {
me.create();
me.updatePosition();
});
});
}
},
/**
* @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
* and also the 'showInLegend' config for each of the series.
*/
isDisplayed: function() {
return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
},
/**
* @private Create the series markers and labels
*/
createItems: function() {
var me = this,
chart = me.chart,
surface = chart.surface,
items = me.items,
padding = me.padding,
itemSpacing = me.itemSpacing,
spacingOffset = 2,
maxWidth = 0,
maxHeight = 0,
totalWidth = 0,
totalHeight = 0,
vertical = me.isVertical,
math = Math,
mfloor = math.floor,
mmax = math.max,
index = 0,
i = 0,
len = items ? items.length : 0,
x, y, spacing, item, bbox, height, width;
//remove all legend items
if (len) {
for (; i < len; i++) {
items[i].destroy();
}
}
//empty array
items.length = [];
// Create all the item labels, collecting their dimensions and positioning each one
// properly in relation to the previous item
chart.series.each(function(series, i) {
if (series.showInLegend) {
Ext.each([].concat(series.yField), function(field, j) {
item = Ext.create('Ext.chart.LegendItem', {
legend: this,
series: series,
surface: chart.surface,
yFieldIndex: j
});
bbox = item.getBBox();
//always measure from x=0, since not all markers go all the way to the left
width = bbox.width;
height = bbox.height;
if (i + j === 0) {
spacing = vertical ? padding + height / 2 : padding;
}
else {
spacing = itemSpacing / (vertical ? 2 : 1);
}
// Set the item's position relative to the legend box
item.x = mfloor(vertical ? padding : totalWidth + spacing);
item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
// Collect cumulative dimensions
totalWidth += width + spacing;
totalHeight += height + spacing;
maxWidth = mmax(maxWidth, width);
maxHeight = mmax(maxHeight, height);
items.push(item);
}, this);
}
}, me);
// Store the collected dimensions for later
me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
if (vertical && items.length === 1) {
spacingOffset = 1;
}
me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
me.itemHeight = maxHeight;
},
/**
* @private Get the bounds for the legend's outer box
*/
getBBox: function() {
var me = this;
return {
x: Math.round(me.x) - me.boxStrokeWidth / 2,
y: Math.round(me.y) - me.boxStrokeWidth / 2,
width: me.width,
height: me.height
};
},
/**
* @private Create the box around the legend items
*/
createBox: function() {
var me = this,
box;
if (me.boxSprite) {
me.boxSprite.destroy();
}
box = me.boxSprite = me.chart.surface.add(Ext.apply({
type: 'rect',
stroke: me.boxStroke,
"stroke-width": me.boxStrokeWidth,
fill: me.boxFill,
zIndex: me.boxZIndex
}, me.getBBox()));
box.redraw();
},
/**
* @private Update the position of all the legend's sprites to match its current x/y values
*/
updatePosition: function() {
var me = this,
x, y,
legendWidth = me.width,
legendHeight = me.height,
padding = me.padding,
chart = me.chart,
chartBBox = chart.chartBBox,
insets = chart.insetPadding,
chartWidth = chartBBox.width - (insets * 2),
chartHeight = chartBBox.height - (insets * 2),
chartX = chartBBox.x + insets,
chartY = chartBBox.y + insets,
surface = chart.surface,
mfloor = Math.floor;
if (me.isDisplayed()) {
// Find the position based on the dimensions
switch(me.position) {
case "left":
x = insets;
y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
break;
case "right":
x = mfloor(surface.width - legendWidth) - insets;
y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
break;
case "top":
x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
y = insets;
break;
case "bottom":
x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
y = mfloor(surface.height - legendHeight) - insets;
break;
default:
x = mfloor(me.origX) + insets;
y = mfloor(me.origY) + insets;
}
me.x = x;
me.y = y;
// Update the position of each item
Ext.each(me.items, function(item) {
item.updatePosition();
});
// Update the position of the outer box
me.boxSprite.setAttributes(me.getBBox(), true);
}
}
});

View File

@@ -0,0 +1,225 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.LegendItem
* @extends Ext.draw.CompositeSprite
* A single item of a legend (marker plus label)
*/
Ext.define('Ext.chart.LegendItem', {
/* Begin Definitions */
extend: 'Ext.draw.CompositeSprite',
requires: ['Ext.chart.Shape'],
/* End Definitions */
// Position of the item, relative to the upper-left corner of the legend box
x: 0,
y: 0,
zIndex: 500,
constructor: function(config) {
this.callParent(arguments);
this.createLegend(config);
},
/**
* Creates all the individual sprites for this legend item
*/
createLegend: function(config) {
var me = this,
index = config.yFieldIndex,
series = me.series,
seriesType = series.type,
idx = me.yFieldIndex,
legend = me.legend,
surface = me.surface,
refX = legend.x + me.x,
refY = legend.y + me.y,
bbox, z = me.zIndex,
markerConfig, label, mask,
radius, toggle = false,
seriesStyle = Ext.apply(series.seriesStyle, series.style);
function getSeriesProp(name) {
var val = series[name];
return (Ext.isArray(val) ? val[idx] : val);
}
label = me.add('label', surface.add({
type: 'text',
x: 20,
y: 0,
zIndex: z || 0,
font: legend.labelFont,
text: getSeriesProp('title') || getSeriesProp('yField')
}));
// Line series - display as short line with optional marker in the middle
if (seriesType === 'line' || seriesType === 'scatter') {
if(seriesType === 'line') {
me.add('line', surface.add({
type: 'path',
path: 'M0.5,0.5L16.5,0.5',
zIndex: z,
"stroke-width": series.lineWidth,
"stroke-linejoin": "round",
"stroke-dasharray": series.dash,
stroke: seriesStyle.stroke || '#000',
style: {
cursor: 'pointer'
}
}));
}
if (series.showMarkers || seriesType === 'scatter') {
markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
fill: markerConfig.fill,
x: 8.5,
y: 0.5,
zIndex: z,
radius: markerConfig.radius || markerConfig.size,
style: {
cursor: 'pointer'
}
}));
}
}
// All other series types - display as filled box
else {
me.add('box', surface.add({
type: 'rect',
zIndex: z,
x: 0,
y: 0,
width: 12,
height: 12,
fill: series.getLegendColor(index),
style: {
cursor: 'pointer'
}
}));
}
me.setAttributes({
hidden: false
}, true);
bbox = me.getBBox();
mask = me.add('mask', surface.add({
type: 'rect',
x: bbox.x,
y: bbox.y,
width: bbox.width || 20,
height: bbox.height || 20,
zIndex: (z || 0) + 1000,
fill: '#f00',
opacity: 0,
style: {
'cursor': 'pointer'
}
}));
//add toggle listener
me.on('mouseover', function() {
label.setStyle({
'font-weight': 'bold'
});
mask.setStyle({
'cursor': 'pointer'
});
series._index = index;
series.highlightItem();
}, me);
me.on('mouseout', function() {
label.setStyle({
'font-weight': 'normal'
});
series._index = index;
series.unHighlightItem();
}, me);
if (!series.visibleInLegend(index)) {
toggle = true;
label.setAttributes({
opacity: 0.5
}, true);
}
me.on('mousedown', function() {
if (!toggle) {
series.hideAll();
label.setAttributes({
opacity: 0.5
}, true);
} else {
series.showAll();
label.setAttributes({
opacity: 1
}, true);
}
toggle = !toggle;
}, me);
me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
},
/**
* Update the positions of all this item's sprites to match the root position
* of the legend box.
* @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
* as the reference point for the relative positioning. Defaults to the Legend.
*/
updatePosition: function(relativeTo) {
var me = this,
items = me.items,
ln = items.length,
i = 0,
item;
if (!relativeTo) {
relativeTo = me.legend;
}
for (; i < ln; i++) {
item = items[i];
switch (item.type) {
case 'text':
item.setAttributes({
x: 20 + relativeTo.x + me.x,
y: relativeTo.y + me.y
}, true);
break;
case 'rect':
item.setAttributes({
translate: {
x: relativeTo.x + me.x,
y: relativeTo.y + me.y - 6
}
}, true);
break;
default:
item.setAttributes({
translate: {
x: relativeTo.x + me.x,
y: relativeTo.y + me.y
}
}, true);
}
}
}
});

View File

@@ -0,0 +1,231 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Mask
*
* Defines a mask for a chart's series.
* The 'chart' member must be set prior to rendering.
*
* A Mask can be used to select a certain region in a chart.
* When enabled, the `select` event will be triggered when a
* region is selected by the mask, allowing the user to perform
* other tasks like zooming on that region, etc.
*
* In order to use the mask one has to set the Chart `mask` option to
* `true`, `vertical` or `horizontal`. Then a possible configuration for the
* listener could be:
*
items: {
xtype: 'chart',
animate: true,
store: store1,
mask: 'horizontal',
listeners: {
select: {
fn: function(me, selection) {
me.setZoom(selection);
me.mask.hide();
}
}
},
* In this example we zoom the chart to that particular region. You can also get
* a handle to a mask instance from the chart object. The `chart.mask` element is a
* `Ext.Panel`.
*
*/
Ext.define('Ext.chart.Mask', {
require: ['Ext.chart.MaskLayer'],
/**
* Creates new Mask.
* @param {Object} config (optional) Config object.
*/
constructor: function(config) {
var me = this;
me.addEvents('select');
if (config) {
Ext.apply(me, config);
}
if (me.mask) {
me.on('afterrender', function() {
//create a mask layer component
var comp = Ext.create('Ext.chart.MaskLayer', {
renderTo: me.el
});
comp.el.on({
'mousemove': function(e) {
me.onMouseMove(e);
},
'mouseup': function(e) {
me.resized(e);
}
});
//create a resize handler for the component
var resizeHandler = Ext.create('Ext.resizer.Resizer', {
el: comp.el,
handles: 'all',
pinned: true
});
resizeHandler.on({
'resize': function(e) {
me.resized(e);
}
});
comp.initDraggable();
me.maskType = me.mask;
me.mask = comp;
me.maskSprite = me.surface.add({
type: 'path',
path: ['M', 0, 0],
zIndex: 1001,
opacity: 0.7,
hidden: true,
stroke: '#444'
});
}, me, { single: true });
}
},
resized: function(e) {
var me = this,
bbox = me.bbox || me.chartBBox,
x = bbox.x,
y = bbox.y,
width = bbox.width,
height = bbox.height,
box = me.mask.getBox(true),
max = Math.max,
min = Math.min,
staticX = box.x - x,
staticY = box.y - y;
staticX = max(staticX, x);
staticY = max(staticY, y);
staticX = min(staticX, width);
staticY = min(staticY, height);
box.x = staticX;
box.y = staticY;
me.fireEvent('select', me, box);
},
onMouseUp: function(e) {
var me = this,
bbox = me.bbox || me.chartBBox,
sel = me.maskSelection;
me.maskMouseDown = false;
me.mouseDown = false;
if (me.mouseMoved) {
me.onMouseMove(e);
me.mouseMoved = false;
me.fireEvent('select', me, {
x: sel.x - bbox.x,
y: sel.y - bbox.y,
width: sel.width,
height: sel.height
});
}
},
onMouseDown: function(e) {
var me = this;
me.mouseDown = true;
me.mouseMoved = false;
me.maskMouseDown = {
x: e.getPageX() - me.el.getX(),
y: e.getPageY() - me.el.getY()
};
},
onMouseMove: function(e) {
var me = this,
mask = me.maskType,
bbox = me.bbox || me.chartBBox,
x = bbox.x,
y = bbox.y,
math = Math,
floor = math.floor,
abs = math.abs,
min = math.min,
max = math.max,
height = floor(y + bbox.height),
width = floor(x + bbox.width),
posX = e.getPageX(),
posY = e.getPageY(),
staticX = posX - me.el.getX(),
staticY = posY - me.el.getY(),
maskMouseDown = me.maskMouseDown,
path;
me.mouseMoved = me.mouseDown;
staticX = max(staticX, x);
staticY = max(staticY, y);
staticX = min(staticX, width);
staticY = min(staticY, height);
if (maskMouseDown && me.mouseDown) {
if (mask == 'horizontal') {
staticY = y;
maskMouseDown.y = height;
posY = me.el.getY() + bbox.height + me.insetPadding;
}
else if (mask == 'vertical') {
staticX = x;
maskMouseDown.x = width;
}
width = maskMouseDown.x - staticX;
height = maskMouseDown.y - staticY;
path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
me.maskSelection = {
x: width > 0 ? staticX : staticX + width,
y: height > 0 ? staticY : staticY + height,
width: abs(width),
height: abs(height)
};
me.mask.updateBox(me.maskSelection);
me.mask.show();
me.maskSprite.setAttributes({
hidden: true
}, true);
}
else {
if (mask == 'horizontal') {
path = ['M', staticX, y, 'L', staticX, height];
}
else if (mask == 'vertical') {
path = ['M', x, staticY, 'L', width, staticY];
}
else {
path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
}
me.maskSprite.setAttributes({
path: path,
fill: me.maskMouseDown ? me.maskSprite.stroke : false,
'stroke-width': mask === true ? 1 : 3,
hidden: false
}, true);
}
},
onMouseLeave: function(e) {
var me = this;
me.mouseMoved = false;
me.mouseDown = false;
me.maskMouseDown = false;
me.mask.hide();
me.maskSprite.hide(true);
}
});

View File

@@ -0,0 +1,59 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Ext.chart.MaskLayer', {
extend: 'Ext.Component',
constructor: function(config) {
config = Ext.apply(config || {}, {
style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
});
this.callParent([config]);
},
initComponent: function() {
var me = this;
me.callParent(arguments);
me.addEvents(
'mousedown',
'mouseup',
'mousemove',
'mouseenter',
'mouseleave'
);
},
initDraggable: function() {
this.callParent(arguments);
this.dd.onStart = function (e) {
var me = this,
comp = me.comp;
// Cache the start [X, Y] array
this.startPosition = comp.getPosition(true);
// If client Component has a ghost method to show a lightweight version of itself
// then use that as a drag proxy unless configured to liveDrag.
if (comp.ghost && !comp.liveDrag) {
me.proxy = comp.ghost();
me.dragTarget = me.proxy.header.el;
}
// Set the constrainTo Region before we start dragging.
if (me.constrain || me.constrainDelegate) {
me.constrainTo = me.calculateConstrainRegion();
}
};
}
});

View File

@@ -0,0 +1,88 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Navigation
*
* Handles panning and zooming capabilities.
*
* Used as mixin by Ext.chart.Chart.
*/
Ext.define('Ext.chart.Navigation', {
constructor: function() {
this.originalStore = this.store;
},
/**
* Zooms the chart to the specified selection range.
* Can be used with a selection mask. For example:
*
* items: {
* xtype: 'chart',
* animate: true,
* store: store1,
* mask: 'horizontal',
* listeners: {
* select: {
* fn: function(me, selection) {
* me.setZoom(selection);
* me.mask.hide();
* }
* }
* }
* }
*/
setZoom: function(zoomConfig) {
var me = this,
axes = me.axes,
bbox = me.chartBBox,
xScale = 1 / bbox.width,
yScale = 1 / bbox.height,
zoomer = {
x : zoomConfig.x * xScale,
y : zoomConfig.y * yScale,
width : zoomConfig.width * xScale,
height : zoomConfig.height * yScale
};
axes.each(function(axis) {
var ends = axis.calcEnds();
if (axis.position == 'bottom' || axis.position == 'top') {
var from = (ends.to - ends.from) * zoomer.x + ends.from,
to = (ends.to - ends.from) * zoomer.width + from;
axis.minimum = from;
axis.maximum = to;
} else {
var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
from = to - (ends.to - ends.from) * zoomer.height;
axis.minimum = from;
axis.maximum = to;
}
});
me.redraw(false);
},
/**
* Restores the zoom to the original value. This can be used to reset
* the previous zoom state set by `setZoom`. For example:
*
* myChart.restoreZoom();
*/
restoreZoom: function() {
this.store = this.substore = this.originalStore;
this.redraw(true);
}
});

View File

@@ -0,0 +1,121 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Shape
* @ignore
*/
Ext.define('Ext.chart.Shape', {
/* Begin Definitions */
singleton: true,
/* End Definitions */
circle: function (surface, opts) {
return surface.add(Ext.apply({
type: 'circle',
x: opts.x,
y: opts.y,
stroke: null,
radius: opts.radius
}, opts));
},
line: function (surface, opts) {
return surface.add(Ext.apply({
type: 'rect',
x: opts.x - opts.radius,
y: opts.y - opts.radius,
height: 2 * opts.radius,
width: 2 * opts.radius / 5
}, opts));
},
square: function (surface, opts) {
return surface.add(Ext.applyIf({
type: 'rect',
x: opts.x - opts.radius,
y: opts.y - opts.radius,
height: 2 * opts.radius,
width: 2 * opts.radius,
radius: null
}, opts));
},
triangle: function (surface, opts) {
opts.radius *= 1.75;
return surface.add(Ext.apply({
type: 'path',
stroke: null,
path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
}, opts));
},
diamond: function (surface, opts) {
var r = opts.radius;
r *= 1.5;
return surface.add(Ext.apply({
type: 'path',
stroke: null,
path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
}, opts));
},
cross: function (surface, opts) {
var r = opts.radius;
r = r / 1.7;
return surface.add(Ext.apply({
type: 'path',
stroke: null,
path: "M".concat(opts.x - r, ",", opts.y, "l", [-r, -r, r, -r, r, r, r, -r, r, r, -r, r, r, r, -r, r, -r, -r, -r, r, -r, -r, "z"])
}, opts));
},
plus: function (surface, opts) {
var r = opts.radius / 1.3;
return surface.add(Ext.apply({
type: 'path',
stroke: null,
path: "M".concat(opts.x - r / 2, ",", opts.y - r / 2, "l", [0, -r, r, 0, 0, r, r, 0, 0, r, -r, 0, 0, r, -r, 0, 0, -r, -r, 0, 0, -r, "z"])
}, opts));
},
arrow: function (surface, opts) {
var r = opts.radius;
return surface.add(Ext.apply({
type: 'path',
path: "M".concat(opts.x - r * 0.7, ",", opts.y - r * 0.4, "l", [r * 0.6, 0, 0, -r * 0.4, r, r * 0.8, -r, r * 0.8, 0, -r * 0.4, -r * 0.6, 0], "z")
}, opts));
},
drop: function (surface, x, y, text, size, angle) {
size = size || 30;
angle = angle || 0;
surface.add({
type: 'path',
path: ['M', x, y, 'l', size, 0, 'A', size * 0.4, size * 0.4, 0, 1, 0, x + size * 0.7, y - size * 0.7, 'z'],
fill: '#000',
stroke: 'none',
rotate: {
degrees: 22.5 - angle,
x: x,
y: y
}
});
angle = (angle + 90) * Math.PI / 180;
surface.add({
type: 'text',
x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
y: y + size * Math.cos(angle) + 5,
text: text,
'font-size': size * 12 / 40,
stroke: 'none',
fill: '#fff'
});
}
});

View File

@@ -0,0 +1,102 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.Tip
* Provides tips for Ext.chart.series.Series.
*/
Ext.define('Ext.chart.Tip', {
/* Begin Definitions */
requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
/* End Definitions */
constructor: function(config) {
var me = this,
surface,
sprites,
tipSurface;
if (config.tips) {
me.tipTimeout = null;
me.tipConfig = Ext.apply({}, config.tips, {
renderer: Ext.emptyFn,
constrainPosition: false
});
me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
me.chart.surface.on('mouseleave', function() {
me.hideTip();
});
if (me.tipConfig.surface) {
//initialize a surface
surface = me.tipConfig.surface;
sprites = surface.sprites;
tipSurface = Ext.create('Ext.chart.TipSurface', {
id: 'tipSurfaceComponent',
sprites: sprites
});
if (surface.width && surface.height) {
tipSurface.setSize(surface.width, surface.height);
}
me.tooltip.add(tipSurface);
me.spriteTip = tipSurface;
}
}
},
showTip: function(item) {
var me = this;
if (!me.tooltip) {
return;
}
clearTimeout(me.tipTimeout);
var tooltip = me.tooltip,
spriteTip = me.spriteTip,
tipConfig = me.tipConfig,
trackMouse = tooltip.trackMouse,
sprite, surface, surfaceExt, pos, x, y;
if (!trackMouse) {
tooltip.trackMouse = true;
sprite = item.sprite;
surface = sprite.surface;
surfaceExt = Ext.get(surface.getId());
if (surfaceExt) {
pos = surfaceExt.getXY();
x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
tooltip.targetXY = [x, y];
}
}
if (spriteTip) {
tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
} else {
tipConfig.renderer.call(tooltip, item.storeItem, item);
}
tooltip.show();
tooltip.trackMouse = trackMouse;
},
hideTip: function(item) {
var tooltip = this.tooltip;
if (!tooltip) {
return;
}
clearTimeout(this.tipTimeout);
this.tipTimeout = setTimeout(function() {
tooltip.hide();
}, 0);
}
});

View File

@@ -0,0 +1,58 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.TipSurface
* @ignore
*/
Ext.define('Ext.chart.TipSurface', {
/* Begin Definitions */
extend: 'Ext.draw.Component',
/* End Definitions */
spriteArray: false,
renderFirst: true,
constructor: function(config) {
this.callParent([config]);
if (config.sprites) {
this.spriteArray = [].concat(config.sprites);
delete config.sprites;
}
},
onRender: function() {
var me = this,
i = 0,
l = 0,
sp,
sprites;
this.callParent(arguments);
sprites = me.spriteArray;
if (me.renderFirst && sprites) {
me.renderFirst = false;
for (l = sprites.length; i < l; i++) {
sp = me.surface.add(sprites[i]);
sp.setAttributes({
hidden: false
},
true);
}
}
}
});

View File

@@ -0,0 +1,74 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Abstract
* Base class for all axis classes.
* @private
*/
Ext.define('Ext.chart.axis.Abstract', {
/* Begin Definitions */
requires: ['Ext.chart.Chart'],
/* End Definitions */
/**
* Creates new Axis.
* @param {Object} config (optional) Config options.
*/
constructor: function(config) {
config = config || {};
var me = this,
pos = config.position || 'left';
pos = pos.charAt(0).toUpperCase() + pos.substring(1);
//axisLabel(Top|Bottom|Right|Left)Style
config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
Ext.apply(me, config);
me.fields = [].concat(me.fields);
this.callParent();
me.labels = [];
me.getId();
me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
},
alignment: null,
grid: false,
steps: 10,
x: 0,
y: 0,
minValue: 0,
maxValue: 0,
getId: function() {
return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
},
/*
Called to process a view i.e to make aggregation and filtering over
a store creating a substore to be used to render the axis. Since many axes
may do different things on the data and we want the final result of all these
operations to be rendered we need to call processView on all axes before drawing
them.
*/
processView: Ext.emptyFn,
drawAxis: Ext.emptyFn,
addDisplayAndLabels: Ext.emptyFn
});

View File

@@ -0,0 +1,854 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Axis
* @extends Ext.chart.axis.Abstract
*
* Defines axis for charts. The axis position, type, style can be configured.
* The axes are defined in an axes array of configuration objects where the type,
* field, grid and other configuration options can be set. To know more about how
* to create a Chart please check the Chart class documentation. Here's an example for the axes part:
* An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
*
* axes: [{
* type: 'Numeric',
* grid: true,
* position: 'left',
* fields: ['data1', 'data2', 'data3'],
* title: 'Number of Hits',
* grid: {
* odd: {
* opacity: 1,
* fill: '#ddd',
* stroke: '#bbb',
* 'stroke-width': 1
* }
* },
* minimum: 0
* }, {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Month of the Year',
* grid: true,
* label: {
* rotate: {
* degrees: 315
* }
* }
* }]
*
* In this case we use a `Numeric` axis for displaying the values of the Area series and a `Category` axis for displaying the names of
* the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
* Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
* category axis the labels will be rotated so they can fit the space better.
*/
Ext.define('Ext.chart.axis.Axis', {
/* Begin Definitions */
extend: 'Ext.chart.axis.Abstract',
alternateClassName: 'Ext.chart.Axis',
requires: ['Ext.draw.Draw'],
/* End Definitions */
/**
* @cfg {Boolean/Object} grid
* The grid configuration enables you to set a background grid for an axis.
* If set to *true* on a vertical axis, vertical lines will be drawn.
* If set to *true* on a horizontal axis, horizontal lines will be drawn.
* If both are set, a proper grid with horizontal and vertical lines will be drawn.
*
* You can set specific options for the grid configuration for odd and/or even lines/rows.
* Since the rows being drawn are rectangle sprites, you can set to an odd or even property
* all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
* properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
*
* The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
* where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
*
* For example:
*
* axes: [{
* type: 'Numeric',
* grid: true,
* position: 'left',
* fields: ['data1', 'data2', 'data3'],
* title: 'Number of Hits',
* grid: {
* odd: {
* opacity: 1,
* fill: '#ddd',
* stroke: '#bbb',
* 'stroke-width': 1
* }
* }
* }, {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Month of the Year',
* grid: true
* }]
*
*/
/**
* @cfg {Number} majorTickSteps
* If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
*/
/**
* @cfg {Number} minorTickSteps
* The number of small ticks between two major ticks. Default is zero.
*/
/**
* @cfg {String} title
* The title for the Axis
*/
//@private force min/max values from store
forceMinMax: false,
/**
* @cfg {Number} dashSize
* The size of the dash marker. Default's 3.
*/
dashSize: 3,
/**
* @cfg {String} position
* Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
*/
position: 'bottom',
// @private
skipFirst: false,
/**
* @cfg {Number} length
* Offset axis position. Default's 0.
*/
length: 0,
/**
* @cfg {Number} width
* Offset axis width. Default's 0.
*/
width: 0,
majorTickSteps: false,
// @private
applyData: Ext.emptyFn,
getRange: function () {
var me = this,
store = me.chart.getChartStore(),
fields = me.fields,
ln = fields.length,
math = Math,
mmax = math.max,
mmin = math.min,
aggregate = false,
min = isNaN(me.minimum) ? Infinity : me.minimum,
max = isNaN(me.maximum) ? -Infinity : me.maximum,
total = 0, i, l, value, values, rec,
excludes = [],
series = me.chart.series.items;
//if one series is stacked I have to aggregate the values
//for the scale.
// TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
// listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
// Assuming only stack on y-axis.
// CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since
// it was constraining the minmax calculation to y-axis stacked
// visualizations.
for (i = 0, l = series.length; !aggregate && i < l; i++) {
aggregate = aggregate || series[i].stacked;
excludes = series[i].__excludes || excludes;
}
store.each(function(record) {
if (aggregate) {
if (!isFinite(min)) {
min = 0;
}
for (values = [0, 0], i = 0; i < ln; i++) {
if (excludes[i]) {
continue;
}
rec = record.get(fields[i]);
values[+(rec > 0)] += math.abs(rec);
}
max = mmax(max, -values[0], +values[1]);
min = mmin(min, -values[0], +values[1]);
}
else {
for (i = 0; i < ln; i++) {
if (excludes[i]) {
continue;
}
value = record.get(fields[i]);
max = mmax(max, +value);
min = mmin(min, +value);
}
}
});
if (!isFinite(max)) {
max = me.prevMax || 0;
}
if (!isFinite(min)) {
min = me.prevMin || 0;
}
//normalize min max for snapEnds.
if (min != max && (max != Math.floor(max))) {
max = Math.floor(max) + 1;
}
if (!isNaN(me.minimum)) {
min = me.minimum;
}
if (!isNaN(me.maximum)) {
max = me.maximum;
}
return {min: min, max: max};
},
// @private creates a structure with start, end and step points.
calcEnds: function() {
var me = this,
fields = me.fields,
range = me.getRange(),
min = range.min,
max = range.max,
outfrom, outto, out;
out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ? (me.majorTickSteps +1) : me.steps);
outfrom = out.from;
outto = out.to;
if (me.forceMinMax) {
if (!isNaN(max)) {
out.to = max;
}
if (!isNaN(min)) {
out.from = min;
}
}
if (!isNaN(me.maximum)) {
//TODO(nico) users are responsible for their own minimum/maximum values set.
//Clipping should be added to remove lines in the chart which are below the axis.
out.to = me.maximum;
}
if (!isNaN(me.minimum)) {
//TODO(nico) users are responsible for their own minimum/maximum values set.
//Clipping should be added to remove lines in the chart which are below the axis.
out.from = me.minimum;
}
//Adjust after adjusting minimum and maximum
out.step = (out.to - out.from) / (outto - outfrom) * out.step;
if (me.adjustMaximumByMajorUnit) {
out.to += out.step;
}
if (me.adjustMinimumByMajorUnit) {
out.from -= out.step;
}
me.prevMin = min == max? 0 : min;
me.prevMax = max;
return out;
},
/**
* Renders the axis into the screen and updates its position.
*/
drawAxis: function (init) {
var me = this,
i, j,
x = me.x,
y = me.y,
gutterX = me.chart.maxGutter[0],
gutterY = me.chart.maxGutter[1],
dashSize = me.dashSize,
subDashesX = me.minorTickSteps || 0,
subDashesY = me.minorTickSteps || 0,
length = me.length,
position = me.position,
inflections = [],
calcLabels = false,
stepCalcs = me.applyData(),
step = stepCalcs.step,
steps = stepCalcs.steps,
from = stepCalcs.from,
to = stepCalcs.to,
trueLength,
currentX,
currentY,
path,
prev,
dashesX,
dashesY,
delta;
//If no steps are specified
//then don't draw the axis. This generally happens
//when an empty store.
if (me.hidden || isNaN(step) || (from == to)) {
return;
}
me.from = stepCalcs.from;
me.to = stepCalcs.to;
if (position == 'left' || position == 'right') {
currentX = Math.floor(x) + 0.5;
path = ["M", currentX, y, "l", 0, -length];
trueLength = length - (gutterY * 2);
}
else {
currentY = Math.floor(y) + 0.5;
path = ["M", x, currentY, "l", length, 0];
trueLength = length - (gutterX * 2);
}
delta = trueLength / (steps || 1);
dashesX = Math.max(subDashesX +1, 0);
dashesY = Math.max(subDashesY +1, 0);
if (me.type == 'Numeric' || me.type == 'Time') {
calcLabels = true;
me.labels = [stepCalcs.from];
}
if (position == 'right' || position == 'left') {
currentY = y - gutterY;
currentX = x - ((position == 'left') * dashSize * 2);
while (currentY >= y - gutterY - trueLength) {
path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
if (currentY != y - gutterY) {
for (i = 1; i < dashesY; i++) {
path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
}
}
inflections.push([ Math.floor(x), Math.floor(currentY) ]);
currentY -= delta;
if (calcLabels) {
me.labels.push(me.labels[me.labels.length -1] + step);
}
if (delta === 0) {
break;
}
}
if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
for (i = 1; i < dashesY; i++) {
path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
}
inflections.push([ Math.floor(x), Math.floor(currentY) ]);
if (calcLabels) {
me.labels.push(me.labels[me.labels.length -1] + step);
}
}
} else {
currentX = x + gutterX;
currentY = y - ((position == 'top') * dashSize * 2);
while (currentX <= x + gutterX + trueLength) {
path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
if (currentX != x + gutterX) {
for (i = 1; i < dashesX; i++) {
path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
}
}
inflections.push([ Math.floor(currentX), Math.floor(y) ]);
currentX += delta;
if (calcLabels) {
me.labels.push(me.labels[me.labels.length -1] + step);
}
if (delta === 0) {
break;
}
}
if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
for (i = 1; i < dashesX; i++) {
path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
}
inflections.push([ Math.floor(currentX), Math.floor(y) ]);
if (calcLabels) {
me.labels.push(me.labels[me.labels.length -1] + step);
}
}
}
if (!me.axis) {
me.axis = me.chart.surface.add(Ext.apply({
type: 'path',
path: path
}, me.axisStyle));
}
me.axis.setAttributes({
path: path
}, true);
me.inflections = inflections;
if (!init && me.grid) {
me.drawGrid();
}
me.axisBBox = me.axis.getBBox();
me.drawLabel();
},
/**
* Renders an horizontal and/or vertical grid into the Surface.
*/
drawGrid: function() {
var me = this,
surface = me.chart.surface,
grid = me.grid,
odd = grid.odd,
even = grid.even,
inflections = me.inflections,
ln = inflections.length - ((odd || even)? 0 : 1),
position = me.position,
gutter = me.chart.maxGutter,
width = me.width - 2,
vert = false,
point, prevPoint,
i = 1,
path = [], styles, lineWidth, dlineWidth,
oddPath = [], evenPath = [];
if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
(gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
i = 0;
ln++;
}
for (; i < ln; i++) {
point = inflections[i];
prevPoint = inflections[i - 1];
if (odd || even) {
path = (i % 2)? oddPath : evenPath;
styles = ((i % 2)? odd : even) || {};
lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
dlineWidth = 2 * lineWidth;
if (position == 'left') {
path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
"L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
}
else if (position == 'right') {
path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
"L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
"L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
}
else if (position == 'top') {
path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
"L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
}
else {
path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
"L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
"L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
}
} else {
if (position == 'left') {
path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
}
else if (position == 'right') {
path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
}
else if (position == 'top') {
path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
}
else {
path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
}
}
}
if (odd || even) {
if (oddPath.length) {
if (!me.gridOdd && oddPath.length) {
me.gridOdd = surface.add({
type: 'path',
path: oddPath
});
}
me.gridOdd.setAttributes(Ext.apply({
path: oddPath,
hidden: false
}, odd || {}), true);
}
if (evenPath.length) {
if (!me.gridEven) {
me.gridEven = surface.add({
type: 'path',
path: evenPath
});
}
me.gridEven.setAttributes(Ext.apply({
path: evenPath,
hidden: false
}, even || {}), true);
}
}
else {
if (path.length) {
if (!me.gridLines) {
me.gridLines = me.chart.surface.add({
type: 'path',
path: path,
"stroke-width": me.lineWidth || 1,
stroke: me.gridColor || '#ccc'
});
}
me.gridLines.setAttributes({
hidden: false,
path: path
}, true);
}
else if (me.gridLines) {
me.gridLines.hide(true);
}
}
},
//@private
getOrCreateLabel: function(i, text) {
var me = this,
labelGroup = me.labelGroup,
textLabel = labelGroup.getAt(i),
surface = me.chart.surface;
if (textLabel) {
if (text != textLabel.attr.text) {
textLabel.setAttributes(Ext.apply({
text: text
}, me.label), true);
textLabel._bbox = textLabel.getBBox();
}
}
else {
textLabel = surface.add(Ext.apply({
group: labelGroup,
type: 'text',
x: 0,
y: 0,
text: text
}, me.label));
surface.renderItem(textLabel);
textLabel._bbox = textLabel.getBBox();
}
//get untransformed bounding box
if (me.label.rotation) {
textLabel.setAttributes({
rotation: {
degrees: 0
}
}, true);
textLabel._ubbox = textLabel.getBBox();
textLabel.setAttributes(me.label, true);
} else {
textLabel._ubbox = textLabel._bbox;
}
return textLabel;
},
rect2pointArray: function(sprite) {
var surface = this.chart.surface,
rect = surface.getBBox(sprite, true),
p1 = [rect.x, rect.y],
p1p = p1.slice(),
p2 = [rect.x + rect.width, rect.y],
p2p = p2.slice(),
p3 = [rect.x + rect.width, rect.y + rect.height],
p3p = p3.slice(),
p4 = [rect.x, rect.y + rect.height],
p4p = p4.slice(),
matrix = sprite.matrix;
//transform the points
p1[0] = matrix.x.apply(matrix, p1p);
p1[1] = matrix.y.apply(matrix, p1p);
p2[0] = matrix.x.apply(matrix, p2p);
p2[1] = matrix.y.apply(matrix, p2p);
p3[0] = matrix.x.apply(matrix, p3p);
p3[1] = matrix.y.apply(matrix, p3p);
p4[0] = matrix.x.apply(matrix, p4p);
p4[1] = matrix.y.apply(matrix, p4p);
return [p1, p2, p3, p4];
},
intersect: function(l1, l2) {
var r1 = this.rect2pointArray(l1),
r2 = this.rect2pointArray(l2);
return !!Ext.draw.Draw.intersect(r1, r2).length;
},
drawHorizontalLabels: function() {
var me = this,
labelConf = me.label,
floor = Math.floor,
max = Math.max,
axes = me.chart.axes,
position = me.position,
inflections = me.inflections,
ln = inflections.length,
labels = me.labels,
labelGroup = me.labelGroup,
maxHeight = 0,
ratio,
gutterY = me.chart.maxGutter[1],
ubbox, bbox, point, prevX, prevLabel,
projectedWidth = 0,
textLabel, attr, textRight, text,
label, last, x, y, i, firstLabel;
last = ln - 1;
//get a reference to the first text label dimensions
point = inflections[0];
firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
for (i = 0; i < ln; i++) {
point = inflections[i];
text = me.label.renderer(labels[i]);
textLabel = me.getOrCreateLabel(i, text);
bbox = textLabel._bbox;
maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
if (me.chart.maxGutter[0] == 0) {
if (i == 0 && axes.findIndex('position', 'left') == -1) {
x = point[0];
}
else if (i == last && axes.findIndex('position', 'right') == -1) {
x = point[0] - bbox.width;
}
}
if (position == 'top') {
y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
}
else {
y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
}
textLabel.setAttributes({
hidden: false,
x: x,
y: y
}, true);
// Skip label if there isn't available minimum space
if (i != 0 && (me.intersect(textLabel, prevLabel)
|| me.intersect(textLabel, firstLabel))) {
textLabel.hide(true);
continue;
}
prevLabel = textLabel;
}
return maxHeight;
},
drawVerticalLabels: function() {
var me = this,
inflections = me.inflections,
position = me.position,
ln = inflections.length,
labels = me.labels,
maxWidth = 0,
max = Math.max,
floor = Math.floor,
ceil = Math.ceil,
axes = me.chart.axes,
gutterY = me.chart.maxGutter[1],
ubbox, bbox, point, prevLabel,
projectedWidth = 0,
textLabel, attr, textRight, text,
label, last, x, y, i;
last = ln;
for (i = 0; i < last; i++) {
point = inflections[i];
text = me.label.renderer(labels[i]);
textLabel = me.getOrCreateLabel(i, text);
bbox = textLabel._bbox;
maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
y = point[1];
if (gutterY < bbox.height / 2) {
if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
y = me.y - me.length + ceil(bbox.height / 2);
}
else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
y = me.y - floor(bbox.height / 2);
}
}
if (position == 'left') {
x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
}
else {
x = point[0] + me.dashSize + me.label.padding + 2;
}
textLabel.setAttributes(Ext.apply({
hidden: false,
x: x,
y: y
}, me.label), true);
// Skip label if there isn't available minimum space
if (i != 0 && me.intersect(textLabel, prevLabel)) {
textLabel.hide(true);
continue;
}
prevLabel = textLabel;
}
return maxWidth;
},
/**
* Renders the labels in the axes.
*/
drawLabel: function() {
var me = this,
position = me.position,
labelGroup = me.labelGroup,
inflections = me.inflections,
maxWidth = 0,
maxHeight = 0,
ln, i;
if (position == 'left' || position == 'right') {
maxWidth = me.drawVerticalLabels();
} else {
maxHeight = me.drawHorizontalLabels();
}
// Hide unused bars
ln = labelGroup.getCount();
i = inflections.length;
for (; i < ln; i++) {
labelGroup.getAt(i).hide(true);
}
me.bbox = {};
Ext.apply(me.bbox, me.axisBBox);
me.bbox.height = maxHeight;
me.bbox.width = maxWidth;
if (Ext.isString(me.title)) {
me.drawTitle(maxWidth, maxHeight);
}
},
// @private creates the elipsis for the text.
elipsis: function(sprite, text, desiredWidth, minWidth, center) {
var bbox,
x;
if (desiredWidth < minWidth) {
sprite.hide(true);
return false;
}
while (text.length > 4) {
text = text.substr(0, text.length - 4) + "...";
sprite.setAttributes({
text: text
}, true);
bbox = sprite.getBBox();
if (bbox.width < desiredWidth) {
if (typeof center == 'number') {
sprite.setAttributes({
x: Math.floor(center - (bbox.width / 2))
}, true);
}
break;
}
}
return true;
},
/**
* Updates the {@link #title} of this axis.
* @param {String} title
*/
setTitle: function(title) {
this.title = title;
this.drawLabel();
},
// @private draws the title for the axis.
drawTitle: function(maxWidth, maxHeight) {
var me = this,
position = me.position,
surface = me.chart.surface,
displaySprite = me.displaySprite,
title = me.title,
rotate = (position == 'left' || position == 'right'),
x = me.x,
y = me.y,
base, bbox, pad;
if (displaySprite) {
displaySprite.setAttributes({text: title}, true);
} else {
base = {
type: 'text',
x: 0,
y: 0,
text: title
};
displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
surface.renderItem(displaySprite);
}
bbox = displaySprite.getBBox();
pad = me.dashSize + me.label.padding;
if (rotate) {
y -= ((me.length / 2) - (bbox.height / 2));
if (position == 'left') {
x -= (maxWidth + pad + (bbox.width / 2));
}
else {
x += (maxWidth + pad + bbox.width - (bbox.width / 2));
}
me.bbox.width += bbox.width + 10;
}
else {
x += (me.length / 2) - (bbox.width * 0.5);
if (position == 'top') {
y -= (maxHeight + pad + (bbox.height * 0.3));
}
else {
y += (maxHeight + pad + (bbox.height * 0.8));
}
me.bbox.height += bbox.height + 10;
}
displaySprite.setAttributes({
translate: {
x: x,
y: y
}
}, true);
}
});

View File

@@ -0,0 +1,144 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Category
* @extends Ext.chart.axis.Axis
*
* A type of axis that displays items in categories. This axis is generally used to
* display categorical information like names of items, month names, quarters, etc.
* but no quantitative values. For that other type of information `Number`
* axis are more suitable.
*
* As with other axis you can set the position of the axis and its title. For example:
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
* {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
* {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
* {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
* {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* store: store,
* axes: [{
* type: 'Numeric',
* grid: true,
* position: 'left',
* fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
* title: 'Sample Values',
* grid: {
* odd: {
* opacity: 1,
* fill: '#ddd',
* stroke: '#bbb',
* 'stroke-width': 1
* }
* },
* minimum: 0,
* adjustMinimumByMajorUnit: 0
* }, {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics',
* grid: true,
* label: {
* rotate: {
* degrees: 315
* }
* }
* }],
* series: [{
* type: 'area',
* highlight: false,
* axis: 'left',
* xField: 'name',
* yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
* style: {
* opacity: 0.93
* }
* }]
* });
*
* In this example with set the category axis to the bottom of the surface, bound the axis to
* the `name` property and set as title _Month of the Year_.
*/
Ext.define('Ext.chart.axis.Category', {
/* Begin Definitions */
extend: 'Ext.chart.axis.Axis',
alternateClassName: 'Ext.chart.CategoryAxis',
alias: 'axis.category',
/* End Definitions */
/**
* A list of category names to display along this axis.
* @property {String} categoryNames
*/
categoryNames: null,
/**
* Indicates whether or not to calculate the number of categories (ticks and
* labels) when there is not enough room to display all labels on the axis.
* If set to true, the axis will determine the number of categories to plot.
* If not, all categories will be plotted.
*
* @property calculateCategoryCount
* @type Boolean
*/
calculateCategoryCount: false,
// @private creates an array of labels to be used when rendering.
setLabels: function() {
var store = this.chart.store,
fields = this.fields,
ln = fields.length,
i;
this.labels = [];
store.each(function(record) {
for (i = 0; i < ln; i++) {
this.labels.push(record.get(fields[i]));
}
}, this);
},
// @private calculates labels positions and marker positions for rendering.
applyData: function() {
this.callParent();
this.setLabels();
var count = this.chart.store.getCount();
return {
from: 0,
to: count,
power: 1,
step: 1,
steps: count - 1
};
}
});

View File

@@ -0,0 +1,217 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Gauge
* @extends Ext.chart.axis.Abstract
*
* Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
* displays numeric data from an interval defined by the `minimum`, `maximum` and
* `step` configuration properties. The placement of the numeric data can be changed
* by altering the `margin` option that is set to `10` by default.
*
* A possible configuration for this axis would look like:
*
* axes: [{
* type: 'gauge',
* position: 'gauge',
* minimum: 0,
* maximum: 100,
* steps: 10,
* margin: 7
* }],
*/
Ext.define('Ext.chart.axis.Gauge', {
/* Begin Definitions */
extend: 'Ext.chart.axis.Abstract',
/* End Definitions */
/**
* @cfg {Number} minimum (required)
* The minimum value of the interval to be displayed in the axis.
*/
/**
* @cfg {Number} maximum (required)
* The maximum value of the interval to be displayed in the axis.
*/
/**
* @cfg {Number} steps (required)
* The number of steps and tick marks to add to the interval.
*/
/**
* @cfg {Number} [margin=10]
* The offset positioning of the tick marks and labels in pixels.
*/
/**
* @cfg {String} title
* The title for the Axis.
*/
position: 'gauge',
alias: 'axis.gauge',
drawAxis: function(init) {
var chart = this.chart,
surface = chart.surface,
bbox = chart.chartBBox,
centerX = bbox.x + (bbox.width / 2),
centerY = bbox.y + bbox.height,
margin = this.margin || 10,
rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
sprites = [], sprite,
steps = this.steps,
i, pi = Math.PI,
cos = Math.cos,
sin = Math.sin;
if (this.sprites && !chart.resizing) {
this.drawLabel();
return;
}
if (this.margin >= 0) {
if (!this.sprites) {
//draw circles
for (i = 0; i <= steps; i++) {
sprite = surface.add({
type: 'path',
path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
centerY + (rho - margin) * sin(i / steps * pi - pi),
'L', centerX + rho * cos(i / steps * pi - pi),
centerY + rho * sin(i / steps * pi - pi), 'Z'],
stroke: '#ccc'
});
sprite.setAttributes({
hidden: false
}, true);
sprites.push(sprite);
}
} else {
sprites = this.sprites;
//draw circles
for (i = 0; i <= steps; i++) {
sprites[i].setAttributes({
path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
centerY + (rho - margin) * sin(i / steps * pi - pi),
'L', centerX + rho * cos(i / steps * pi - pi),
centerY + rho * sin(i / steps * pi - pi), 'Z'],
stroke: '#ccc'
}, true);
}
}
}
this.sprites = sprites;
this.drawLabel();
if (this.title) {
this.drawTitle();
}
},
drawTitle: function() {
var me = this,
chart = me.chart,
surface = chart.surface,
bbox = chart.chartBBox,
labelSprite = me.titleSprite,
labelBBox;
if (!labelSprite) {
me.titleSprite = labelSprite = surface.add({
type: 'text',
zIndex: 2
});
}
labelSprite.setAttributes(Ext.apply({
text: me.title
}, me.label || {}), true);
labelBBox = labelSprite.getBBox();
labelSprite.setAttributes({
x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
}, true);
},
/**
* Updates the {@link #title} of this axis.
* @param {String} title
*/
setTitle: function(title) {
this.title = title;
this.drawTitle();
},
drawLabel: function() {
var chart = this.chart,
surface = chart.surface,
bbox = chart.chartBBox,
centerX = bbox.x + (bbox.width / 2),
centerY = bbox.y + bbox.height,
margin = this.margin || 10,
rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
round = Math.round,
labelArray = [], label,
maxValue = this.maximum || 0,
steps = this.steps, i = 0,
adjY,
pi = Math.PI,
cos = Math.cos,
sin = Math.sin,
labelConf = this.label,
renderer = labelConf.renderer || function(v) { return v; };
if (!this.labelArray) {
//draw scale
for (i = 0; i <= steps; i++) {
// TODO Adjust for height of text / 2 instead
adjY = (i === 0 || i === steps) ? 7 : 0;
label = surface.add({
type: 'text',
text: renderer(round(i / steps * maxValue)),
x: centerX + rho * cos(i / steps * pi - pi),
y: centerY + rho * sin(i / steps * pi - pi) - adjY,
'text-anchor': 'middle',
'stroke-width': 0.2,
zIndex: 10,
stroke: '#333'
});
label.setAttributes({
hidden: false
}, true);
labelArray.push(label);
}
}
else {
labelArray = this.labelArray;
//draw values
for (i = 0; i <= steps; i++) {
// TODO Adjust for height of text / 2 instead
adjY = (i === 0 || i === steps) ? 7 : 0;
labelArray[i].setAttributes({
text: renderer(round(i / steps * maxValue)),
x: centerX + rho * cos(i / steps * pi - pi),
y: centerY + rho * sin(i / steps * pi - pi) - adjY
}, true);
}
}
this.labelArray = labelArray;
}
});

View File

@@ -0,0 +1,186 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Numeric
* @extends Ext.chart.axis.Axis
*
* An axis to handle numeric values. This axis is used for quantitative data as
* opposed to the category axis. You can set mininum and maximum values to the
* axis so that the values are bound to that. If no values are set, then the
* scale will auto-adjust to the values.
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
* {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
* {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
* {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
* {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* store: store,
* axes: [{
* type: 'Numeric',
* grid: true,
* position: 'left',
* fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
* title: 'Sample Values',
* grid: {
* odd: {
* opacity: 1,
* fill: '#ddd',
* stroke: '#bbb',
* 'stroke-width': 1
* }
* },
* minimum: 0,
* adjustMinimumByMajorUnit: 0
* }, {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics',
* grid: true,
* label: {
* rotate: {
* degrees: 315
* }
* }
* }],
* series: [{
* type: 'area',
* highlight: false,
* axis: 'left',
* xField: 'name',
* yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
* style: {
* opacity: 0.93
* }
* }]
* });
*
* In this example we create an axis of Numeric type. We set a minimum value so that
* even if all series have values greater than zero, the grid starts at zero. We bind
* the axis onto the left part of the surface by setting `position` to `left`.
* We bind three different store fields to this axis by setting `fields` to an array.
* We set the title of the axis to _Number of Hits_ by using the `title` property.
* We use a `grid` configuration to set odd background rows to a certain style and even rows
* to be transparent/ignored.
*/
Ext.define('Ext.chart.axis.Numeric', {
/* Begin Definitions */
extend: 'Ext.chart.axis.Axis',
alternateClassName: 'Ext.chart.NumericAxis',
/* End Definitions */
type: 'numeric',
alias: 'axis.numeric',
constructor: function(config) {
var me = this,
hasLabel = !!(config.label && config.label.renderer),
label;
me.callParent([config]);
label = me.label;
if (me.roundToDecimal === false) {
return;
}
if (!hasLabel) {
label.renderer = function(v) {
return me.roundToDecimal(v, me.decimals);
};
}
},
roundToDecimal: function(v, dec) {
var val = Math.pow(10, dec || 0);
return Math.floor(v * val) / val;
},
/**
* The minimum value drawn by the axis. If not set explicitly, the axis
* minimum will be calculated automatically.
*
* @property {Number} minimum
*/
minimum: NaN,
/**
* The maximum value drawn by the axis. If not set explicitly, the axis
* maximum will be calculated automatically.
*
* @property {Number} maximum
*/
maximum: NaN,
/**
* The number of decimals to round the value to.
*
* @property {Number} decimals
*/
decimals: 2,
/**
* The scaling algorithm to use on this axis. May be "linear" or
* "logarithmic". Currently only linear scale is implemented.
*
* @property {String} scale
* @private
*/
scale: "linear",
/**
* Indicates the position of the axis relative to the chart
*
* @property {String} position
*/
position: 'left',
/**
* Indicates whether to extend maximum beyond data's maximum to the nearest
* majorUnit.
*
* @property {Boolean} adjustMaximumByMajorUnit
*/
adjustMaximumByMajorUnit: false,
/**
* Indicates whether to extend the minimum beyond data's minimum to the
* nearest majorUnit.
*
* @property {Boolean} adjustMinimumByMajorUnit
*/
adjustMinimumByMajorUnit: false,
// @private apply data.
applyData: function() {
this.callParent();
return this.calcEnds();
}
});

View File

@@ -0,0 +1,214 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Radial
* @extends Ext.chart.axis.Abstract
* @ignore
*/
Ext.define('Ext.chart.axis.Radial', {
/* Begin Definitions */
extend: 'Ext.chart.axis.Abstract',
/* End Definitions */
position: 'radial',
alias: 'axis.radial',
drawAxis: function(init) {
var chart = this.chart,
surface = chart.surface,
bbox = chart.chartBBox,
store = chart.store,
l = store.getCount(),
centerX = bbox.x + (bbox.width / 2),
centerY = bbox.y + (bbox.height / 2),
rho = Math.min(bbox.width, bbox.height) /2,
sprites = [], sprite,
steps = this.steps,
i, j, pi2 = Math.PI * 2,
cos = Math.cos, sin = Math.sin;
if (this.sprites && !chart.resizing) {
this.drawLabel();
return;
}
if (!this.sprites) {
//draw circles
for (i = 1; i <= steps; i++) {
sprite = surface.add({
type: 'circle',
x: centerX,
y: centerY,
radius: Math.max(rho * i / steps, 0),
stroke: '#ccc'
});
sprite.setAttributes({
hidden: false
}, true);
sprites.push(sprite);
}
//draw lines
store.each(function(rec, i) {
sprite = surface.add({
type: 'path',
path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
stroke: '#ccc'
});
sprite.setAttributes({
hidden: false
}, true);
sprites.push(sprite);
});
} else {
sprites = this.sprites;
//draw circles
for (i = 0; i < steps; i++) {
sprites[i].setAttributes({
x: centerX,
y: centerY,
radius: Math.max(rho * (i + 1) / steps, 0),
stroke: '#ccc'
}, true);
}
//draw lines
store.each(function(rec, j) {
sprites[i + j].setAttributes({
path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
stroke: '#ccc'
}, true);
});
}
this.sprites = sprites;
this.drawLabel();
},
drawLabel: function() {
var chart = this.chart,
surface = chart.surface,
bbox = chart.chartBBox,
store = chart.store,
centerX = bbox.x + (bbox.width / 2),
centerY = bbox.y + (bbox.height / 2),
rho = Math.min(bbox.width, bbox.height) /2,
max = Math.max, round = Math.round,
labelArray = [], label,
fields = [], nfields,
categories = [], xField,
aggregate = !this.maximum,
maxValue = this.maximum || 0,
steps = this.steps, i = 0, j, dx, dy,
pi2 = Math.PI * 2,
cos = Math.cos, sin = Math.sin,
display = this.label.display,
draw = display !== 'none',
margin = 10;
if (!draw) {
return;
}
//get all rendered fields
chart.series.each(function(series) {
fields.push(series.yField);
xField = series.xField;
});
//get maxValue to interpolate
store.each(function(record, i) {
if (aggregate) {
for (i = 0, nfields = fields.length; i < nfields; i++) {
maxValue = max(+record.get(fields[i]), maxValue);
}
}
categories.push(record.get(xField));
});
if (!this.labelArray) {
if (display != 'categories') {
//draw scale
for (i = 1; i <= steps; i++) {
label = surface.add({
type: 'text',
text: round(i / steps * maxValue),
x: centerX,
y: centerY - rho * i / steps,
'text-anchor': 'middle',
'stroke-width': 0.1,
stroke: '#333'
});
label.setAttributes({
hidden: false
}, true);
labelArray.push(label);
}
}
if (display != 'scale') {
//draw text
for (j = 0, steps = categories.length; j < steps; j++) {
dx = cos(j / steps * pi2) * (rho + margin);
dy = sin(j / steps * pi2) * (rho + margin);
label = surface.add({
type: 'text',
text: categories[j],
x: centerX + dx,
y: centerY + dy,
'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
});
label.setAttributes({
hidden: false
}, true);
labelArray.push(label);
}
}
}
else {
labelArray = this.labelArray;
if (display != 'categories') {
//draw values
for (i = 0; i < steps; i++) {
labelArray[i].setAttributes({
text: round((i + 1) / steps * maxValue),
x: centerX,
y: centerY - rho * (i + 1) / steps,
'text-anchor': 'middle',
'stroke-width': 0.1,
stroke: '#333'
}, true);
}
}
if (display != 'scale') {
//draw text
for (j = 0, steps = categories.length; j < steps; j++) {
dx = cos(j / steps * pi2) * (rho + margin);
dy = sin(j / steps * pi2) * (rho + margin);
if (labelArray[i + j]) {
labelArray[i + j].setAttributes({
type: 'text',
text: categories[j],
x: centerX + dx,
y: centerY + dy,
'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
}, true);
}
}
}
}
this.labelArray = labelArray;
}
});

View File

@@ -0,0 +1,181 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.axis.Time
* @extends Ext.chart.axis.Numeric
*
* A type of axis whose units are measured in time values. Use this axis
* for listing dates that you will want to group or dynamically change.
* If you just want to display dates as categories then use the
* Category class for axis instead.
*
* For example:
*
* axes: [{
* type: 'Time',
* position: 'bottom',
* fields: 'date',
* title: 'Day',
* dateFormat: 'M d',
*
* constrain: true,
* fromDate: new Date('1/1/11'),
* toDate: new Date('1/7/11')
* }]
*
* In this example we're creating a time axis that has as title *Day*.
* The field the axis is bound to is `date`.
* The date format to use to display the text for the axis labels is `M d`
* which is a three letter month abbreviation followed by the day number.
* The time axis will show values for dates between `fromDate` and `toDate`.
* Since `constrain` is set to true all other values for other dates not between
* the fromDate and toDate will not be displayed.
*
*/
Ext.define('Ext.chart.axis.Time', {
/* Begin Definitions */
extend: 'Ext.chart.axis.Numeric',
alternateClassName: 'Ext.chart.TimeAxis',
alias: 'axis.time',
requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
/* End Definitions */
/**
* @cfg {String/Boolean} dateFormat
* Indicates the format the date will be rendered on.
* For example: 'M d' will render the dates as 'Jan 30', etc.
* For a list of possible format strings see {@link Ext.Date Date}
*/
dateFormat: false,
/**
* @cfg {Date} fromDate The starting date for the time axis.
*/
fromDate: false,
/**
* @cfg {Date} toDate The ending date for the time axis.
*/
toDate: false,
/**
* @cfg {Array/Boolean} step
* An array with two components: The first is the unit of the step (day, month, year, etc).
* The second one is the number of units for the step (1, 2, etc.).
* Defaults to `[Ext.Date.DAY, 1]`.
*/
step: [Ext.Date.DAY, 1],
/**
* @cfg {Boolean} constrain
* If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
* If false, the time axis will adapt to the new values by adding/removing steps.
*/
constrain: false,
// Avoid roundtoDecimal call in Numeric Axis's constructor
roundToDecimal: false,
constructor: function (config) {
var me = this, label, f, df;
me.callParent([config]);
label = me.label || {};
df = this.dateFormat;
if (df) {
if (label.renderer) {
f = label.renderer;
label.renderer = function(v) {
v = f(v);
return Ext.Date.format(new Date(f(v)), df);
};
} else {
label.renderer = function(v) {
return Ext.Date.format(new Date(v >> 0), df);
};
}
}
},
doConstrain: function () {
var me = this,
store = me.chart.store,
data = [],
series = me.chart.series.items,
math = Math,
mmax = math.max,
mmin = math.min,
fields = me.fields,
ln = fields.length,
range = me.getRange(),
min = range.min, max = range.max, i, l, excludes = [],
value, values, rec, data = [];
for (i = 0, l = series.length; i < l; i++) {
excludes[i] = series[i].__excludes;
}
store.each(function(record) {
for (i = 0; i < ln; i++) {
if (excludes[i]) {
continue;
}
value = record.get(fields[i]);
if (+value < +min) return;
if (+value > +max) return;
}
data.push(record);
})
me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
},
// Before rendering, set current default step count to be number of records.
processView: function () {
var me = this;
if (me.fromDate) {
me.minimum = +me.fromDate;
}
if (me.toDate) {
me.maximum = +me.toDate;
}
if (me.constrain) {
me.doConstrain();
}
},
// @private modifies the store and creates the labels for the axes.
calcEnds: function() {
var me = this, range, step = me.step;
if (step) {
range = me.getRange();
range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
if (me.minimum) {
range.from = me.minimum;
}
if (me.maximum) {
range.to = me.maximum;
}
range.step = (range.to - range.from) / range.steps;
return range;
} else {
return me.callParent(arguments);
}
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,833 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
* different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
* Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
* A typical configuration object for the bar series could be:
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* { 'name': 'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13 },
* { 'name': 'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3 },
* { 'name': 'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7 },
* { 'name': 'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23 },
* { 'name': 'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* store: store,
* axes: [{
* type: 'Numeric',
* position: 'bottom',
* fields: ['data1'],
* label: {
* renderer: Ext.util.Format.numberRenderer('0,0')
* },
* title: 'Sample Values',
* grid: true,
* minimum: 0
* }, {
* type: 'Category',
* position: 'left',
* fields: ['name'],
* title: 'Sample Metrics'
* }],
* series: [{
* type: 'bar',
* axis: 'bottom',
* highlight: true,
* tips: {
* trackMouse: true,
* width: 140,
* height: 28,
* renderer: function(storeItem, item) {
* this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
* }
* },
* label: {
* display: 'insideEnd',
* field: 'data1',
* renderer: Ext.util.Format.numberRenderer('0'),
* orientation: 'horizontal',
* color: '#333',
* 'text-anchor': 'middle'
* },
* xField: 'name',
* yField: ['data1']
* }]
* });
*
* In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
* xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
* animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
* to display the information found in the `data1` property of each element store, to render a formated text with the
* `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
* other styles like `color`, `text-anchor`, etc.
*/
Ext.define('Ext.chart.series.Bar', {
/* Begin Definitions */
extend: 'Ext.chart.series.Cartesian',
alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
/* End Definitions */
type: 'bar',
alias: 'series.bar',
/**
* @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
*/
column: false,
/**
* @cfg style Style properties that will override the theming series styles.
*/
style: {},
/**
* @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
*/
gutter: 38.2,
/**
* @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
*/
groupGutter: 38.2,
/**
* @cfg {Number} xPadding Padding between the left/right axes and the bars
*/
xPadding: 0,
/**
* @cfg {Number} yPadding Padding between the top/bottom axes and the bars
*/
yPadding: 10,
constructor: function(config) {
this.callParent(arguments);
var me = this,
surface = me.chart.surface,
shadow = me.chart.shadow,
i, l;
Ext.apply(me, config, {
highlightCfg: {
lineWidth: 3,
stroke: '#55c',
opacity: 0.8,
color: '#f00'
},
shadowAttributes: [{
"stroke-width": 6,
"stroke-opacity": 0.05,
stroke: 'rgb(200, 200, 200)',
translate: {
x: 1.2,
y: 1.2
}
}, {
"stroke-width": 4,
"stroke-opacity": 0.1,
stroke: 'rgb(150, 150, 150)',
translate: {
x: 0.9,
y: 0.9
}
}, {
"stroke-width": 2,
"stroke-opacity": 0.15,
stroke: 'rgb(100, 100, 100)',
translate: {
x: 0.6,
y: 0.6
}
}]
});
me.group = surface.getGroup(me.seriesId + '-bars');
if (shadow) {
for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
}
}
},
// @private sets the bar girth.
getBarGirth: function() {
var me = this,
store = me.chart.getChartStore(),
column = me.column,
ln = store.getCount(),
gutter = me.gutter / 100;
return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
},
// @private returns the gutters.
getGutters: function() {
var me = this,
column = me.column,
gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
return me.column ? [gutter, 0] : [0, gutter];
},
// @private Get chart and data boundaries
getBounds: function() {
var me = this,
chart = me.chart,
store = chart.getChartStore(),
bars = [].concat(me.yField),
barsLen = bars.length,
groupBarsLen = barsLen,
groupGutter = me.groupGutter / 100,
column = me.column,
xPadding = me.xPadding,
yPadding = me.yPadding,
stacked = me.stacked,
barWidth = me.getBarGirth(),
math = Math,
mmax = math.max,
mabs = math.abs,
groupBarWidth, bbox, minY, maxY, axis, out,
scale, zero, total, rec, j, plus, minus;
me.setBBox(true);
bbox = me.bbox;
//Skip excluded series
if (me.__excludes) {
for (j = 0, total = me.__excludes.length; j < total; j++) {
if (me.__excludes[j]) {
groupBarsLen--;
}
}
}
if (me.axis) {
axis = chart.axes.get(me.axis);
if (axis) {
out = axis.calcEnds();
minY = out.from;
maxY = out.to;
}
}
if (me.yField && !Ext.isNumber(minY)) {
axis = Ext.create('Ext.chart.axis.Axis', {
chart: chart,
fields: [].concat(me.yField)
});
out = axis.calcEnds();
minY = out.from;
maxY = out.to;
}
if (!Ext.isNumber(minY)) {
minY = 0;
}
if (!Ext.isNumber(maxY)) {
maxY = 0;
}
scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
if (stacked) {
total = [[], []];
store.each(function(record, i) {
total[0][i] = total[0][i] || 0;
total[1][i] = total[1][i] || 0;
for (j = 0; j < barsLen; j++) {
if (me.__excludes && me.__excludes[j]) {
continue;
}
rec = record.get(bars[j]);
total[+(rec > 0)][i] += mabs(rec);
}
});
total[+(maxY > 0)].push(mabs(maxY));
total[+(minY > 0)].push(mabs(minY));
minus = mmax.apply(math, total[0]);
plus = mmax.apply(math, total[1]);
scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
zero = zero + minus * scale * (column ? -1 : 1);
}
else if (minY / maxY < 0) {
zero = zero - minY * scale * (column ? -1 : 1);
}
return {
bars: bars,
bbox: bbox,
barsLen: barsLen,
groupBarsLen: groupBarsLen,
barWidth: barWidth,
groupBarWidth: groupBarWidth,
scale: scale,
zero: zero,
xPadding: xPadding,
yPadding: yPadding,
signed: minY / maxY < 0,
minY: minY,
maxY: maxY
};
},
// @private Build an array of paths for the chart
getPaths: function() {
var me = this,
chart = me.chart,
store = chart.getChartStore(),
bounds = me.bounds = me.getBounds(),
items = me.items = [],
gutter = me.gutter / 100,
groupGutter = me.groupGutter / 100,
animate = chart.animate,
column = me.column,
group = me.group,
enableShadows = chart.shadow,
shadowGroups = me.shadowGroups,
shadowAttributes = me.shadowAttributes,
shadowGroupsLn = shadowGroups.length,
bbox = bounds.bbox,
xPadding = me.xPadding,
yPadding = me.yPadding,
stacked = me.stacked,
barsLen = bounds.barsLen,
colors = me.colorArrayStyle,
colorLength = colors && colors.length || 0,
math = Math,
mmax = math.max,
mmin = math.min,
mabs = math.abs,
j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
shadowIndex, shadow, sprite, offset, floorY;
store.each(function(record, i, total) {
bottom = bounds.zero;
top = bounds.zero;
totalDim = 0;
totalNegDim = 0;
hasShadow = false;
for (j = 0, counter = 0; j < barsLen; j++) {
// Excluded series
if (me.__excludes && me.__excludes[j]) {
continue;
}
yValue = record.get(bounds.bars[j]);
height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
barAttr = {
fill: colors[(barsLen > 1 ? j : 0) % colorLength]
};
if (column) {
Ext.apply(barAttr, {
height: height,
width: mmax(bounds.groupBarWidth, 0),
x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
y: bottom - height
});
}
else {
// draw in reverse order
offset = (total - 1) - i;
Ext.apply(barAttr, {
height: mmax(bounds.groupBarWidth, 0),
width: height + (bottom == bounds.zero),
x: bottom + (bottom != bounds.zero),
y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
});
}
if (height < 0) {
if (column) {
barAttr.y = top;
barAttr.height = mabs(height);
} else {
barAttr.x = top + height;
barAttr.width = mabs(height);
}
}
if (stacked) {
if (height < 0) {
top += height * (column ? -1 : 1);
} else {
bottom += height * (column ? -1 : 1);
}
totalDim += mabs(height);
if (height < 0) {
totalNegDim += mabs(height);
}
}
barAttr.x = Math.floor(barAttr.x) + 1;
floorY = Math.floor(barAttr.y);
if (!Ext.isIE9 && barAttr.y > floorY) {
floorY--;
}
barAttr.y = floorY;
barAttr.width = Math.floor(barAttr.width);
barAttr.height = Math.floor(barAttr.height);
items.push({
series: me,
storeItem: record,
value: [record.get(me.xField), yValue],
attr: barAttr,
point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
[yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
});
// When resizing, reset before animating
if (animate && chart.resizing) {
attrs = column ? {
x: barAttr.x,
y: bounds.zero,
width: barAttr.width,
height: 0
} : {
x: bounds.zero,
y: barAttr.y,
width: 0,
height: barAttr.height
};
if (enableShadows && (stacked && !hasShadow || !stacked)) {
hasShadow = true;
//update shadows
for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
if (shadow) {
shadow.setAttributes(attrs, true);
}
}
}
//update sprite position and width/height
sprite = group.getAt(i * barsLen + j);
if (sprite) {
sprite.setAttributes(attrs, true);
}
}
counter++;
}
if (stacked && items.length) {
items[i * counter].totalDim = totalDim;
items[i * counter].totalNegDim = totalNegDim;
}
}, me);
},
// @private render/setAttributes on the shadows
renderShadows: function(i, barAttr, baseAttrs, bounds) {
var me = this,
chart = me.chart,
surface = chart.surface,
animate = chart.animate,
stacked = me.stacked,
shadowGroups = me.shadowGroups,
shadowAttributes = me.shadowAttributes,
shadowGroupsLn = shadowGroups.length,
store = chart.getChartStore(),
column = me.column,
items = me.items,
shadows = [],
zero = bounds.zero,
shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
j = i / bounds.groupBarsLen;
//create shadows
for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
if (!shadow) {
shadow = surface.add(Ext.apply({
type: 'rect',
group: shadowGroups[shadowIndex]
}, Ext.apply({}, baseAttrs, shadowBarAttr)));
}
if (stacked) {
totalDim = items[i].totalDim;
totalNegDim = items[i].totalNegDim;
if (column) {
shadowBarAttr.y = zero - totalNegDim;
shadowBarAttr.height = totalDim;
}
else {
shadowBarAttr.x = zero - totalNegDim;
shadowBarAttr.width = totalDim;
}
}
if (animate) {
if (!stacked) {
rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
me.onAnimate(shadow, { to: rendererAttributes });
}
else {
rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
shadow.setAttributes(rendererAttributes, true);
}
}
else {
rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
shadow.setAttributes(rendererAttributes, true);
}
shadows.push(shadow);
}
}
return shadows;
},
/**
* Draws the series for the current chart.
*/
drawSeries: function() {
var me = this,
chart = me.chart,
store = chart.getChartStore(),
surface = chart.surface,
animate = chart.animate,
stacked = me.stacked,
column = me.column,
enableShadows = chart.shadow,
shadowGroups = me.shadowGroups,
shadowGroupsLn = shadowGroups.length,
group = me.group,
seriesStyle = me.seriesStyle,
items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
bounds, endSeriesStyle, barAttr, attrs, anim;
if (!store || !store.getCount()) {
return;
}
//fill colors are taken from the colors array.
delete seriesStyle.fill;
endSeriesStyle = Ext.apply(seriesStyle, this.style);
me.unHighlightItem();
me.cleanHighlights();
me.getPaths();
bounds = me.bounds;
items = me.items;
baseAttrs = column ? {
y: bounds.zero,
height: 0
} : {
x: bounds.zero,
width: 0
};
ln = items.length;
// Create new or reuse sprites and animate/display
for (i = 0; i < ln; i++) {
sprite = group.getAt(i);
barAttr = items[i].attr;
if (enableShadows) {
items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
}
// Create a new sprite if needed (no height)
if (!sprite) {
attrs = Ext.apply({}, baseAttrs, barAttr);
attrs = Ext.apply(attrs, endSeriesStyle || {});
sprite = surface.add(Ext.apply({}, {
type: 'rect',
group: group
}, attrs));
}
if (animate) {
rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
sprite._to = rendererAttributes;
anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
j = i / bounds.barsLen;
for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
anim.on('afteranimate', function() {
this.show(true);
}, shadowGroups[shadowIndex].getAt(j));
}
}
}
else {
rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
}
items[i].sprite = sprite;
}
// Hide unused sprites
ln = group.getCount();
for (j = i; j < ln; j++) {
group.getAt(j).hide(true);
}
// Hide unused shadows
if (enableShadows) {
for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
shadowGroup = shadowGroups[shadowIndex];
ln = shadowGroup.getCount();
for (j = i; j < ln; j++) {
shadowGroup.getAt(j).hide(true);
}
}
}
me.renderLabels();
},
// @private handled when creating a label.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
surface = me.chart.surface,
group = me.labelsGroup,
config = me.label,
endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
sprite;
return surface.add(Ext.apply({
type: 'text',
group: group
}, endLabelStyle || {}));
},
// @private callback used when placing a label.
onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
// Determine the label's final position. Starts with the configured preferred value but
// may get flipped from inside to outside or vice-versa depending on space.
var me = this,
opt = me.bounds,
groupBarWidth = opt.groupBarWidth,
column = me.column,
chart = me.chart,
chartBBox = chart.chartBBox,
resizing = chart.resizing,
xValue = item.value[0],
yValue = item.value[1],
attr = item.attr,
config = me.label,
rotate = config.orientation == 'vertical',
field = [].concat(config.field),
format = config.renderer,
text = format(storeItem.get(field[index])),
size = me.getLabelSize(text),
width = size.width,
height = size.height,
zero = opt.zero,
outside = 'outside',
insideStart = 'insideStart',
insideEnd = 'insideEnd',
offsetX = 10,
offsetY = 6,
signed = opt.signed,
x, y, finalAttr;
label.setAttributes({
text: text
});
label.isOutside = false;
if (column) {
if (display == outside) {
if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
display = insideEnd;
}
} else {
if (height + offsetY > attr.height) {
display = outside;
label.isOutside = true;
}
}
x = attr.x + groupBarWidth / 2;
y = display == insideStart ?
(zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
(yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
(attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
}
else {
if (display == outside) {
if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
display = insideEnd;
}
}
else {
if (width + offsetX > attr.width) {
display = outside;
label.isOutside = true;
}
}
x = display == insideStart ?
(zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
(yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
(attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
y = attr.y + groupBarWidth / 2;
}
//set position
finalAttr = {
x: x,
y: y
};
//rotate
if (rotate) {
finalAttr.rotate = {
x: x,
y: y,
degrees: 270
};
}
//check for resizing
if (animate && resizing) {
if (column) {
x = attr.x + attr.width / 2;
y = zero;
} else {
x = zero;
y = attr.y + attr.height / 2;
}
label.setAttributes({
x: x,
y: y
}, true);
if (rotate) {
label.setAttributes({
rotate: {
x: x,
y: y,
degrees: 270
}
}, true);
}
}
//handle animation
if (animate) {
me.onAnimate(label, { to: finalAttr });
}
else {
label.setAttributes(Ext.apply(finalAttr, {
hidden: false
}), true);
}
},
/* @private
* Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
* changing visible sprites.
* @param value
*/
getLabelSize: function(value) {
var tester = this.testerLabel,
config = this.label,
endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
rotated = config.orientation === 'vertical',
bbox, w, h,
undef;
if (!tester) {
tester = this.testerLabel = this.chart.surface.add(Ext.apply({
type: 'text',
opacity: 0
}, endLabelStyle));
}
tester.setAttributes({
text: value
}, true);
// Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
bbox = tester.getBBox();
w = bbox.width;
h = bbox.height;
return {
width: rotated ? h : w,
height: rotated ? w : h
};
},
// @private used to animate label, markers and other sprites.
onAnimate: function(sprite, attr) {
sprite.show();
return this.callParent(arguments);
},
isItemInPoint: function(x, y, item) {
var bbox = item.sprite.getBBox();
return bbox.x <= x && bbox.y <= y
&& (bbox.x + bbox.width) >= x
&& (bbox.y + bbox.height) >= y;
},
// @private hide all markers
hideAll: function() {
var axes = this.chart.axes;
if (!isNaN(this._index)) {
if (!this.__excludes) {
this.__excludes = [];
}
this.__excludes[this._index] = true;
this.drawSeries();
axes.each(function(axis) {
axis.drawAxis();
});
}
},
// @private show all markers
showAll: function() {
var axes = this.chart.axes;
if (!isNaN(this._index)) {
if (!this.__excludes) {
this.__excludes = [];
}
this.__excludes[this._index] = false;
this.drawSeries();
axes.each(function(axis) {
axis.drawAxis();
});
}
},
/**
* Returns a string with the color to be used for the series legend item.
* @param index
*/
getLegendColor: function(index) {
var me = this,
colorLength = me.colorArrayStyle.length;
if (me.style && me.style.fill) {
return me.style.fill;
} else {
return me.colorArrayStyle[index % colorLength];
}
},
highlightItem: function(item) {
this.callParent(arguments);
this.renderLabels();
},
unHighlightItem: function() {
this.callParent(arguments);
this.renderLabels();
},
cleanHighlights: function() {
this.callParent(arguments);
this.renderLabels();
}
});

View File

@@ -0,0 +1,267 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.series.Cartesian
* @extends Ext.chart.series.Series
*
* Common base class for series implementations which plot values using x/y coordinates.
*/
Ext.define('Ext.chart.series.Cartesian', {
/* Begin Definitions */
extend: 'Ext.chart.series.Series',
alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
/* End Definitions */
/**
* The field used to access the x axis value from the items from the data
* source.
*
* @cfg xField
* @type String
*/
xField: null,
/**
* The field used to access the y-axis value from the items from the data
* source.
*
* @cfg yField
* @type String
*/
yField: null,
/**
* @cfg {String} axis
* The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
* You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
* relative scale will be used.
*/
axis: 'left',
getLegendLabels: function() {
var me = this,
labels = [],
combinations = me.combinations;
Ext.each([].concat(me.yField), function(yField, i) {
var title = me.title;
// Use the 'title' config if present, otherwise use the raw yField name
labels.push((Ext.isArray(title) ? title[i] : title) || yField);
});
// Handle yFields combined via legend drag-drop
if (combinations) {
Ext.each(combinations, function(combo) {
var label0 = labels[combo[0]],
label1 = labels[combo[1]];
labels[combo[1]] = label0 + ' & ' + label1;
labels.splice(combo[0], 1);
});
}
return labels;
},
/**
* @protected Iterates over a given record's values for each of this series's yFields,
* executing a given function for each value. Any yFields that have been combined
* via legend drag-drop will be treated as a single value.
* @param {Ext.data.Model} record
* @param {Function} fn
* @param {Object} scope
*/
eachYValue: function(record, fn, scope) {
Ext.each(this.getYValueAccessors(), function(accessor, i) {
fn.call(scope, accessor(record), i);
});
},
/**
* @protected Returns the number of yField values, taking into account fields combined
* via legend drag-drop.
* @return {Number}
*/
getYValueCount: function() {
return this.getYValueAccessors().length;
},
combine: function(index1, index2) {
var me = this,
accessors = me.getYValueAccessors(),
accessor1 = accessors[index1],
accessor2 = accessors[index2];
// Combine the yValue accessors for the two indexes into a single accessor that returns their sum
accessors[index2] = function(record) {
return accessor1(record) + accessor2(record);
};
accessors.splice(index1, 1);
me.callParent([index1, index2]);
},
clearCombinations: function() {
// Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
delete this.yValueAccessors;
this.callParent();
},
/**
* @protected Returns an array of functions, each of which returns the value of the yField
* corresponding to function's index in the array, for a given record (each function takes the
* record as its only argument.) If yFields have been combined by the user via legend drag-drop,
* this list of accessors will be kept in sync with those combinations.
* @return {Array} array of accessor functions
*/
getYValueAccessors: function() {
var me = this,
accessors = me.yValueAccessors;
if (!accessors) {
accessors = me.yValueAccessors = [];
Ext.each([].concat(me.yField), function(yField) {
accessors.push(function(record) {
return record.get(yField);
});
});
}
return accessors;
},
/**
* Calculate the min and max values for this series's xField.
* @return {Array} [min, max]
*/
getMinMaxXValues: function() {
var me = this,
min, max,
xField = me.xField;
if (me.getRecordCount() > 0) {
min = Infinity;
max = -min;
me.eachRecord(function(record) {
var xValue = record.get(xField);
if (xValue > max) {
max = xValue;
}
if (xValue < min) {
min = xValue;
}
});
} else {
min = max = 0;
}
return [min, max];
},
/**
* Calculate the min and max values for this series's yField(s). Takes into account yField
* combinations, exclusions, and stacking.
* @return {Array} [min, max]
*/
getMinMaxYValues: function() {
var me = this,
stacked = me.stacked,
min, max,
positiveTotal, negativeTotal;
function eachYValueStacked(yValue, i) {
if (!me.isExcluded(i)) {
if (yValue < 0) {
negativeTotal += yValue;
} else {
positiveTotal += yValue;
}
}
}
function eachYValue(yValue, i) {
if (!me.isExcluded(i)) {
if (yValue > max) {
max = yValue;
}
if (yValue < min) {
min = yValue;
}
}
}
if (me.getRecordCount() > 0) {
min = Infinity;
max = -min;
me.eachRecord(function(record) {
if (stacked) {
positiveTotal = 0;
negativeTotal = 0;
me.eachYValue(record, eachYValueStacked);
if (positiveTotal > max) {
max = positiveTotal;
}
if (negativeTotal < min) {
min = negativeTotal;
}
} else {
me.eachYValue(record, eachYValue);
}
});
} else {
min = max = 0;
}
return [min, max];
},
getAxesForXAndYFields: function() {
var me = this,
axes = me.chart.axes,
axis = [].concat(me.axis),
xAxis, yAxis;
if (Ext.Array.indexOf(axis, 'top') > -1) {
xAxis = 'top';
} else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
xAxis = 'bottom';
} else {
if (axes.get('top')) {
xAxis = 'top';
} else if (axes.get('bottom')) {
xAxis = 'bottom';
}
}
if (Ext.Array.indexOf(axis, 'left') > -1) {
yAxis = 'left';
} else if (Ext.Array.indexOf(axis, 'right') > -1) {
yAxis = 'right';
} else {
if (axes.get('left')) {
yAxis = 'left';
} else if (axes.get('right')) {
yAxis = 'right';
}
}
return {
xAxis: xAxis,
yAxis: yAxis
};
}
});

View File

@@ -0,0 +1,119 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.series.Column
* @extends Ext.chart.series.Bar
*
* Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
* visualization technique to display quantitative information for different categories that can
* show some progression (or regression) in the data set. As with all other series, the Column Series
* must be appended in the *series* Chart array configuration. See the Chart documentation for more
* information. A typical configuration object for the column series could be:
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
* { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
* { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
* { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
* { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* store: store,
* axes: [
* {
* type: 'Numeric',
* position: 'left',
* fields: ['data1'],
* label: {
* renderer: Ext.util.Format.numberRenderer('0,0')
* },
* title: 'Sample Values',
* grid: true,
* minimum: 0
* },
* {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics'
* }
* ],
* series: [
* {
* type: 'column',
* axis: 'left',
* highlight: true,
* tips: {
* trackMouse: true,
* width: 140,
* height: 28,
* renderer: function(storeItem, item) {
* this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
* }
* },
* label: {
* display: 'insideEnd',
* 'text-anchor': 'middle',
* field: 'data1',
* renderer: Ext.util.Format.numberRenderer('0'),
* orientation: 'vertical',
* color: '#333'
* },
* xField: 'name',
* yField: 'data1'
* }
* ]
* });
*
* In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
* set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
* field to the data store `name` property and the `yField` as the data1 property of a store element.
*/
Ext.define('Ext.chart.series.Column', {
/* Begin Definitions */
alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
extend: 'Ext.chart.series.Bar',
/* End Definitions */
type: 'column',
alias: 'series.column',
column: true,
/**
* @cfg {Number} xPadding
* Padding between the left/right axes and the bars
*/
xPadding: 10,
/**
* @cfg {Number} yPadding
* Padding between the top/bottom axes and the bars
*/
yPadding: 0
});

View File

@@ -0,0 +1,468 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.series.Gauge
* @extends Ext.chart.series.Series
*
* Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
* One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
* visualization and using the `setValue` method to adjust the value you want.
*
* A chart/series configuration for the Gauge visualization could look like this:
*
* {
* xtype: 'chart',
* store: store,
* axes: [{
* type: 'gauge',
* position: 'gauge',
* minimum: 0,
* maximum: 100,
* steps: 10,
* margin: -10
* }],
* series: [{
* type: 'gauge',
* field: 'data1',
* donut: false,
* colorSet: ['#F49D10', '#ddd']
* }]
* }
*
* In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
* setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
* the visual display and the color set to be used with the visualization.
*
* @xtype gauge
*/
Ext.define('Ext.chart.series.Gauge', {
/* Begin Definitions */
extend: 'Ext.chart.series.Series',
/* End Definitions */
type: "gauge",
alias: 'series.gauge',
rad: Math.PI / 180,
/**
* @cfg {Number} highlightDuration
* The duration for the pie slice highlight effect.
*/
highlightDuration: 150,
/**
* @cfg {String} angleField (required)
* The store record field name to be used for the pie angles.
* The values bound to this field name must be positive real numbers.
*/
angleField: false,
/**
* @cfg {Boolean} needle
* Use the Gauge Series as an area series or add a needle to it. Default's false.
*/
needle: false,
/**
* @cfg {Boolean/Number} donut
* Use the entire disk or just a fraction of it for the gauge. Default's false.
*/
donut: false,
/**
* @cfg {Boolean} showInLegend
* Whether to add the pie chart elements as legend items. Default's false.
*/
showInLegend: false,
/**
* @cfg {Object} style
* An object containing styles for overriding series styles from Theming.
*/
style: {},
constructor: function(config) {
this.callParent(arguments);
var me = this,
chart = me.chart,
surface = chart.surface,
store = chart.store,
shadow = chart.shadow, i, l, cfg;
Ext.apply(me, config, {
shadowAttributes: [{
"stroke-width": 6,
"stroke-opacity": 1,
stroke: 'rgb(200, 200, 200)',
translate: {
x: 1.2,
y: 2
}
},
{
"stroke-width": 4,
"stroke-opacity": 1,
stroke: 'rgb(150, 150, 150)',
translate: {
x: 0.9,
y: 1.5
}
},
{
"stroke-width": 2,
"stroke-opacity": 1,
stroke: 'rgb(100, 100, 100)',
translate: {
x: 0.6,
y: 1
}
}]
});
me.group = surface.getGroup(me.seriesId);
if (shadow) {
for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
}
}
surface.customAttributes.segment = function(opt) {
return me.getSegment(opt);
};
},
//@private updates some onbefore render parameters.
initialize: function() {
var me = this,
store = me.chart.getChartStore();
//Add yFields to be used in Legend.js
me.yField = [];
if (me.label.field) {
store.each(function(rec) {
me.yField.push(rec.get(me.label.field));
});
}
},
// @private returns an object with properties for a Slice
getSegment: function(opt) {
var me = this,
rad = me.rad,
cos = Math.cos,
sin = Math.sin,
abs = Math.abs,
x = me.centerX,
y = me.centerY,
x1 = 0, x2 = 0, x3 = 0, x4 = 0,
y1 = 0, y2 = 0, y3 = 0, y4 = 0,
delta = 1e-2,
r = opt.endRho - opt.startRho,
startAngle = opt.startAngle,
endAngle = opt.endAngle,
midAngle = (startAngle + endAngle) / 2 * rad,
margin = opt.margin || 0,
flag = abs(endAngle - startAngle) > 180,
a1 = Math.min(startAngle, endAngle) * rad,
a2 = Math.max(startAngle, endAngle) * rad,
singleSlice = false;
x += margin * cos(midAngle);
y += margin * sin(midAngle);
x1 = x + opt.startRho * cos(a1);
y1 = y + opt.startRho * sin(a1);
x2 = x + opt.endRho * cos(a1);
y2 = y + opt.endRho * sin(a1);
x3 = x + opt.startRho * cos(a2);
y3 = y + opt.startRho * sin(a2);
x4 = x + opt.endRho * cos(a2);
y4 = y + opt.endRho * sin(a2);
if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
singleSlice = true;
}
//Solves mysterious clipping bug with IE
if (singleSlice) {
return {
path: [
["M", x1, y1],
["L", x2, y2],
["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
["Z"]]
};
} else {
return {
path: [
["M", x1, y1],
["L", x2, y2],
["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
["L", x3, y3],
["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
["Z"]]
};
}
},
// @private utility function to calculate the middle point of a pie slice.
calcMiddle: function(item) {
var me = this,
rad = me.rad,
slice = item.slice,
x = me.centerX,
y = me.centerY,
startAngle = slice.startAngle,
endAngle = slice.endAngle,
radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
donut = +me.donut,
a1 = Math.min(startAngle, endAngle) * rad,
a2 = Math.max(startAngle, endAngle) * rad,
midAngle = -(a1 + (a2 - a1) / 2),
xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
item.middle = {
x: xm,
y: ym
};
},
/**
* Draws the series for the current chart.
*/
drawSeries: function() {
var me = this,
chart = me.chart,
store = chart.getChartStore(),
group = me.group,
animate = me.chart.animate,
axis = me.chart.axes.get(0),
minimum = axis && axis.minimum || me.minimum || 0,
maximum = axis && axis.maximum || me.maximum || 0,
field = me.angleField || me.field || me.xField,
surface = chart.surface,
chartBBox = chart.chartBBox,
rad = me.rad,
donut = +me.donut,
values = {},
items = [],
seriesStyle = me.seriesStyle,
seriesLabelStyle = me.seriesLabelStyle,
colorArrayStyle = me.colorArrayStyle,
colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
gutterX = chart.maxGutter[0],
gutterY = chart.maxGutter[1],
cos = Math.cos,
sin = Math.sin,
rendererAttributes, centerX, centerY, slice, slices, sprite, value,
item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
Ext.apply(seriesStyle, me.style || {});
me.setBBox();
bbox = me.bbox;
//override theme colors
if (me.colorSet) {
colorArrayStyle = me.colorSet;
colorArrayLength = colorArrayStyle.length;
}
//if not store or store is empty then there's nothing to draw
if (!store || !store.getCount()) {
return;
}
centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
centerY = me.centerY = chartBBox.y + chartBBox.height;
me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
me.slices = slices = [];
me.items = items = [];
if (!me.value) {
record = store.getAt(0);
me.value = record.get(field);
}
value = me.value;
if (me.needle) {
sliceA = {
series: me,
value: value,
startAngle: -180,
endAngle: 0,
rho: me.radius
};
splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
slices.push(sliceA);
} else {
splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
sliceA = {
series: me,
value: value,
startAngle: -180,
endAngle: splitAngle,
rho: me.radius
};
sliceB = {
series: me,
value: me.maximum - value,
startAngle: splitAngle,
endAngle: 0,
rho: me.radius
};
slices.push(sliceA, sliceB);
}
//do pie slices after.
for (i = 0, ln = slices.length; i < ln; i++) {
slice = slices[i];
sprite = group.getAt(i);
//set pie slice properties
rendererAttributes = Ext.apply({
segment: {
startAngle: slice.startAngle,
endAngle: slice.endAngle,
margin: 0,
rho: slice.rho,
startRho: slice.rho * +donut / 100,
endRho: slice.rho
}
}, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
item = Ext.apply({},
rendererAttributes.segment, {
slice: slice,
series: me,
storeItem: record,
index: i
});
items[i] = item;
// Create a new sprite if needed (no height)
if (!sprite) {
spriteOptions = Ext.apply({
type: "path",
group: group
}, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
}
slice.sprite = slice.sprite || [];
item.sprite = sprite;
slice.sprite.push(sprite);
if (animate) {
rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
sprite._to = rendererAttributes;
me.onAnimate(sprite, {
to: rendererAttributes
});
} else {
rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
hidden: false
}), i, store);
sprite.setAttributes(rendererAttributes, true);
}
}
if (me.needle) {
splitAngle = splitAngle * Math.PI / 180;
if (!me.needleSprite) {
me.needleSprite = me.chart.surface.add({
type: 'path',
path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
'L', centerX + me.radius * cos(splitAngle),
centerY + -Math.abs(me.radius * sin(splitAngle))],
'stroke-width': 4,
'stroke': '#222'
});
} else {
if (animate) {
me.onAnimate(me.needleSprite, {
to: {
path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
'L', centerX + me.radius * cos(splitAngle),
centerY + -Math.abs(me.radius * sin(splitAngle))]
}
});
} else {
me.needleSprite.setAttributes({
type: 'path',
path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
'L', centerX + me.radius * cos(splitAngle),
centerY + -Math.abs(me.radius * sin(splitAngle))]
});
}
}
me.needleSprite.setAttributes({
hidden: false
}, true);
}
delete me.value;
},
/**
* Sets the Gauge chart to the current specified value.
*/
setValue: function (value) {
this.value = value;
this.drawSeries();
},
// @private callback for when creating a label sprite.
onCreateLabel: function(storeItem, item, i, display) {},
// @private callback for when placing a label sprite.
onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
// @private callback for when placing a callout.
onPlaceCallout: function() {},
// @private handles sprite animation for the series.
onAnimate: function(sprite, attr) {
sprite.show();
return this.callParent(arguments);
},
isItemInPoint: function(x, y, item, i) {
return false;
},
// @private shows all elements in the series.
showAll: function() {
if (!isNaN(this._index)) {
this.__excludes[this._index] = false;
this.drawSeries();
}
},
/**
* Returns the color of the series (to be displayed as color for the series legend item).
* @param item {Object} Info about the item; same format as returned by #getItemForPoint
*/
getLegendColor: function(index) {
var me = this;
return me.colorArrayStyle[index % me.colorArrayStyle.length];
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,435 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.series.Radar
* @extends Ext.chart.series.Series
*
* Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
* a constrained number of categories.
*
* As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information. A typical configuration object for the radar series could be:
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
* { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
* { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
* { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
* { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* theme:'Category2',
* store: store,
* axes: [{
* type: 'Radial',
* position: 'radial',
* label: {
* display: true
* }
* }],
* series: [{
* type: 'radar',
* xField: 'name',
* yField: 'data3',
* showInLegend: true,
* showMarkers: true,
* markerConfig: {
* radius: 5,
* size: 5
* },
* style: {
* 'stroke-width': 2,
* fill: 'none'
* }
* },{
* type: 'radar',
* xField: 'name',
* yField: 'data2',
* showMarkers: true,
* showInLegend: true,
* markerConfig: {
* radius: 5,
* size: 5
* },
* style: {
* 'stroke-width': 2,
* fill: 'none'
* }
* },{
* type: 'radar',
* xField: 'name',
* yField: 'data5',
* showMarkers: true,
* showInLegend: true,
* markerConfig: {
* radius: 5,
* size: 5
* },
* style: {
* 'stroke-width': 2,
* fill: 'none'
* }
* }]
* });
*
* In this configuration we add three series to the chart. Each of these series is bound to the same
* categories field, `name` but bound to different properties for each category, `data1`, `data2` and
* `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
* for the markers of each series can be set by adding properties onto the markerConfig object.
* Finally we override some theme styling properties by adding properties to the `style` object.
*
* @xtype radar
*/
Ext.define('Ext.chart.series.Radar', {
/* Begin Definitions */
extend: 'Ext.chart.series.Series',
requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
/* End Definitions */
type: "radar",
alias: 'series.radar',
rad: Math.PI / 180,
showInLegend: false,
/**
* @cfg {Object} style
* An object containing styles for overriding series styles from Theming.
*/
style: {},
constructor: function(config) {
this.callParent(arguments);
var me = this,
surface = me.chart.surface, i, l;
me.group = surface.getGroup(me.seriesId);
if (me.showMarkers) {
me.markerGroup = surface.getGroup(me.seriesId + '-markers');
}
},
/**
* Draws the series for the current chart.
*/
drawSeries: function() {
var me = this,
store = me.chart.getChartStore(),
group = me.group,
sprite,
chart = me.chart,
animate = chart.animate,
field = me.field || me.yField,
surface = chart.surface,
chartBBox = chart.chartBBox,
rendererAttributes,
centerX, centerY,
items,
radius,
maxValue = 0,
fields = [],
max = Math.max,
cos = Math.cos,
sin = Math.sin,
pi2 = Math.PI * 2,
l = store.getCount(),
startPath, path, x, y, rho,
i, nfields,
seriesStyle = me.seriesStyle,
seriesLabelStyle = me.seriesLabelStyle,
first = chart.resizing || !me.radar,
axis = chart.axes && chart.axes.get(0),
aggregate = !(axis && axis.maximum);
me.setBBox();
maxValue = aggregate? 0 : (axis.maximum || 0);
Ext.apply(seriesStyle, me.style || {});
//if the store is empty then there's nothing to draw
if (!store || !store.getCount()) {
return;
}
me.unHighlightItem();
me.cleanHighlights();
centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
me.items = items = [];
if (aggregate) {
//get all renderer fields
chart.series.each(function(series) {
fields.push(series.yField);
});
//get maxValue to interpolate
store.each(function(record, i) {
for (i = 0, nfields = fields.length; i < nfields; i++) {
maxValue = max(+record.get(fields[i]), maxValue);
}
});
}
//ensure non-zero value.
maxValue = maxValue || 1;
//create path and items
startPath = []; path = [];
store.each(function(record, i) {
rho = radius * record.get(field) / maxValue;
x = rho * cos(i / l * pi2);
y = rho * sin(i / l * pi2);
if (i == 0) {
path.push('M', x + centerX, y + centerY);
startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
} else {
path.push('L', x + centerX, y + centerY);
startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
}
items.push({
sprite: false, //TODO(nico): add markers
point: [centerX + x, centerY + y],
series: me
});
});
path.push('Z');
//create path sprite
if (!me.radar) {
me.radar = surface.add(Ext.apply({
type: 'path',
group: group,
path: startPath
}, seriesStyle || {}));
}
//reset on resizing
if (chart.resizing) {
me.radar.setAttributes({
path: startPath
}, true);
}
//render/animate
if (chart.animate) {
me.onAnimate(me.radar, {
to: Ext.apply({
path: path
}, seriesStyle || {})
});
} else {
me.radar.setAttributes(Ext.apply({
path: path
}, seriesStyle || {}), true);
}
//render markers, labels and callouts
if (me.showMarkers) {
me.drawMarkers();
}
me.renderLabels();
me.renderCallouts();
},
// @private draws the markers for the lines (if any).
drawMarkers: function() {
var me = this,
chart = me.chart,
surface = chart.surface,
markerStyle = Ext.apply({}, me.markerStyle || {}),
endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
items = me.items,
type = endMarkerStyle.type,
markerGroup = me.markerGroup,
centerX = me.centerX,
centerY = me.centerY,
item, i, l, marker;
delete endMarkerStyle.type;
for (i = 0, l = items.length; i < l; i++) {
item = items[i];
marker = markerGroup.getAt(i);
if (!marker) {
marker = Ext.chart.Shape[type](surface, Ext.apply({
group: markerGroup,
x: 0,
y: 0,
translate: {
x: centerX,
y: centerY
}
}, endMarkerStyle));
}
else {
marker.show();
}
if (chart.resizing) {
marker.setAttributes({
x: 0,
y: 0,
translate: {
x: centerX,
y: centerY
}
}, true);
}
marker._to = {
translate: {
x: item.point[0],
y: item.point[1]
}
};
//render/animate
if (chart.animate) {
me.onAnimate(marker, {
to: marker._to
});
}
else {
marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
}
}
},
isItemInPoint: function(x, y, item) {
var point,
tolerance = 10,
abs = Math.abs;
point = item.point;
return (abs(point[0] - x) <= tolerance &&
abs(point[1] - y) <= tolerance);
},
// @private callback for when creating a label sprite.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
group = me.labelsGroup,
config = me.label,
centerX = me.centerX,
centerY = me.centerY,
point = item.point,
endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
return me.chart.surface.add(Ext.apply({
'type': 'text',
'text-anchor': 'middle',
'group': group,
'x': centerX,
'y': centerY
}, config || {}));
},
// @private callback for when placing a label sprite.
onPlaceLabel: function(label, storeItem, item, i, display, animate) {
var me = this,
chart = me.chart,
resizing = chart.resizing,
config = me.label,
format = config.renderer,
field = config.field,
centerX = me.centerX,
centerY = me.centerY,
opt = {
x: item.point[0],
y: item.point[1]
},
x = opt.x - centerX,
y = opt.y - centerY;
label.setAttributes({
text: format(storeItem.get(field)),
hidden: true
},
true);
if (resizing) {
label.setAttributes({
x: centerX,
y: centerY
}, true);
}
if (animate) {
label.show(true);
me.onAnimate(label, {
to: opt
});
} else {
label.setAttributes(opt, true);
label.show(true);
}
},
// @private for toggling (show/hide) series.
toggleAll: function(show) {
var me = this,
i, ln, shadow, shadows;
if (!show) {
Ext.chart.series.Radar.superclass.hideAll.call(me);
}
else {
Ext.chart.series.Radar.superclass.showAll.call(me);
}
if (me.radar) {
me.radar.setAttributes({
hidden: !show
}, true);
//hide shadows too
if (me.radar.shadows) {
for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
shadow = shadows[i];
shadow.setAttributes({
hidden: !show
}, true);
}
}
}
},
// @private hide all elements in the series.
hideAll: function() {
this.toggleAll(false);
this.hideMarkers(0);
},
// @private show all elements in the series.
showAll: function() {
this.toggleAll(true);
},
// @private hide all markers that belong to `markerGroup`
hideMarkers: function(index) {
var me = this,
count = me.markerGroup && me.markerGroup.getCount() || 0,
i = index || 0;
for (; i < count; i++) {
me.markerGroup.getAt(i).hide(true);
}
}
});

View File

@@ -0,0 +1,679 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.series.Scatter
* @extends Ext.chart.series.Cartesian
*
* Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
* These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
* As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
* documentation for more information on creating charts. A typical configuration object for the scatter could be:
*
* @example
* var store = Ext.create('Ext.data.JsonStore', {
* fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
* data: [
* { 'name': 'metric one', 'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8, 'data5': 13 },
* { 'name': 'metric two', 'data1': 7, 'data2': 8, 'data3': 16, 'data4': 10, 'data5': 3 },
* { 'name': 'metric three', 'data1': 5, 'data2': 2, 'data3': 14, 'data4': 12, 'data5': 7 },
* { 'name': 'metric four', 'data1': 2, 'data2': 14, 'data3': 6, 'data4': 1, 'data5': 23 },
* { 'name': 'metric five', 'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
* ]
* });
*
* Ext.create('Ext.chart.Chart', {
* renderTo: Ext.getBody(),
* width: 500,
* height: 300,
* animate: true,
* theme:'Category2',
* store: store,
* axes: [{
* type: 'Numeric',
* position: 'left',
* fields: ['data2', 'data3'],
* title: 'Sample Values',
* grid: true,
* minimum: 0
* }, {
* type: 'Category',
* position: 'bottom',
* fields: ['name'],
* title: 'Sample Metrics'
* }],
* series: [{
* type: 'scatter',
* markerConfig: {
* radius: 5,
* size: 5
* },
* axis: 'left',
* xField: 'name',
* yField: 'data2'
* }, {
* type: 'scatter',
* markerConfig: {
* radius: 5,
* size: 5
* },
* axis: 'left',
* xField: 'name',
* yField: 'data3'
* }]
* });
*
* In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
* `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
* Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
* axis to show the current values of the elements.
*
* @xtype scatter
*/
Ext.define('Ext.chart.series.Scatter', {
/* Begin Definitions */
extend: 'Ext.chart.series.Cartesian',
requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
/* End Definitions */
type: 'scatter',
alias: 'series.scatter',
/**
* @cfg {Object} markerConfig
* The display style for the scatter series markers.
*/
/**
* @cfg {Object} style
* Append styling properties to this object for it to override theme properties.
*/
/**
* @cfg {String/Array} axis
* The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
* You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
* relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
*/
constructor: function(config) {
this.callParent(arguments);
var me = this,
shadow = me.chart.shadow,
surface = me.chart.surface, i, l;
Ext.apply(me, config, {
style: {},
markerConfig: {},
shadowAttributes: [{
"stroke-width": 6,
"stroke-opacity": 0.05,
stroke: 'rgb(0, 0, 0)'
}, {
"stroke-width": 4,
"stroke-opacity": 0.1,
stroke: 'rgb(0, 0, 0)'
}, {
"stroke-width": 2,
"stroke-opacity": 0.15,
stroke: 'rgb(0, 0, 0)'
}]
});
me.group = surface.getGroup(me.seriesId);
if (shadow) {
for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
}
}
},
// @private Get chart and data boundaries
getBounds: function() {
var me = this,
chart = me.chart,
store = chart.getChartStore(),
axes = [].concat(me.axis),
bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
me.setBBox();
bbox = me.bbox;
for (i = 0, ln = axes.length; i < ln; i++) {
axis = chart.axes.get(axes[i]);
if (axis) {
ends = axis.calcEnds();
if (axis.position == 'top' || axis.position == 'bottom') {
minX = ends.from;
maxX = ends.to;
}
else {
minY = ends.from;
maxY = ends.to;
}
}
}
// If a field was specified without a corresponding axis, create one to get bounds
if (me.xField && !Ext.isNumber(minX)) {
axis = Ext.create('Ext.chart.axis.Axis', {
chart: chart,
fields: [].concat(me.xField)
}).calcEnds();
minX = axis.from;
maxX = axis.to;
}
if (me.yField && !Ext.isNumber(minY)) {
axis = Ext.create('Ext.chart.axis.Axis', {
chart: chart,
fields: [].concat(me.yField)
}).calcEnds();
minY = axis.from;
maxY = axis.to;
}
if (isNaN(minX)) {
minX = 0;
maxX = store.getCount() - 1;
xScale = bbox.width / (store.getCount() - 1);
}
else {
xScale = bbox.width / (maxX - minX);
}
if (isNaN(minY)) {
minY = 0;
maxY = store.getCount() - 1;
yScale = bbox.height / (store.getCount() - 1);
}
else {
yScale = bbox.height / (maxY - minY);
}
return {
bbox: bbox,
minX: minX,
minY: minY,
xScale: xScale,
yScale: yScale
};
},
// @private Build an array of paths for the chart
getPaths: function() {
var me = this,
chart = me.chart,
enableShadows = chart.shadow,
store = chart.getChartStore(),
group = me.group,
bounds = me.bounds = me.getBounds(),
bbox = me.bbox,
xScale = bounds.xScale,
yScale = bounds.yScale,
minX = bounds.minX,
minY = bounds.minY,
boxX = bbox.x,
boxY = bbox.y,
boxHeight = bbox.height,
items = me.items = [],
attrs = [],
x, y, xValue, yValue, sprite;
store.each(function(record, i) {
xValue = record.get(me.xField);
yValue = record.get(me.yField);
//skip undefined values
if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
//<debug warn>
if (Ext.isDefined(Ext.global.console)) {
Ext.global.console.warn("[Ext.chart.series.Scatter] Skipping a store element with an undefined value at ", record, xValue, yValue);
}
//</debug>
return;
}
// Ensure a value
if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
xValue = i;
}
if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
yValue = i;
}
x = boxX + (xValue - minX) * xScale;
y = boxY + boxHeight - (yValue - minY) * yScale;
attrs.push({
x: x,
y: y
});
me.items.push({
series: me,
value: [xValue, yValue],
point: [x, y],
storeItem: record
});
// When resizing, reset before animating
if (chart.animate && chart.resizing) {
sprite = group.getAt(i);
if (sprite) {
me.resetPoint(sprite);
if (enableShadows) {
me.resetShadow(sprite);
}
}
}
});
return attrs;
},
// @private translate point to the center
resetPoint: function(sprite) {
var bbox = this.bbox;
sprite.setAttributes({
translate: {
x: (bbox.x + bbox.width) / 2,
y: (bbox.y + bbox.height) / 2
}
}, true);
},
// @private translate shadows of a sprite to the center
resetShadow: function(sprite) {
var me = this,
shadows = sprite.shadows,
shadowAttributes = me.shadowAttributes,
ln = me.shadowGroups.length,
bbox = me.bbox,
i, attr;
for (i = 0; i < ln; i++) {
attr = Ext.apply({}, shadowAttributes[i]);
if (attr.translate) {
attr.translate.x += (bbox.x + bbox.width) / 2;
attr.translate.y += (bbox.y + bbox.height) / 2;
}
else {
attr.translate = {
x: (bbox.x + bbox.width) / 2,
y: (bbox.y + bbox.height) / 2
};
}
shadows[i].setAttributes(attr, true);
}
},
// @private create a new point
createPoint: function(attr, type) {
var me = this,
chart = me.chart,
group = me.group,
bbox = me.bbox;
return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
x: 0,
y: 0,
group: group,
translate: {
x: (bbox.x + bbox.width) / 2,
y: (bbox.y + bbox.height) / 2
}
}, attr));
},
// @private create a new set of shadows for a sprite
createShadow: function(sprite, endMarkerStyle, type) {
var me = this,
chart = me.chart,
shadowGroups = me.shadowGroups,
shadowAttributes = me.shadowAttributes,
lnsh = shadowGroups.length,
bbox = me.bbox,
i, shadow, shadows, attr;
sprite.shadows = shadows = [];
for (i = 0; i < lnsh; i++) {
attr = Ext.apply({}, shadowAttributes[i]);
if (attr.translate) {
attr.translate.x += (bbox.x + bbox.width) / 2;
attr.translate.y += (bbox.y + bbox.height) / 2;
}
else {
Ext.apply(attr, {
translate: {
x: (bbox.x + bbox.width) / 2,
y: (bbox.y + bbox.height) / 2
}
});
}
Ext.apply(attr, endMarkerStyle);
shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
x: 0,
y: 0,
group: shadowGroups[i]
}, attr));
shadows.push(shadow);
}
},
/**
* Draws the series for the current chart.
*/
drawSeries: function() {
var me = this,
chart = me.chart,
store = chart.getChartStore(),
group = me.group,
enableShadows = chart.shadow,
shadowGroups = me.shadowGroups,
shadowAttributes = me.shadowAttributes,
lnsh = shadowGroups.length,
sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
rendererAttributes, shadowAttribute;
endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
type = endMarkerStyle.type;
delete endMarkerStyle.type;
//if the store is empty then there's nothing to be rendered
if (!store || !store.getCount()) {
return;
}
me.unHighlightItem();
me.cleanHighlights();
attrs = me.getPaths();
ln = attrs.length;
for (i = 0; i < ln; i++) {
attr = attrs[i];
sprite = group.getAt(i);
Ext.apply(attr, endMarkerStyle);
// Create a new sprite if needed (no height)
if (!sprite) {
sprite = me.createPoint(attr, type);
if (enableShadows) {
me.createShadow(sprite, endMarkerStyle, type);
}
}
shadows = sprite.shadows;
if (chart.animate) {
rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
sprite._to = rendererAttributes;
me.onAnimate(sprite, {
to: rendererAttributes
});
//animate shadows
for (shindex = 0; shindex < lnsh; shindex++) {
shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
hidden: false,
translate: {
x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
}
}, shadowAttribute), i, store);
me.onAnimate(shadows[shindex], { to: rendererAttributes });
}
}
else {
rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
sprite._to = rendererAttributes;
sprite.setAttributes(rendererAttributes, true);
//animate shadows
for (shindex = 0; shindex < lnsh; shindex++) {
shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, {
hidden: false,
translate: {
x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
}
}, shadowAttribute), i, store);
shadows[shindex].setAttributes(rendererAttributes, true);
}
}
me.items[i].sprite = sprite;
}
// Hide unused sprites
ln = group.getCount();
for (i = attrs.length; i < ln; i++) {
group.getAt(i).hide(true);
}
me.renderLabels();
me.renderCallouts();
},
// @private callback for when creating a label sprite.
onCreateLabel: function(storeItem, item, i, display) {
var me = this,
group = me.labelsGroup,
config = me.label,
endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
bbox = me.bbox;
return me.chart.surface.add(Ext.apply({
type: 'text',
group: group,
x: item.point[0],
y: bbox.y + bbox.height / 2
}, endLabelStyle));
},
// @private callback for when placing a label sprite.
onPlaceLabel: function(label, storeItem, item, i, display, animate) {
var me = this,
chart = me.chart,
resizing = chart.resizing,
config = me.label,
format = config.renderer,
field = config.field,
bbox = me.bbox,
x = item.point[0],
y = item.point[1],
radius = item.sprite.attr.radius,
bb, width, height, anim;
label.setAttributes({
text: format(storeItem.get(field)),
hidden: true
}, true);
if (display == 'rotate') {
label.setAttributes({
'text-anchor': 'start',
'rotation': {
x: x,
y: y,
degrees: -45
}
}, true);
//correct label position to fit into the box
bb = label.getBBox();
width = bb.width;
height = bb.height;
x = x < bbox.x? bbox.x : x;
x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
y = (y - height < bbox.y)? bbox.y + height : y;
} else if (display == 'under' || display == 'over') {
//TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
bb = item.sprite.getBBox();
bb.width = bb.width || (radius * 2);
bb.height = bb.height || (radius * 2);
y = y + (display == 'over'? -bb.height : bb.height);
//correct label position to fit into the box
bb = label.getBBox();
width = bb.width/2;
height = bb.height/2;
x = x - width < bbox.x ? bbox.x + width : x;
x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
y = y - height < bbox.y? bbox.y + height : y;
y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
}
if (!chart.animate) {
label.setAttributes({
x: x,
y: y
}, true);
label.show(true);
}
else {
if (resizing) {
anim = item.sprite.getActiveAnimation();
if (anim) {
anim.on('afteranimate', function() {
label.setAttributes({
x: x,
y: y
}, true);
label.show(true);
});
}
else {
label.show(true);
}
}
else {
me.onAnimate(label, {
to: {
x: x,
y: y
}
});
}
}
},
// @private callback for when placing a callout sprite.
onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
var me = this,
chart = me.chart,
surface = chart.surface,
resizing = chart.resizing,
config = me.callouts,
items = me.items,
cur = item.point,
normal,
bbox = callout.label.getBBox(),
offsetFromViz = 30,
offsetToSide = 10,
offsetBox = 3,
boxx, boxy, boxw, boxh,
p, clipRect = me.bbox,
x, y;
//position
normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
//box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
//now check if we're out of bounds and invert the normal vector correspondingly
//this may add new overlaps between labels (but labels won't be out of bounds).
if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
normal[0] *= -1;
}
if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
normal[1] *= -1;
}
//update positions
x = cur[0] + normal[0] * offsetFromViz;
y = cur[1] + normal[1] * offsetFromViz;
//update box position and dimensions
boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
boxy = y - bbox.height /2 - offsetBox;
boxw = bbox.width + 2 * offsetBox;
boxh = bbox.height + 2 * offsetBox;
if (chart.animate) {
//set the line from the middle of the pie to the box.
me.onAnimate(callout.lines, {
to: {
path: ["M", cur[0], cur[1], "L", x, y, "Z"]
}
}, true);
//set box position
me.onAnimate(callout.box, {
to: {
x: boxx,
y: boxy,
width: boxw,
height: boxh
}
}, true);
//set text position
me.onAnimate(callout.label, {
to: {
x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
y: y
}
}, true);
} else {
//set the line from the middle of the pie to the box.
callout.lines.setAttributes({
path: ["M", cur[0], cur[1], "L", x, y, "Z"]
}, true);
//set box position
callout.box.setAttributes({
x: boxx,
y: boxy,
width: boxw,
height: boxh
}, true);
//set text position
callout.label.setAttributes({
x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
y: y
}, true);
}
for (p in callout) {
callout[p].show(true);
}
},
// @private handles sprite animation for the series.
onAnimate: function(sprite, attr) {
sprite.show();
return this.callParent(arguments);
},
isItemInPoint: function(x, y, item) {
var point,
tolerance = 10,
abs = Math.abs;
function dist(point) {
var dx = abs(point[0] - x),
dy = abs(point[1] - y);
return Math.sqrt(dx * dx + dy * dy);
}
point = item.point;
return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
point[1] - tolerance <= y && point[1] + tolerance >= y);
}
});

View File

@@ -0,0 +1,428 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.series.Series
*
* Series is the abstract class containing the common logic to all chart series. Series includes
* methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
* mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
*
* ## Listeners
*
* The series class supports listeners via the Observable syntax. Some of these listeners are:
*
* - `itemmouseup` When the user interacts with a marker.
* - `itemmousedown` When the user interacts with a marker.
* - `itemmousemove` When the user iteracts with a marker.
* - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
*
* For example:
*
* series: [{
* type: 'column',
* axis: 'left',
* listeners: {
* 'afterrender': function() {
* console('afterrender');
* }
* },
* xField: 'category',
* yField: 'data1'
* }]
*/
Ext.define('Ext.chart.series.Series', {
/* Begin Definitions */
mixins: {
observable: 'Ext.util.Observable',
labels: 'Ext.chart.Label',
highlights: 'Ext.chart.Highlight',
tips: 'Ext.chart.Tip',
callouts: 'Ext.chart.Callout'
},
/* End Definitions */
/**
* @cfg {Boolean/Object} highlight
* If set to `true` it will highlight the markers or the series when hovering
* with the mouse. This parameter can also be an object with the same style
* properties you would apply to a {@link Ext.draw.Sprite} to apply custom
* styles to markers and series.
*/
/**
* @cfg {Object} tips
* Add tooltips to the visualization's markers. The options for the tips are the
* same configuration used with {@link Ext.tip.ToolTip}. For example:
*
* tips: {
* trackMouse: true,
* width: 140,
* height: 28,
* renderer: function(storeItem, item) {
* this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
* }
* },
*/
/**
* @cfg {String} type
* The type of series. Set in subclasses.
*/
type: null,
/**
* @cfg {String} title
* The human-readable name of the series.
*/
title: null,
/**
* @cfg {Boolean} showInLegend
* Whether to show this series in the legend.
*/
showInLegend: true,
/**
* @cfg {Function} renderer
* A function that can be overridden to set custom styling properties to each rendered element.
* Passes in (sprite, record, attributes, index, store) to the function.
*/
renderer: function(sprite, record, attributes, index, store) {
return attributes;
},
/**
* @cfg {Array} shadowAttributes
* An array with shadow attributes
*/
shadowAttributes: null,
//@private triggerdrawlistener flag
triggerAfterDraw: false,
/**
* @cfg {Object} listeners
* An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
*
* - itemmouseover
* - itemmouseout
* - itemmousedown
* - itemmouseup
*/
constructor: function(config) {
var me = this;
if (config) {
Ext.apply(me, config);
}
me.shadowGroups = [];
me.mixins.labels.constructor.call(me, config);
me.mixins.highlights.constructor.call(me, config);
me.mixins.tips.constructor.call(me, config);
me.mixins.callouts.constructor.call(me, config);
me.addEvents({
scope: me,
itemmouseover: true,
itemmouseout: true,
itemmousedown: true,
itemmouseup: true,
mouseleave: true,
afterdraw: true,
/**
* @event titlechange
* Fires when the series title is changed via {@link #setTitle}.
* @param {String} title The new title value
* @param {Number} index The index in the collection of titles
*/
titlechange: true
});
me.mixins.observable.constructor.call(me, config);
me.on({
scope: me,
itemmouseover: me.onItemMouseOver,
itemmouseout: me.onItemMouseOut,
mouseleave: me.onMouseLeave
});
},
/**
* Iterate over each of the records for this series. The default implementation simply iterates
* through the entire data store, but individual series implementations can override this to
* provide custom handling, e.g. adding/removing records.
* @param {Function} fn The function to execute for each record.
* @param {Object} scope Scope for the fn.
*/
eachRecord: function(fn, scope) {
var chart = this.chart;
(chart.substore || chart.store).each(fn, scope);
},
/**
* Return the number of records being displayed in this series. Defaults to the number of
* records in the store; individual series implementations can override to provide custom handling.
*/
getRecordCount: function() {
var chart = this.chart,
store = chart.substore || chart.store;
return store ? store.getCount() : 0;
},
/**
* Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
* @param index
*/
isExcluded: function(index) {
var excludes = this.__excludes;
return !!(excludes && excludes[index]);
},
// @private set the bbox and clipBox for the series
setBBox: function(noGutter) {
var me = this,
chart = me.chart,
chartBBox = chart.chartBBox,
gutterX = noGutter ? 0 : chart.maxGutter[0],
gutterY = noGutter ? 0 : chart.maxGutter[1],
clipBox, bbox;
clipBox = {
x: chartBBox.x,
y: chartBBox.y,
width: chartBBox.width,
height: chartBBox.height
};
me.clipBox = clipBox;
bbox = {
x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
};
me.bbox = bbox;
},
// @private set the animation for the sprite
onAnimate: function(sprite, attr) {
var me = this;
sprite.stopAnimation();
if (me.triggerAfterDraw) {
return sprite.animate(Ext.applyIf(attr, me.chart.animate));
} else {
me.triggerAfterDraw = true;
return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
listeners: {
'afteranimate': function() {
me.triggerAfterDraw = false;
me.fireEvent('afterrender');
}
}
}));
}
},
// @private return the gutter.
getGutters: function() {
return [0, 0];
},
// @private wrapper for the itemmouseover event.
onItemMouseOver: function(item) {
var me = this;
if (item.series === me) {
if (me.highlight) {
me.highlightItem(item);
}
if (me.tooltip) {
me.showTip(item);
}
}
},
// @private wrapper for the itemmouseout event.
onItemMouseOut: function(item) {
var me = this;
if (item.series === me) {
me.unHighlightItem();
if (me.tooltip) {
me.hideTip(item);
}
}
},
// @private wrapper for the mouseleave event.
onMouseLeave: function() {
var me = this;
me.unHighlightItem();
if (me.tooltip) {
me.hideTip();
}
},
/**
* For a given x/y point relative to the Surface, find a corresponding item from this
* series, if any.
* @param {Number} x
* @param {Number} y
* @return {Object} An object describing the item, or null if there is no matching item.
* The exact contents of this object will vary by series type, but should always contain the following:
* @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
* @return {Object} return.value the value(s) of the item's data point
* @return {Array} return.point the x/y coordinates relative to the chart box of a single point
* for this data item, which can be used as e.g. a tooltip anchor point.
* @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
*/
getItemForPoint: function(x, y) {
//if there are no items to query just return null.
if (!this.items || !this.items.length || this.seriesIsHidden) {
return null;
}
var me = this,
items = me.items,
bbox = me.bbox,
item, i, ln;
// Check bounds
if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
return null;
}
for (i = 0, ln = items.length; i < ln; i++) {
if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
return items[i];
}
}
return null;
},
isItemInPoint: function(x, y, item, i) {
return false;
},
/**
* Hides all the elements in the series.
*/
hideAll: function() {
var me = this,
items = me.items,
item, len, i, j, l, sprite, shadows;
me.seriesIsHidden = true;
me._prevShowMarkers = me.showMarkers;
me.showMarkers = false;
//hide all labels
me.hideLabels(0);
//hide all sprites
for (i = 0, len = items.length; i < len; i++) {
item = items[i];
sprite = item.sprite;
if (sprite) {
sprite.setAttributes({
hidden: true
}, true);
}
if (sprite && sprite.shadows) {
shadows = sprite.shadows;
for (j = 0, l = shadows.length; j < l; ++j) {
shadows[j].setAttributes({
hidden: true
}, true);
}
}
}
},
/**
* Shows all the elements in the series.
*/
showAll: function() {
var me = this,
prevAnimate = me.chart.animate;
me.chart.animate = false;
me.seriesIsHidden = false;
me.showMarkers = me._prevShowMarkers;
me.drawSeries();
me.chart.animate = prevAnimate;
},
/**
* Returns a string with the color to be used for the series legend item.
*/
getLegendColor: function(index) {
var me = this, fill, stroke;
if (me.seriesStyle) {
fill = me.seriesStyle.fill;
stroke = me.seriesStyle.stroke;
if (fill && fill != 'none') {
return fill;
}
return stroke;
}
return '#000';
},
/**
* Checks whether the data field should be visible in the legend
* @private
* @param {Number} index The index of the current item
*/
visibleInLegend: function(index){
var excludes = this.__excludes;
if (excludes) {
return !excludes[index];
}
return !this.seriesIsHidden;
},
/**
* Changes the value of the {@link #title} for the series.
* Arguments can take two forms:
* <ul>
* <li>A single String value: this will be used as the new single title for the series (applies
* to series with only one yField)</li>
* <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
* </ul>
* @param {Number} index
* @param {String} title
*/
setTitle: function(index, title) {
var me = this,
oldTitle = me.title;
if (Ext.isString(index)) {
title = index;
index = 0;
}
if (Ext.isArray(oldTitle)) {
oldTitle[index] = title;
} else {
me.title = title;
}
me.fireEvent('titlechange', title, index);
}
});

View File

@@ -0,0 +1,170 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.theme.Base
* Provides default colors for non-specified things. Should be sub-classed when creating new themes.
* @ignore
*/
Ext.define('Ext.chart.theme.Base', {
/* Begin Definitions */
requires: ['Ext.chart.theme.Theme'],
/* End Definitions */
constructor: function(config) {
Ext.chart.theme.call(this, config, {
background: false,
axis: {
stroke: '#444',
'stroke-width': 1
},
axisLabelTop: {
fill: '#444',
font: '12px Arial, Helvetica, sans-serif',
spacing: 2,
padding: 5,
renderer: function(v) { return v; }
},
axisLabelRight: {
fill: '#444',
font: '12px Arial, Helvetica, sans-serif',
spacing: 2,
padding: 5,
renderer: function(v) { return v; }
},
axisLabelBottom: {
fill: '#444',
font: '12px Arial, Helvetica, sans-serif',
spacing: 2,
padding: 5,
renderer: function(v) { return v; }
},
axisLabelLeft: {
fill: '#444',
font: '12px Arial, Helvetica, sans-serif',
spacing: 2,
padding: 5,
renderer: function(v) { return v; }
},
axisTitleTop: {
font: 'bold 18px Arial',
fill: '#444'
},
axisTitleRight: {
font: 'bold 18px Arial',
fill: '#444',
rotate: {
x:0, y:0,
degrees: 270
}
},
axisTitleBottom: {
font: 'bold 18px Arial',
fill: '#444'
},
axisTitleLeft: {
font: 'bold 18px Arial',
fill: '#444',
rotate: {
x:0, y:0,
degrees: 270
}
},
series: {
'stroke-width': 0
},
seriesLabel: {
font: '12px Arial',
fill: '#333'
},
marker: {
stroke: '#555',
fill: '#000',
radius: 3,
size: 3
},
colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
seriesThemes: [{
fill: "#115fa6"
}, {
fill: "#94ae0a"
}, {
fill: "#a61120"
}, {
fill: "#ff8809"
}, {
fill: "#ffd13e"
}, {
fill: "#a61187"
}, {
fill: "#24ad9a"
}, {
fill: "#7c7474"
}, {
fill: "#a66111"
}],
markerThemes: [{
fill: "#115fa6",
type: 'circle'
}, {
fill: "#94ae0a",
type: 'cross'
}, {
fill: "#a61120",
type: 'plus'
}]
});
}
}, function() {
var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
cats = categories.length;
//Create themes from base colors
for (; i < l; i++) {
themes[names[i]] = (function(color) {
return Ext.extend(themes.Base, {
constructor: function(config) {
themes.Base.prototype.constructor.call(this, Ext.apply({
baseColor: color
}, config));
}
});
})(palette[i]);
}
//Create theme from color array
for (i = 0; i < cats; i++) {
themes['Category' + (i + 1)] = (function(category) {
return Ext.extend(themes.Base, {
constructor: function(config) {
themes.Base.prototype.constructor.call(this, Ext.apply({
colors: category
}, config));
}
});
})(categories[i]);
}
});

View File

@@ -0,0 +1,268 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.chart.theme.Theme
*
* Provides chart theming.
*
* Used as mixins by Ext.chart.Chart.
*/
Ext.define('Ext.chart.theme.Theme', {
/* Begin Definitions */
requires: ['Ext.draw.Color'],
/* End Definitions */
theme: 'Base',
themeAttrs: false,
initTheme: function(theme) {
var me = this,
themes = Ext.chart.theme,
key, gradients;
if (theme) {
theme = theme.split(':');
for (key in themes) {
if (key == theme[0]) {
gradients = theme[1] == 'gradients';
me.themeAttrs = new themes[key]({
useGradients: gradients
});
if (gradients) {
me.gradients = me.themeAttrs.gradients;
}
if (me.themeAttrs.background) {
me.background = me.themeAttrs.background;
}
return;
}
}
//<debug>
Ext.Error.raise('No theme found named "' + theme + '"');
//</debug>
}
}
},
// This callback is executed right after when the class is created. This scope refers to the newly created class itself
function() {
/* Theme constructor: takes either a complex object with styles like:
{
axis: {
fill: '#000',
'stroke-width': 1
},
axisLabelTop: {
fill: '#000',
font: '11px Arial'
},
axisLabelLeft: {
fill: '#000',
font: '11px Arial'
},
axisLabelRight: {
fill: '#000',
font: '11px Arial'
},
axisLabelBottom: {
fill: '#000',
font: '11px Arial'
},
axisTitleTop: {
fill: '#000',
font: '11px Arial'
},
axisTitleLeft: {
fill: '#000',
font: '11px Arial'
},
axisTitleRight: {
fill: '#000',
font: '11px Arial'
},
axisTitleBottom: {
fill: '#000',
font: '11px Arial'
},
series: {
'stroke-width': 1
},
seriesLabel: {
font: '12px Arial',
fill: '#333'
},
marker: {
stroke: '#555',
fill: '#000',
radius: 3,
size: 3
},
seriesThemes: [{
fill: '#C6DBEF'
}, {
fill: '#9ECAE1'
}, {
fill: '#6BAED6'
}, {
fill: '#4292C6'
}, {
fill: '#2171B5'
}, {
fill: '#084594'
}],
markerThemes: [{
fill: '#084594',
type: 'circle'
}, {
fill: '#2171B5',
type: 'cross'
}, {
fill: '#4292C6',
type: 'plus'
}]
}
...or also takes just an array of colors and creates the complex object:
{
colors: ['#aaa', '#bcd', '#eee']
}
...or takes just a base color and makes a theme from it
{
baseColor: '#bce'
}
To create a new theme you may add it to the Themes object:
Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
constructor: function(config) {
Ext.chart.theme.call(this, config, {
baseColor: '#mybasecolor'
});
}
});
//Proposal:
Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
...and then to use it provide the name of the theme (as a lower case string) in the chart config.
{
theme: 'mynewtheme'
}
*/
(function() {
Ext.chart.theme = function(config, base) {
config = config || {};
var i = 0, l, colors, color,
seriesThemes, markerThemes,
seriesTheme, markerTheme,
key, gradients = [],
midColor, midL;
if (config.baseColor) {
midColor = Ext.draw.Color.fromString(config.baseColor);
midL = midColor.getHSL()[2];
if (midL < 0.15) {
midColor = midColor.getLighter(0.3);
} else if (midL < 0.3) {
midColor = midColor.getLighter(0.15);
} else if (midL > 0.85) {
midColor = midColor.getDarker(0.3);
} else if (midL > 0.7) {
midColor = midColor.getDarker(0.15);
}
config.colors = [ midColor.getDarker(0.3).toString(),
midColor.getDarker(0.15).toString(),
midColor.toString(),
midColor.getLighter(0.15).toString(),
midColor.getLighter(0.3).toString()];
delete config.baseColor;
}
if (config.colors) {
colors = config.colors.slice();
markerThemes = base.markerThemes;
seriesThemes = base.seriesThemes;
l = colors.length;
base.colors = colors;
for (; i < l; i++) {
color = colors[i];
markerTheme = markerThemes[i] || {};
seriesTheme = seriesThemes[i] || {};
markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
markerThemes[i] = markerTheme;
seriesThemes[i] = seriesTheme;
}
base.markerThemes = markerThemes.slice(0, l);
base.seriesThemes = seriesThemes.slice(0, l);
//the user is configuring something in particular (either markers, series or pie slices)
}
for (key in base) {
if (key in config) {
if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
Ext.apply(base[key], config[key]);
} else {
base[key] = config[key];
}
}
}
if (config.useGradients) {
colors = base.colors || (function () {
var ans = [];
for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
}
return ans;
})();
for (i = 0, l = colors.length; i < l; i++) {
midColor = Ext.draw.Color.fromString(colors[i]);
if (midColor) {
color = midColor.getDarker(0.1).toString();
midColor = midColor.toString();
key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
gradients.push({
id: key,
angle: 45,
stops: {
0: {
color: midColor.toString()
},
100: {
color: color.toString()
}
}
});
colors[i] = 'url(#' + key + ')';
}
}
base.gradients = gradients;
base.colors = colors;
}
/*
base.axis = Ext.apply(base.axis || {}, config.axis || {});
base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
*/
Ext.apply(this, base);
};
})();
});

View File

@@ -0,0 +1,875 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.container.AbstractContainer
* @extends Ext.Component
* An abstract base class which provides shared methods for Containers across the Sencha product line.
* @private
*/
Ext.define('Ext.container.AbstractContainer', {
/* Begin Definitions */
extend: 'Ext.Component',
requires: [
'Ext.util.MixedCollection',
'Ext.layout.container.Auto',
'Ext.ZIndexManager'
],
/* End Definitions */
/**
* @cfg {String/Object} layout
* <p><b>Important</b>: In order for child items to be correctly sized and
* positioned, typically a layout manager <b>must</b> be specified through
* the <code>layout</code> configuration option.</p>
* <p>The sizing and positioning of child {@link #items} is the responsibility of
* the Container's layout manager which creates and manages the type of layout
* you have in mind. For example:</p>
* <p>If the {@link #layout} configuration is not explicitly specified for
* a general purpose container (e.g. Container or Panel) the
* {@link Ext.layout.container.Auto default layout manager} will be used
* which does nothing but render child components sequentially into the
* Container (no sizing or positioning will be performed in this situation).</p>
* <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
* <div><ul class="mdetail-params">
* <li><u>Specify as an Object</u></li>
* <div><ul class="mdetail-params">
* <li>Example usage:</li>
* <pre><code>
layout: {
type: 'vbox',
align: 'left'
}
</code></pre>
*
* <li><code><b>type</b></code></li>
* <br/><p>The layout type to be used for this container. If not specified,
* a default {@link Ext.layout.container.Auto} will be created and used.</p>
* <p>Valid layout <code>type</code> values are:</p>
* <div class="sub-desc"><ul class="mdetail-params">
* <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
* <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
* <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
* <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
* <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
* <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
* <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
* </ul></div>
*
* <li>Layout specific configuration properties</li>
* <p>Additional layout specific configuration properties may also be
* specified. For complete details regarding the valid config options for
* each layout type, see the layout class corresponding to the <code>type</code>
* specified.</p>
*
* </ul></div>
*
* <li><u>Specify as a String</u></li>
* <div><ul class="mdetail-params">
* <li>Example usage:</li>
* <pre><code>
layout: 'vbox'
</code></pre>
* <li><code><b>layout</b></code></li>
* <p>The layout <code>type</code> to be used for this container (see list
* of valid layout type values above).</p>
* <p>Additional layout specific configuration properties. For complete
* details regarding the valid config options for each layout type, see the
* layout class corresponding to the <code>layout</code> specified.</p>
* </ul></div></ul></div>
*/
/**
* @cfg {String/Number} activeItem
* A string component id or the numeric index of the component that should be initially activated within the
* container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
* item in the container's collection). activeItem only applies to layout styles that can display
* items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
*/
/**
* @cfg {Object/Object[]} items
* <p>A single item, or an array of child Components to be added to this container</p>
* <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
* its encapsulating element and performs no sizing or positioning upon them.</b><p>
* <p>Example:</p>
* <pre><code>
// specifying a single item
items: {...},
layout: 'fit', // The single items is sized to fit
// specifying multiple items
items: [{...}, {...}],
layout: 'hbox', // The items are arranged horizontally
</code></pre>
* <p>Each item may be:</p>
* <ul>
* <li>A {@link Ext.Component Component}</li>
* <li>A Component configuration object</li>
* </ul>
* <p>If a configuration object is specified, the actual type of Component to be
* instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
* <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
* <p>If an {@link Ext.Component#xtype xtype} is not explicitly
* specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
* <p><b>Notes</b>:</p>
* <p>Ext uses lazy rendering. Child Components will only be rendered
* should it become necessary. Items are automatically laid out when they are first
* shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
* <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
* <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
*/
/**
* @cfg {Object/Function} defaults
* This option is a means of applying default settings to all added items whether added through the {@link #items}
* config or via the {@link #add} or {@link #insert} methods.
*
* Defaults are applied to both config objects and instantiated components conditionally so as not to override
* existing properties in the item (see {@link Ext#applyIf}).
*
* If the defaults option is specified as a function, then the function will be called using this Container as the
* scope (`this` reference) and passing the added item as the first parameter. Any resulting object
* from that call is then applied to the item as default properties.
*
* For example, to automatically apply padding to the body of each of a set of
* contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
*
* Usage:
*
* defaults: { // defaults are applied to items, not the container
* autoScroll: true
* },
* items: [
* // default will not be applied here, panel1 will be autoScroll: false
* {
* xtype: 'panel',
* id: 'panel1',
* autoScroll: false
* },
* // this component will have autoScroll: true
* new Ext.panel.Panel({
* id: 'panel2'
* })
* ]
*/
/** @cfg {Boolean} suspendLayout
* If true, suspend calls to doLayout. Useful when batching multiple adds to a container and not passing them
* as multiple arguments or an array.
*/
suspendLayout : false,
/** @cfg {Boolean} autoDestroy
* If true the container will automatically destroy any contained component that is removed from it, else
* destruction must be handled manually.
* Defaults to true.
*/
autoDestroy : true,
/** @cfg {String} defaultType
* <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
* a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
* <p>Defaults to <code>'panel'</code>.</p>
*/
defaultType: 'panel',
isContainer : true,
/**
* The number of container layout calls made on this object.
* @property layoutCounter
* @type {Number}
* @private
*/
layoutCounter : 0,
baseCls: Ext.baseCSSPrefix + 'container',
/**
* @cfg {String[]} bubbleEvents
* <p>An array of events that, when fired, should be bubbled to any parent container.
* See {@link Ext.util.Observable#enableBubble}.
* Defaults to <code>['add', 'remove']</code>.
*/
bubbleEvents: ['add', 'remove'],
// @private
initComponent : function(){
var me = this;
me.addEvents(
/**
* @event afterlayout
* Fires when the components in this container are arranged by the associated layout manager.
* @param {Ext.container.Container} this
* @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
*/
'afterlayout',
/**
* @event beforeadd
* Fires before any {@link Ext.Component} is added or inserted into the container.
* A handler can return false to cancel the add.
* @param {Ext.container.Container} this
* @param {Ext.Component} component The component being added
* @param {Number} index The index at which the component will be added to the container's items collection
*/
'beforeadd',
/**
* @event beforeremove
* Fires before any {@link Ext.Component} is removed from the container. A handler can return
* false to cancel the remove.
* @param {Ext.container.Container} this
* @param {Ext.Component} component The component being removed
*/
'beforeremove',
/**
* @event add
* @bubbles
* Fires after any {@link Ext.Component} is added or inserted into the container.
* @param {Ext.container.Container} this
* @param {Ext.Component} component The component that was added
* @param {Number} index The index at which the component was added to the container's items collection
*/
'add',
/**
* @event remove
* @bubbles
* Fires after any {@link Ext.Component} is removed from the container.
* @param {Ext.container.Container} this
* @param {Ext.Component} component The component that was removed
*/
'remove'
);
// layoutOnShow stack
me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
me.callParent();
me.initItems();
},
// @private
initItems : function() {
var me = this,
items = me.items;
/**
* The MixedCollection containing all the child items of this container.
* @property items
* @type Ext.util.MixedCollection
*/
me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
if (items) {
if (!Ext.isArray(items)) {
items = [items];
}
me.add(items);
}
},
// @private
afterRender : function() {
this.getLayout();
this.callParent();
},
renderChildren: function () {
var me = this,
layout = me.getLayout();
me.callParent();
// this component's elements exist, so now create the child components' elements
if (layout) {
me.suspendLayout = true;
layout.renderChildren();
delete me.suspendLayout;
}
},
// @private
setLayout : function(layout) {
var currentLayout = this.layout;
if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
currentLayout.setOwner(null);
}
this.layout = layout;
layout.setOwner(this);
},
/**
* Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
* If a layout has not been instantiated yet, that is done first
* @return {Ext.layout.container.AbstractContainer} The layout
*/
getLayout : function() {
var me = this;
if (!me.layout || !me.layout.isLayout) {
me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
}
return me.layout;
},
/**
* Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
* form most cases.
* @return {Ext.container.Container} this
*/
doLayout : function() {
var me = this,
layout = me.getLayout();
if (me.rendered && layout && !me.suspendLayout) {
// If either dimension is being auto-set, then it requires a ComponentLayout to be run.
if (!me.isFixedWidth() || !me.isFixedHeight()) {
// Only run the ComponentLayout if it is not already in progress
if (me.componentLayout.layoutBusy !== true) {
me.doComponentLayout();
if (me.componentLayout.layoutCancelled === true) {
layout.layout();
}
}
}
// Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
else {
// Only run the ContainerLayout if it is not already in progress
if (layout.layoutBusy !== true) {
layout.layout();
}
}
}
return me;
},
// @private
afterLayout : function(layout) {
++this.layoutCounter;
this.fireEvent('afterlayout', this, layout);
},
// @private
prepareItems : function(items, applyDefaults) {
if (!Ext.isArray(items)) {
items = [items];
}
// Make sure defaults are applied and item is initialized
var i = 0,
len = items.length,
item;
for (; i < len; i++) {
item = items[i];
if (applyDefaults) {
item = this.applyDefaults(item);
}
items[i] = this.lookupComponent(item);
}
return items;
},
// @private
applyDefaults : function(config) {
var defaults = this.defaults;
if (defaults) {
if (Ext.isFunction(defaults)) {
defaults = defaults.call(this, config);
}
if (Ext.isString(config)) {
config = Ext.ComponentManager.get(config);
}
Ext.applyIf(config, defaults);
}
return config;
},
// @private
lookupComponent : function(comp) {
return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
},
// @private
createComponent : function(config, defaultType) {
// // add in ownerCt at creation time but then immediately
// // remove so that onBeforeAdd can handle it
// var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
//
// delete component.initialConfig.ownerCt;
// delete component.ownerCt;
return Ext.ComponentManager.create(config, defaultType || this.defaultType);
},
// @private - used as the key lookup function for the items collection
getComponentId : function(comp) {
return comp.getItemId();
},
/**
Adds {@link Ext.Component Component}(s) to this Container.
##Description:##
- Fires the {@link #beforeadd} event before adding.
- The Container's {@link #defaults default config values} will be applied
accordingly (see `{@link #defaults}` for details).
- Fires the `{@link #add}` event after the component has been added.
##Notes:##
If the Container is __already rendered__ when `add`
is called, it will render the newly added Component into its content area.
__**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
will recalculate its internal layout at this time too.
Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
tb = new {@link Ext.toolbar.Toolbar}({
renderTo: document.body
}); // toolbar is rendered
tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
##Warning:##
Components directly managed by the BorderLayout layout manager
may not be removed or added. See the Notes for {@link Ext.layout.container.Border BorderLayout}
for more details.
* @param {Ext.Component[]/Ext.Component...} component
* Either one or more Components to add or an Array of Components to add.
* See `{@link #items}` for additional information.
*
* @return {Ext.Component[]/Ext.Component} The Components that were added.
* @markdown
*/
add : function() {
var me = this,
args = Array.prototype.slice.call(arguments),
hasMultipleArgs,
items,
results = [],
i,
ln,
item,
index = -1,
cmp;
if (typeof args[0] == 'number') {
index = args.shift();
}
hasMultipleArgs = args.length > 1;
if (hasMultipleArgs || Ext.isArray(args[0])) {
items = hasMultipleArgs ? args : args[0];
// Suspend Layouts while we add multiple items to the container
me.suspendLayout = true;
for (i = 0, ln = items.length; i < ln; i++) {
item = items[i];
//<debug>
if (!item) {
Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
}
//</debug>
if (index != -1) {
item = me.add(index + i, item);
} else {
item = me.add(item);
}
results.push(item);
}
// Resume Layouts now that all items have been added and do a single layout for all the items just added
me.suspendLayout = false;
me.doLayout();
return results;
}
cmp = me.prepareItems(args[0], true)[0];
// Floating Components are not added into the items collection
// But they do get an upward ownerCt link so that they can traverse
// up to their z-index parent.
if (cmp.floating) {
cmp.onAdded(me, index);
} else {
index = (index !== -1) ? index : me.items.length;
if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
me.items.insert(index, cmp);
cmp.onAdded(me, index);
me.onAdd(cmp, index);
me.fireEvent('add', me, cmp, index);
}
me.doLayout();
}
return cmp;
},
onAdd : Ext.emptyFn,
onRemove : Ext.emptyFn,
/**
* Inserts a Component into this Container at a specified index. Fires the
* {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
* Component has been inserted.
* @param {Number} index The index at which the Component will be inserted
* into the Container's items collection
* @param {Ext.Component} component The child Component to insert.<br><br>
* Ext uses lazy rendering, and will only render the inserted Component should
* it become necessary.<br><br>
* A Component config object may be passed in order to avoid the overhead of
* constructing a real Component object if lazy rendering might mean that the
* inserted Component will not be rendered immediately. To take advantage of
* this 'lazy instantiation', set the {@link Ext.Component#xtype} config
* property to the registered type of the Component wanted.<br><br>
* For a list of all available xtypes, see {@link Ext.Component}.
* @return {Ext.Component} component The Component (or config object) that was
* inserted with the Container's default config values applied.
*/
insert : function(index, comp) {
return this.add(index, comp);
},
/**
* Moves a Component within the Container
* @param {Number} fromIdx The index the Component you wish to move is currently at.
* @param {Number} toIdx The new index for the Component.
* @return {Ext.Component} component The Component (or config object) that was moved.
*/
move : function(fromIdx, toIdx) {
var items = this.items,
item;
item = items.removeAt(fromIdx);
if (item === false) {
return false;
}
items.insert(toIdx, item);
this.doLayout();
return item;
},
// @private
onBeforeAdd : function(item) {
var me = this;
if (item.ownerCt) {
item.ownerCt.remove(item, false);
}
if (me.border === false || me.border === 0) {
item.border = (item.border === true);
}
},
/**
* Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
* the {@link #remove} event after the component has been removed.
* @param {Ext.Component/String} component The component reference or id to remove.
* @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
* Defaults to the value of this Container's {@link #autoDestroy} config.
* @return {Ext.Component} component The Component that was removed.
*/
remove : function(comp, autoDestroy) {
var me = this,
c = me.getComponent(comp);
//<debug>
if (Ext.isDefined(Ext.global.console) && !c) {
console.warn("Attempted to remove a component that does not exist. Ext.container.Container: remove takes an argument of the component to remove. cmp.remove() is incorrect usage.");
}
//</debug>
if (c && me.fireEvent('beforeremove', me, c) !== false) {
me.doRemove(c, autoDestroy);
me.fireEvent('remove', me, c);
}
return c;
},
// @private
doRemove : function(component, autoDestroy) {
var me = this,
layout = me.layout,
hasLayout = layout && me.rendered;
me.items.remove(component);
component.onRemoved();
if (hasLayout) {
layout.onRemove(component);
}
me.onRemove(component, autoDestroy);
if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
component.destroy();
}
if (hasLayout && !autoDestroy) {
layout.afterRemove(component);
}
if (!me.destroying) {
me.doLayout();
}
},
/**
* Removes all components from this container.
* @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
* Defaults to the value of this Container's {@link #autoDestroy} config.
* @return {Ext.Component[]} Array of the destroyed components
*/
removeAll : function(autoDestroy) {
var me = this,
removeItems = me.items.items.slice(),
items = [],
i = 0,
len = removeItems.length,
item;
// Suspend Layouts while we remove multiple items from the container
me.suspendLayout = true;
for (; i < len; i++) {
item = removeItems[i];
me.remove(item, autoDestroy);
if (item.ownerCt !== me) {
items.push(item);
}
}
// Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
me.suspendLayout = false;
if (len) {
me.doLayout();
}
return items;
},
// Used by ComponentQuery to retrieve all of the items
// which can potentially be considered a child of this Container.
// This should be overriden by components which have child items
// that are not contained in items. For example dockedItems, menu, etc
// IMPORTANT note for maintainers:
// Items are returned in tree traversal order. Each item is appended to the result array
// followed by the results of that child's getRefItems call.
// Floating child items are appended after internal child items.
getRefItems : function(deep) {
var me = this,
items = me.items.items,
len = items.length,
i = 0,
item,
result = [];
for (; i < len; i++) {
item = items[i];
result.push(item);
if (deep && item.getRefItems) {
result.push.apply(result, item.getRefItems(true));
}
}
// Append floating items to the list.
// These will only be present after they are rendered.
if (me.floatingItems && me.floatingItems.accessList) {
result.push.apply(result, me.floatingItems.accessList);
}
return result;
},
/**
* Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
* each component. The scope (<code>this</code> reference) of the
* function call will be the scope provided or the current component. The arguments to the function
* will be the args provided or the current component. If the function returns false at any point,
* the cascade is stopped on that branch.
* @param {Function} fn The function to call
* @param {Object} [scope] The scope of the function (defaults to current component)
* @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
* @return {Ext.Container} this
*/
cascade : function(fn, scope, origArgs){
var me = this,
cs = me.items ? me.items.items : [],
len = cs.length,
i = 0,
c,
args = origArgs ? origArgs.concat(me) : [me],
componentIndex = args.length - 1;
if (fn.apply(scope || me, args) !== false) {
for(; i < len; i++){
c = cs[i];
if (c.cascade) {
c.cascade(fn, scope, origArgs);
} else {
args[componentIndex] = c;
fn.apply(scope || cs, args);
}
}
}
return this;
},
/**
* Examines this container's <code>{@link #items}</code> <b>property</b>
* and gets a direct child component of this container.
* @param {String/Number} comp This parameter may be any of the following:
* <div><ul class="mdetail-params">
* <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
* or <code>{@link Ext.Component#id id}</code> of the child component </li>
* <li>a <b><code>Number</code></b> : representing the position of the child component
* within the <code>{@link #items}</code> <b>property</b></li>
* </ul></div>
* <p>For additional information see {@link Ext.util.MixedCollection#get}.
* @return Ext.Component The component (if found).
*/
getComponent : function(comp) {
if (Ext.isObject(comp)) {
comp = comp.getItemId();
}
return this.items.get(comp);
},
/**
* Retrieves all descendant components which match the passed selector.
* Executes an Ext.ComponentQuery.query using this container as its root.
* @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
* If no selector is specified all items will be returned.
* @return {Ext.Component[]} Components which matched the selector
*/
query : function(selector) {
selector = selector || '*';
return Ext.ComponentQuery.query(selector, this);
},
/**
* Retrieves the first direct child of this container which matches the passed selector.
* The passed in selector must comply with an Ext.ComponentQuery selector.
* @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
* specified, the first child will be returned.
* @return Ext.Component
*/
child : function(selector) {
selector = selector || '';
return this.query('> ' + selector)[0] || null;
},
/**
* Retrieves the first descendant of this container which matches the passed selector.
* The passed in selector must comply with an Ext.ComponentQuery selector.
* @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
* specified, the first child will be returned.
* @return Ext.Component
*/
down : function(selector) {
return this.query(selector)[0] || null;
},
// inherit docs
show : function() {
this.callParent(arguments);
this.performDeferredLayouts();
return this;
},
// Lay out any descendant containers who queued a layout operation during the time this was hidden
// This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
performDeferredLayouts: function() {
var layoutCollection = this.layoutOnShow,
ln = layoutCollection.getCount(),
i = 0,
needsLayout,
item;
for (; i < ln; i++) {
item = layoutCollection.get(i);
needsLayout = item.needsLayout;
if (Ext.isObject(needsLayout)) {
item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
}
}
layoutCollection.clear();
},
//@private
// Enable all immediate children that was previously disabled
onEnable: function() {
Ext.Array.each(this.query('[isFormField]'), function(item) {
if (item.resetDisable) {
item.enable();
delete item.resetDisable;
}
});
this.callParent();
},
// @private
// Disable all immediate children that was previously disabled
onDisable: function() {
Ext.Array.each(this.query('[isFormField]'), function(item) {
if (item.resetDisable !== false && !item.disabled) {
item.disable();
item.resetDisable = true;
}
});
this.callParent();
},
/**
* Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
* from being executed.
*/
beforeLayout: function() {
return true;
},
// @private
beforeDestroy : function() {
var me = this,
items = me.items,
c;
if (items) {
while ((c = items.first())) {
me.doRemove(c, true);
}
}
Ext.destroy(
me.layout
);
me.callParent();
}
});

View File

@@ -0,0 +1,189 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Provides a container for arranging a group of related Buttons in a tabular manner.
*
* @example
* Ext.create('Ext.panel.Panel', {
* title: 'Panel with ButtonGroup',
* width: 300,
* height:200,
* renderTo: document.body,
* bodyPadding: 10,
* html: 'HTML Panel Content',
* tbar: [{
* xtype: 'buttongroup',
* columns: 3,
* title: 'Clipboard',
* items: [{
* text: 'Paste',
* scale: 'large',
* rowspan: 3,
* iconCls: 'add',
* iconAlign: 'top',
* cls: 'btn-as-arrow'
* },{
* xtype:'splitbutton',
* text: 'Menu Button',
* scale: 'large',
* rowspan: 3,
* iconCls: 'add',
* iconAlign: 'top',
* arrowAlign:'bottom',
* menu: [{ text: 'Menu Item 1' }]
* },{
* xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
* },{
* text: 'Copy', iconCls: 'add16'
* },{
* text: 'Format', iconCls: 'add16'
* }]
* }]
* });
*
*/
Ext.define('Ext.container.ButtonGroup', {
extend: 'Ext.panel.Panel',
alias: 'widget.buttongroup',
alternateClassName: 'Ext.ButtonGroup',
/**
* @cfg {Number} columns The `columns` configuration property passed to the
* {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
*/
/**
* @cfg {String} baseCls Defaults to <tt>'x-btn-group'</tt>. See {@link Ext.panel.Panel#baseCls}.
*/
baseCls: Ext.baseCSSPrefix + 'btn-group',
/**
* @cfg {Object} layout Defaults to <tt>'table'</tt>. See {@link Ext.container.Container#layout}.
*/
layout: {
type: 'table'
},
defaultType: 'button',
/**
* @cfg {Boolean} frame Defaults to <tt>true</tt>. See {@link Ext.panel.Panel#frame}.
*/
frame: true,
frameHeader: false,
internalDefaults: {removeMode: 'container', hideParent: true},
initComponent : function(){
// Copy the component's columns config to the layout if specified
var me = this,
cols = me.columns;
me.noTitleCls = me.baseCls + '-notitle';
if (cols) {
me.layout = Ext.apply({}, {columns: cols}, me.layout);
}
if (!me.title) {
me.addCls(me.noTitleCls);
}
me.callParent(arguments);
},
afterLayout: function() {
var me = this;
me.callParent(arguments);
// Pugly hack for a pugly browser:
// If not an explicitly set width, then size the width to match the inner table
if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
var t = me.getTargetEl();
t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
}
// IE7 needs a forced repaint to make the top framing div expand to full width
if (Ext.isIE7) {
me.el.repaint();
}
},
afterRender: function() {
var me = this;
//we need to add an addition item in here so the ButtonGroup title is centered
if (me.header) {
// Header text cannot flex, but must be natural size if it's being centered
delete me.header.items.items[0].flex;
// For Centering, surround the text with two flex:1 spacers.
me.suspendLayout = true;
me.header.insert(1, {
xtype: 'component',
ui : me.ui,
flex : 1
});
me.header.insert(0, {
xtype: 'component',
ui : me.ui,
flex : 1
});
me.suspendLayout = false;
}
me.callParent(arguments);
},
// private
onBeforeAdd: function(component) {
if (component.is('button')) {
component.ui = component.ui + '-toolbar';
}
this.callParent(arguments);
},
//private
applyDefaults: function(c) {
if (!Ext.isString(c)) {
c = this.callParent(arguments);
var d = this.internalDefaults;
if (c.events) {
Ext.applyIf(c.initialConfig, d);
Ext.apply(c, d);
} else {
Ext.applyIf(c, d);
}
}
return c;
}
/**
* @cfg {Array} tools @hide
*/
/**
* @cfg {Boolean} collapsible @hide
*/
/**
* @cfg {Boolean} collapseMode @hide
*/
/**
* @cfg {Boolean} animCollapse @hide
*/
/**
* @cfg {Boolean} closable @hide
*/
});

View File

@@ -0,0 +1,189 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
* containing items, namely adding, inserting and removing items.
*
* The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
* Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
* lightweight Container to be encapsulated by an HTML element to your specifications by using the
* {@link Ext.Component#autoEl autoEl} config option.
*
* The code below illustrates how to explicitly create a Container:
*
* @example
* // Explicitly create a Container
* Ext.create('Ext.container.Container', {
* layout: {
* type: 'hbox'
* },
* width: 400,
* renderTo: Ext.getBody(),
* border: 1,
* style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
* defaults: {
* labelWidth: 80,
* // implicitly create Container by specifying xtype
* xtype: 'datefield',
* flex: 1,
* style: {
* padding: '10px'
* }
* },
* items: [{
* xtype: 'datefield',
* name: 'startDate',
* fieldLabel: 'Start date'
* },{
* xtype: 'datefield',
* name: 'endDate',
* fieldLabel: 'End date'
* }]
* });
*
* ## Layout
*
* Container classes delegate the rendering of child Components to a layout manager class which must be configured into
* the Container using the `{@link #layout}` configuration property.
*
* When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
* Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
* elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
* {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
* inside the Container, and **does not apply any sizing** at all.
*
* A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
* TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
* use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
* any way when the Container is resized.
*
* Certain layout managers allow dynamic addition of child components. Those that do include
* Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
* Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
*
* // Create the GridPanel.
* var myNewGrid = new Ext.grid.Panel({
* store: myStore,
* headers: myHeaders,
* title: 'Results', // the title becomes the title of the tab
* });
*
* myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
* myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
*
* The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
* Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
* Ext.layout.container.Fit fit} exactly into its client area.
*
* **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
* wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
* wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
* directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
* GridPanel will not be sized as expected.
*
* ## Adding via remote configuration
*
* A server side script can be used to add Components which are generated dynamically on the server. An example of
* adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
*
* // execute an Ajax request to invoke server side script:
* Ext.Ajax.request({
* url: 'gen-invoice-grid.php',
* // send additional parameters to instruct server script
* params: {
* startDate: Ext.getCmp('start-date').getValue(),
* endDate: Ext.getCmp('end-date').getValue()
* },
* // process the response object to add it to the TabPanel:
* success: function(xhr) {
* var newComponent = eval(xhr.responseText); // see discussion below
* myTabPanel.add(newComponent); // add the component to the TabPanel
* myTabPanel.setActiveTab(newComponent);
* },
* failure: function() {
* Ext.Msg.alert("Grid create failed", "Server communication failure");
* }
* });
*
* The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
* config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
*
* {
* "xtype": 'grid',
* "title": 'Invoice Report',
* "store": {
* "model": 'Invoice',
* "proxy": {
* "type": 'ajax',
* "url": 'get-invoice-data.php',
* "reader": {
* "type": 'json'
* "record": 'transaction',
* "idProperty": 'id',
* "totalRecords": 'total'
* })
* },
* "autoLoad": {
* "params": {
* "startDate": '01/01/2008',
* "endDate": '01/31/2008'
* }
* }
* },
* "headers": [
* {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
* {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
* {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
* {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
* ]
* }
*
* When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
* result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
* that the Container is configured with a layout which sizes and positions the child items to your requirements.**
*
* **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
* preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
* into the code since these are all known on the server.
*/
Ext.define('Ext.container.Container', {
extend: 'Ext.container.AbstractContainer',
alias: 'widget.container',
alternateClassName: 'Ext.Container',
/**
* Return the immediate child Component in which the passed element is located.
* @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
* @return {Ext.Component} The child item which contains the passed element.
*/
getChildByElement: function(el) {
var item,
itemEl,
i = 0,
it = this.items.items,
ln = it.length;
el = Ext.getDom(el);
for (; i < ln; i++) {
item = it[i];
itemEl = item.getEl();
if ((itemEl.dom === el) || itemEl.contains(el)) {
return item;
}
}
return null;
}
});

View File

@@ -0,0 +1,178 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* A specialized container representing the viewable application area (the browser viewport).
*
* The Viewport renders itself to the document body, and automatically sizes itself to the size of
* the browser viewport and manages window resizing. There may only be one Viewport created
* in a page.
*
* Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
* on its child Components if you configure it with a {@link #layout}.
*
* A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
* required layout is simpler, a different layout should be chosen.
*
* For example, to simply make a single child item occupy all available space, use
* {@link Ext.layout.container.Fit fit layout}.
*
* To display one "active" item at full size from a choice of several child items, use
* {@link Ext.layout.container.Card card layout}.
*
* Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
* added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
* method of any of its child Panels may themselves have a layout.
*
* The Viewport does not provide scrolling, so child Panels within the Viewport should provide
* for scrolling if needed using the {@link #autoScroll} config.
*
* An example showing a classic application border layout:
*
* @example
* Ext.create('Ext.container.Viewport', {
* layout: 'border',
* items: [{
* region: 'north',
* html: '<h1 class="x-panel-header">Page Title</h1>',
* autoHeight: true,
* border: false,
* margins: '0 0 5 0'
* }, {
* region: 'west',
* collapsible: true,
* title: 'Navigation',
* width: 150
* // could use a TreePanel or AccordionLayout for navigational items
* }, {
* region: 'south',
* title: 'South Panel',
* collapsible: true,
* html: 'Information goes here',
* split: true,
* height: 100,
* minHeight: 100
* }, {
* region: 'east',
* title: 'East Panel',
* collapsible: true,
* split: true,
* width: 150
* }, {
* region: 'center',
* xtype: 'tabpanel', // TabPanel itself has no title
* activeTab: 0, // First tab active by default
* items: {
* title: 'Default Tab',
* html: 'The first tab\'s content. Others may be added dynamically'
* }
* }]
* });
*/
Ext.define('Ext.container.Viewport', {
extend: 'Ext.container.Container',
alias: 'widget.viewport',
requires: ['Ext.EventManager'],
alternateClassName: 'Ext.Viewport',
// Privatize config options which, if used, would interfere with the
// correct operation of the Viewport as the sole manager of the
// layout of the document body.
/**
* @cfg {String/HTMLElement/Ext.Element} applyTo
* Not applicable.
*/
/**
* @cfg {Boolean} allowDomMove
* Not applicable.
*/
/**
* @cfg {Boolean} hideParent
* Not applicable.
*/
/**
* @cfg {String/HTMLElement/Ext.Element} renderTo
* Not applicable. Always renders to document body.
*/
/**
* @cfg {Boolean} hideParent
* Not applicable.
*/
/**
* @cfg {Number} height
* Not applicable. Sets itself to viewport width.
*/
/**
* @cfg {Number} width
* Not applicable. Sets itself to viewport height.
*/
/**
* @cfg {Boolean} autoHeight
* Not applicable.
*/
/**
* @cfg {Boolean} autoWidth
* Not applicable.
*/
/**
* @cfg {Boolean} deferHeight
* Not applicable.
*/
/**
* @cfg {Boolean} monitorResize
* Not applicable.
*/
isViewport: true,
ariaRole: 'application',
initComponent : function() {
var me = this,
html = Ext.fly(document.body.parentNode),
el;
me.callParent(arguments);
html.addCls(Ext.baseCSSPrefix + 'viewport');
if (me.autoScroll) {
html.setStyle('overflow', 'auto');
}
me.el = el = Ext.getBody();
el.setHeight = Ext.emptyFn;
el.setWidth = Ext.emptyFn;
el.setSize = Ext.emptyFn;
el.dom.scroll = 'no';
me.allowDomMove = false;
Ext.EventManager.onWindowResize(me.fireResize, me);
me.renderTo = me.el;
me.width = Ext.Element.getViewportWidth();
me.height = Ext.Element.getViewportHeight();
},
fireResize : function(w, h){
// setSize is the single entry point to layouts
this.setSize(w, h);
}
});

View File

@@ -0,0 +1,321 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Ext Core 4 Class Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
pre { border: 1px gray solid; padding: 5px; margin-top: 30px; }
</style>
<script type="text/javascript" src="../src/Ext.js"></script>
<script type="text/javascript" src="../src/version/Version.js"></script>
<script type="text/javascript" src="../src/lang/String.js"></script>
<script type="text/javascript" src="../src/lang/Array.js"></script>
<script type="text/javascript" src="../src/lang/Number.js"></script>
<script type="text/javascript" src="../src/lang/Date.js"></script>
<script type="text/javascript" src="../src/lang/Object.js"></script>
<script type="text/javascript" src="../src/lang/Function.js"></script>
<script type="text/javascript" src="../src/class/Base.js"></script>
<script type="text/javascript" src="../src/class/Class.js"></script>
<script type="text/javascript" src="../src/class/ClassManager.js"></script>
<script type="text/javascript" src="../src/class/Loader.js"></script>
<script type="text/javascript" src="../src/lang/Error.js"></script>
<script>
Ext.Loader.setConfig({
enabled: true,
paths: {
'Sample': 'src/Sample'
}
});
</script>
<script>
var sources = {};
function storeCode() {
var i = 0,
name,
pre;
while (++i) {
name = 'sample' + i;
pre = document.getElementById(name);
if (!pre) {
break;
}
sources[name] = pre.innerHTML;
}
}
function run(id) {
eval(sources[id]);
}
</script>
</head>
<body onload="storeCode();">
<pre id="sample1">
Ext.require('Sample.Person', function() {
var aaron = new Sample.Person({
name: 'Aaron Conran',
gender: 'male',
isCool: true
});
alert(aaron.getName()); // 'Aaron Conran'
alert(aaron.getGender()); // 'male'
alert(aaron.getIsCool()); // true
aaron.eat("Salad"); // alerts "I'm eating: Salad"
aaron.setGender('weird');
alert(aaron.getGender()); // 'unknown'
});
</pre>
<p>
<button onclick="run('sample1')">Run Code</button>
</p>
<pre id="sample2">
Ext.require('Sample.Developer', function() {
var tommy = new Sample.Developer({
name: 'Tommy Maintz',
languages: ['PHP', 'JavaScript', 'HTML', 'CSS']
});
tommy.code('Objective-C'); // alerts "I can't code in: Objective-C"
tommy.code('JavaScript'); // alerts "I'm coding in: JavaScript"
// alerts "I'm eating: Bugs"
});
</pre>
<p>
<button onclick="run('sample2')">Run Code</button>
</p>
<pre id="sample3">
Ext.require('Sample.Musician', function() {
var dave = new Sample.Musician({
name: 'David Kaneda',
isCool: true
});
dave.composeSongs(); // alerts "David Kaneda is composing songs"
dave.playGuitar(); // alerts "David Kaneda is playing guitar"
var anotherDave = Sample.Musician.clone(dave);
alert(anotherDave.getName()); // alerts "David Kaneda"
});
</pre>
<p>
<button onclick="run('sample3')">Run Code</button>
</p>
<pre id="sample4">
Ext.require('Sample.CoolGuy', function() {
var jacky = new Sample.CoolGuy({
name: 'Jacky Nguyen',
isCool: true
});
jacky.playGuitar(); // alerts "Jacky Nguyen is playing guitar"
jacky.sing("Love Me or Die"); // alerts "Ahem..."
// alerts "I'm singing: Love Me or Die"
});
</pre>
<p>
<button onclick="run('sample4')">Run Code</button>
</p>
<pre id="sample5">
Ext.require(['Sample.Developer', 'Sample.HumanResource'], function() {
var jacky = new Sample.Developer({
name: 'Jacky Nguyen',
languages: ['PHP', 'JavaScript', 'HTML', 'CSS']
}),
noobie = new Sample.Developer({
name: 'Noobie Dumb',
languages: ['LameScript']
}),
hr = Sample.HumanResource;
hr.recruit(noobie); // alerts "Noobie Dumb doesn't know JavaScript, no point recruiting!"
hr.recruit(jacky);
alert(hr.getDevelopersCount()); // alerts 1
});
</pre>
<p>
<button onclick="run('sample5')">Run Code</button>
</p>
<pre id="sample6">
Ext.require(['Sample.deadlock.A'], function() {
// should throw Error
});
</pre>
<p>
<button onclick="run('sample6')">Run Code</button>
</p>
<pre id="sample7">
Ext.require(['Sample.notdeadlock.A'], function() {
alert("Loading sequence: " + Ext.Loader.history.join(" -> "));
});
</pre>
<p>
<button onclick="run('sample7')">Run Code</button>
</p>
<pre id="sample8">
Ext.require(['Sample.CTO', 'Sample.Developer'], function() {
var abe = new Sample.CTO();
alert(abe.isGeek); // alerts true
alert(abe.isSuperGeek); // alerts true
alert(abe.getAverageIQ()); // alerts 140
// not the current class
var ape = abe.clone();
alert(Ext.getClassName(ape)); // alerts 'Sample.CTO'
var jacky = new Sample.Developer();
alert(jacky.getAverageIQ()); // alerts 120
var jackie = abe.hireNewDeveloperLike(jacky);
alert(Ext.getClassName(jackie)); // alerts 'Sample.Developer'
});
</pre>
<p>
<button onclick="run('sample8')">Run Code</button>
</p>
<pre id="sample9">
Ext.define('My.own.A', {
statics: {
myName: 'A'
},
constructor: function() {
alert(this.statics().myName);
alert(this.self.myName);
},
clone: function() {
var cloned = new this.self();
cloned.rootName = this.statics().myName;
return cloned;
}
});
Ext.define('My.own.B', {
extend: 'My.own.A',
statics: {
myName: 'B'
},
clone: function() {
var cloned = this.callParent();
cloned.special = true;
return cloned;
}
});
var a = new My.own.A(); // alerts 'A' then alerts 'A'
var b = new My.own.B(); // alerts 'A' then alerts 'B'
var aa = a.clone();
var bb = b.clone();
alert(Ext.getClassName(aa)); // alerts 'My.own.A'
alert(Ext.getClassName(bb)); // alerts 'My.own.B'
alert(aa.rootName); // alerts 'A'
alert(bb.rootName); // alerts 'A'
alert(bb.special); // alerts true
</pre>
<p>
<button onclick="run('sample9')">Run Code</button>
</p>
<pre id="sample10">
Ext.define('My.Cat', {
statics: {
totalCreated: 0,
speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
},
constructor: function() {
var statics = this.statics();
alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
// equivalent to: My.Cat.speciesName
alert(this.self.speciesName); // dependent on 'this'
statics.totalCreated++;
return this;
},
clone: function() {
var cloned = new this.self; // dependent on 'this'
cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
return cloned;
}
});
Ext.define('My.SnowLeopard', {
extend: 'My.Cat',
statics: {
speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
},
constructor: function() {
this.callParent();
}
});
var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
var clone = snowLeopard.clone();
alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
alert(clone.groupName); // alerts 'Cat'
alert(My.Cat.totalCreated); // alerts 3
</pre>
<p>
<button onclick="run('sample10')">Run Code</button>
</p>
<pre id="sample11">
Ext.define('Bank', {
money: '$$$',
printMoney: function() {
alert('$$$$$$$');
}
});
Ext.define('Thief', {});
Thief.borrow(Bank, ['money', 'printMoney']);
var jacky = new Thief();
alert(jacky.money); // alerts '$$$'
jacky.printMoney(); // alerts '$$$$$$$'
</pre>
<p>
<button onclick="run('sample11')">Run Code</button>
</p>
</body>
</html>

View File

@@ -0,0 +1,21 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

View File

@@ -0,0 +1,40 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.CTO', {
extend: 'Sample.Developer',
statics: {
averageIQ: 140
},
constructor: function(config) {
this.callParent(arguments);
this.isSuperGeek = true;
},
hireNewDeveloperLike: function(developer) {
return developer.clone();
},
clone: function() {
var self = this.statics(),
cloned = new self(this.config);
alert(Ext.getClassName(this.callParent()));
return cloned;
}
});

View File

@@ -0,0 +1,35 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.CoolGuy', {
extend: 'Sample.Person',
mixins: {
guitar: 'Sample.ability.CanPlayGuitar',
sing: 'Sample.ability.CanSing'
},
constructor: function() {
this.config.knownSongs.push("Love Me or Die");
return this.callParent(arguments);
},
sing: function() {
alert("Ahem...");
return this.mixins.sing.sing.apply(this, arguments);
}
});

View File

@@ -0,0 +1,58 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.Developer', {
extend: 'Sample.Person',
statics: {
averageIQ: 120
},
config: {
languages: ['JavaScript', 'C++', 'Python']
},
constructor: function(config) {
this.isGeek = true;
// Apply a method from the parent class' prototype
return this.callParent(arguments);
},
canCode: function(language) {
return Ext.Array.contains(this.getLanguages(), language);
},
code: function(language) {
if (!this.canCode(language)) {
alert("I can't code in: " + language);
return this;
}
alert("I'm coding in: " + language);
this.eat("Bugs");
return this;
},
clone: function() {
var self = this.statics(),
cloned = new self(this.config);
return cloned;
}
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.Gun', {
});

View File

@@ -0,0 +1,38 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.HumanResource', {
requires: 'Sample.Developer',
singleton: true,
developers: [],
recruit: function(developer) {
if (!developer.canCode('JavaScript')) {
alert(developer.getName() + " doesn't know JavaScript, no point recruiting!");
}
else {
this.developers.push(developer);
}
return this;
},
getDevelopersCount: function() {
return this.developers.length;
}
});

View File

@@ -0,0 +1,35 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.Musician', {
extend: 'Sample.Person',
statics: {
clone: function(musician) {
return new this({
name: musician.getName(),
height: musician.getHeight(),
isCool: musician.getIsCool(),
gender: musician.getGender()
});
}
},
mixins: {
guitar: 'Sample.ability.CanPlayGuitar',
compose: 'Sample.ability.CanComposeSongs',
sing: 'Sample.ability.CanSing'
}
});

View File

@@ -0,0 +1,65 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.Person', {
uses: 'Sample.Gun',
statics: {
averageIQ: 100
},
config: {
name: 'Unknown',
gender: 'unknown',
isCool: false,
height: 5.8
},
constructor: function(config) {
this.initConfig(config);
return this;
},
eat: function(foodType) {
alert("I'm eating: " + foodType);
return this;
},
applyScroller: function(scroller) {
return new Ext.util.Scroller(scroller);
},
applyHeight: function(height) {
return parseFloat(height);
},
applyName: function(name) {
return name || 'Unknown';
},
applyGender: function(gender) {
if (!/^(male|female|gay|lesbian)$/.test(gender)) {
return 'unknown';
}
return gender;
},
getAverageIQ: function() {
return this.self.averageIQ;
}
});

View File

@@ -0,0 +1,23 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.ability.CanComposeSongs', {
canComposeSongs: true,
composeSongs: function() {
alert(this.getName() + " is composing songs");
return this;
}
});

View File

@@ -0,0 +1,23 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.ability.CanPlayGuitar', {
canPlayGuitar: true,
playGuitar: function() {
alert(this.getName() + " is playing guitar");
return this;
}
});

View File

@@ -0,0 +1,33 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.ability.CanSing', {
config: {
knownSongs: ['Yesterday', 'Happy New Year', 'Jingle Bells']
},
canSing: true,
sing: function(songName) {
if (!Ext.Array.contains(this.getKnownSongs(), songName)) {
alert("Sorry! I can't sing " + songName);
}
else {
alert("I'm singing " + songName);
}
return this;
}
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.deadlock.A', {
extend: 'Sample.deadlock.B'
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.deadlock.B', {
extend: 'Sample.deadlock.C'
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.deadlock.C', {
extend: 'Sample.deadlock.D'
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.deadlock.D', {
extend: 'Sample.deadlock.E'
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.deadlock.E', {
extend: 'Sample.deadlock.A'
});

View File

@@ -0,0 +1,17 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.notdeadlock.A', {
extend: 'Sample.notdeadlock.B'
});

View File

@@ -0,0 +1,19 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.notdeadlock.B', {
extend: 'Sample.notdeadlock.C',
uses: 'Sample.notdeadlock.A'
});

View File

@@ -0,0 +1,18 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
Ext.define('Sample.notdeadlock.C', {
uses: 'Sample.notdeadlock.A'
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,883 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.EventObject
Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
wraps the browser's native event-object normalizing cross-browser differences,
such as which mouse button is clicked, keys pressed, mechanisms to stop
event-propagation along with a method to prevent default actions from taking place.
For example:
function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
e.preventDefault();
var target = e.getTarget(); // same as t (the target HTMLElement)
...
}
var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
myDiv.on( // 'on' is shorthand for addListener
"click", // perform an action on click of myDiv
handleClick // reference to the action handler
);
// other methods to do the same:
Ext.EventManager.on("myDiv", 'click', handleClick);
Ext.EventManager.addListener("myDiv", 'click', handleClick);
* @singleton
* @markdown
*/
Ext.define('Ext.EventObjectImpl', {
uses: ['Ext.util.Point'],
/** Key constant @type Number */
BACKSPACE: 8,
/** Key constant @type Number */
TAB: 9,
/** Key constant @type Number */
NUM_CENTER: 12,
/** Key constant @type Number */
ENTER: 13,
/** Key constant @type Number */
RETURN: 13,
/** Key constant @type Number */
SHIFT: 16,
/** Key constant @type Number */
CTRL: 17,
/** Key constant @type Number */
ALT: 18,
/** Key constant @type Number */
PAUSE: 19,
/** Key constant @type Number */
CAPS_LOCK: 20,
/** Key constant @type Number */
ESC: 27,
/** Key constant @type Number */
SPACE: 32,
/** Key constant @type Number */
PAGE_UP: 33,
/** Key constant @type Number */
PAGE_DOWN: 34,
/** Key constant @type Number */
END: 35,
/** Key constant @type Number */
HOME: 36,
/** Key constant @type Number */
LEFT: 37,
/** Key constant @type Number */
UP: 38,
/** Key constant @type Number */
RIGHT: 39,
/** Key constant @type Number */
DOWN: 40,
/** Key constant @type Number */
PRINT_SCREEN: 44,
/** Key constant @type Number */
INSERT: 45,
/** Key constant @type Number */
DELETE: 46,
/** Key constant @type Number */
ZERO: 48,
/** Key constant @type Number */
ONE: 49,
/** Key constant @type Number */
TWO: 50,
/** Key constant @type Number */
THREE: 51,
/** Key constant @type Number */
FOUR: 52,
/** Key constant @type Number */
FIVE: 53,
/** Key constant @type Number */
SIX: 54,
/** Key constant @type Number */
SEVEN: 55,
/** Key constant @type Number */
EIGHT: 56,
/** Key constant @type Number */
NINE: 57,
/** Key constant @type Number */
A: 65,
/** Key constant @type Number */
B: 66,
/** Key constant @type Number */
C: 67,
/** Key constant @type Number */
D: 68,
/** Key constant @type Number */
E: 69,
/** Key constant @type Number */
F: 70,
/** Key constant @type Number */
G: 71,
/** Key constant @type Number */
H: 72,
/** Key constant @type Number */
I: 73,
/** Key constant @type Number */
J: 74,
/** Key constant @type Number */
K: 75,
/** Key constant @type Number */
L: 76,
/** Key constant @type Number */
M: 77,
/** Key constant @type Number */
N: 78,
/** Key constant @type Number */
O: 79,
/** Key constant @type Number */
P: 80,
/** Key constant @type Number */
Q: 81,
/** Key constant @type Number */
R: 82,
/** Key constant @type Number */
S: 83,
/** Key constant @type Number */
T: 84,
/** Key constant @type Number */
U: 85,
/** Key constant @type Number */
V: 86,
/** Key constant @type Number */
W: 87,
/** Key constant @type Number */
X: 88,
/** Key constant @type Number */
Y: 89,
/** Key constant @type Number */
Z: 90,
/** Key constant @type Number */
CONTEXT_MENU: 93,
/** Key constant @type Number */
NUM_ZERO: 96,
/** Key constant @type Number */
NUM_ONE: 97,
/** Key constant @type Number */
NUM_TWO: 98,
/** Key constant @type Number */
NUM_THREE: 99,
/** Key constant @type Number */
NUM_FOUR: 100,
/** Key constant @type Number */
NUM_FIVE: 101,
/** Key constant @type Number */
NUM_SIX: 102,
/** Key constant @type Number */
NUM_SEVEN: 103,
/** Key constant @type Number */
NUM_EIGHT: 104,
/** Key constant @type Number */
NUM_NINE: 105,
/** Key constant @type Number */
NUM_MULTIPLY: 106,
/** Key constant @type Number */
NUM_PLUS: 107,
/** Key constant @type Number */
NUM_MINUS: 109,
/** Key constant @type Number */
NUM_PERIOD: 110,
/** Key constant @type Number */
NUM_DIVISION: 111,
/** Key constant @type Number */
F1: 112,
/** Key constant @type Number */
F2: 113,
/** Key constant @type Number */
F3: 114,
/** Key constant @type Number */
F4: 115,
/** Key constant @type Number */
F5: 116,
/** Key constant @type Number */
F6: 117,
/** Key constant @type Number */
F7: 118,
/** Key constant @type Number */
F8: 119,
/** Key constant @type Number */
F9: 120,
/** Key constant @type Number */
F10: 121,
/** Key constant @type Number */
F11: 122,
/** Key constant @type Number */
F12: 123,
/**
* The mouse wheel delta scaling factor. This value depends on browser version and OS and
* attempts to produce a similar scrolling experience across all platforms and browsers.
*
* To change this value:
*
* Ext.EventObjectImpl.prototype.WHEEL_SCALE = 72;
*
* @type Number
* @markdown
*/
WHEEL_SCALE: (function () {
var scale;
if (Ext.isGecko) {
// Firefox uses 3 on all platforms
scale = 3;
} else if (Ext.isMac) {
// Continuous scrolling devices have momentum and produce much more scroll than
// discrete devices on the same OS and browser. To make things exciting, Safari
// (and not Chrome) changed from small values to 120 (like IE).
if (Ext.isSafari && Ext.webKitVersion >= 532.0) {
// Safari changed the scrolling factor to match IE (for details see
// https://bugs.webkit.org/show_bug.cgi?id=24368). The WebKit version where this
// change was introduced was 532.0
// Detailed discussion:
// https://bugs.webkit.org/show_bug.cgi?id=29601
// http://trac.webkit.org/browser/trunk/WebKit/chromium/src/mac/WebInputEventFactory.mm#L1063
scale = 120;
} else {
// MS optical wheel mouse produces multiples of 12 which is close enough
// to help tame the speed of the continuous mice...
scale = 12;
}
// Momentum scrolling produces very fast scrolling, so increase the scale factor
// to help produce similar results cross platform. This could be even larger and
// it would help those mice, but other mice would become almost unusable as a
// result (since we cannot tell which device type is in use).
scale *= 3;
} else {
// IE, Opera and other Windows browsers use 120.
scale = 120;
}
return scale;
})(),
/**
* Simple click regex
* @private
*/
clickRe: /(dbl)?click/,
// safari keypress events for special keys return bad keycodes
safariKeys: {
3: 13, // enter
63234: 37, // left
63235: 39, // right
63232: 38, // up
63233: 40, // down
63276: 33, // page up
63277: 34, // page down
63272: 46, // delete
63273: 36, // home
63275: 35 // end
},
// normalize button clicks, don't see any way to feature detect this.
btnMap: Ext.isIE ? {
1: 0,
4: 1,
2: 2
} : {
0: 0,
1: 1,
2: 2
},
constructor: function(event, freezeEvent){
if (event) {
this.setEvent(event.browserEvent || event, freezeEvent);
}
},
setEvent: function(event, freezeEvent){
var me = this, button, options;
if (event == me || (event && event.browserEvent)) { // already wrapped
return event;
}
me.browserEvent = event;
if (event) {
// normalize buttons
button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
if (me.clickRe.test(event.type) && button == -1) {
button = 0;
}
options = {
type: event.type,
button: button,
shiftKey: event.shiftKey,
// mac metaKey behaves like ctrlKey
ctrlKey: event.ctrlKey || event.metaKey || false,
altKey: event.altKey,
// in getKey these will be normalized for the mac
keyCode: event.keyCode,
charCode: event.charCode,
// cache the targets for the delayed and or buffered events
target: Ext.EventManager.getTarget(event),
relatedTarget: Ext.EventManager.getRelatedTarget(event),
currentTarget: event.currentTarget,
xy: (freezeEvent ? me.getXY() : null)
};
} else {
options = {
button: -1,
shiftKey: false,
ctrlKey: false,
altKey: false,
keyCode: 0,
charCode: 0,
target: null,
xy: [0, 0]
};
}
Ext.apply(me, options);
return me;
},
/**
* Stop the event (preventDefault and stopPropagation)
*/
stopEvent: function(){
this.stopPropagation();
this.preventDefault();
},
/**
* Prevents the browsers default handling of the event.
*/
preventDefault: function(){
if (this.browserEvent) {
Ext.EventManager.preventDefault(this.browserEvent);
}
},
/**
* Cancels bubbling of the event.
*/
stopPropagation: function(){
var browserEvent = this.browserEvent;
if (browserEvent) {
if (browserEvent.type == 'mousedown') {
Ext.EventManager.stoppedMouseDownEvent.fire(this);
}
Ext.EventManager.stopPropagation(browserEvent);
}
},
/**
* Gets the character code for the event.
* @return {Number}
*/
getCharCode: function(){
return this.charCode || this.keyCode;
},
/**
* Returns a normalized keyCode for the event.
* @return {Number} The key code
*/
getKey: function(){
return this.normalizeKey(this.keyCode || this.charCode);
},
/**
* Normalize key codes across browsers
* @private
* @param {Number} key The key code
* @return {Number} The normalized code
*/
normalizeKey: function(key){
// can't feature detect this
return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
},
/**
* Gets the x coordinate of the event.
* @return {Number}
* @deprecated 4.0 Replaced by {@link #getX}
*/
getPageX: function(){
return this.getX();
},
/**
* Gets the y coordinate of the event.
* @return {Number}
* @deprecated 4.0 Replaced by {@link #getY}
*/
getPageY: function(){
return this.getY();
},
/**
* Gets the x coordinate of the event.
* @return {Number}
*/
getX: function() {
return this.getXY()[0];
},
/**
* Gets the y coordinate of the event.
* @return {Number}
*/
getY: function() {
return this.getXY()[1];
},
/**
* Gets the page coordinates of the event.
* @return {Number[]} The xy values like [x, y]
*/
getXY: function() {
if (!this.xy) {
// same for XY
this.xy = Ext.EventManager.getPageXY(this.browserEvent);
}
return this.xy;
},
/**
* Gets the target for the event.
* @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
* @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
* @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
* @return {HTMLElement}
*/
getTarget : function(selector, maxDepth, returnEl){
if (selector) {
return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
}
return returnEl ? Ext.get(this.target) : this.target;
},
/**
* Gets the related target.
* @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
* @param {Number/HTMLElement} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
* @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
* @return {HTMLElement}
*/
getRelatedTarget : function(selector, maxDepth, returnEl){
if (selector) {
return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
}
return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
},
/**
* Correctly scales a given wheel delta.
* @param {Number} delta The delta value.
*/
correctWheelDelta : function (delta) {
var scale = this.WHEEL_SCALE,
ret = Math.round(delta / scale);
if (!ret && delta) {
ret = (delta < 0) ? -1 : 1; // don't allow non-zero deltas to go to zero!
}
return ret;
},
/**
* Returns the mouse wheel deltas for this event.
* @return {Object} An object with "x" and "y" properties holding the mouse wheel deltas.
*/
getWheelDeltas : function () {
var me = this,
event = me.browserEvent,
dx = 0, dy = 0; // the deltas
if (Ext.isDefined(event.wheelDeltaX)) { // WebKit has both dimensions
dx = event.wheelDeltaX;
dy = event.wheelDeltaY;
} else if (event.wheelDelta) { // old WebKit and IE
dy = event.wheelDelta;
} else if (event.detail) { // Gecko
dy = -event.detail; // gecko is backwards
// Gecko sometimes returns really big values if the user changes settings to
// scroll a whole page per scroll
if (dy > 100) {
dy = 3;
} else if (dy < -100) {
dy = -3;
}
// Firefox 3.1 adds an axis field to the event to indicate direction of
// scroll. See https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
if (Ext.isDefined(event.axis) && event.axis === event.HORIZONTAL_AXIS) {
dx = dy;
dy = 0;
}
}
return {
x: me.correctWheelDelta(dx),
y: me.correctWheelDelta(dy)
};
},
/**
* Normalizes mouse wheel y-delta across browsers. To get x-delta information, use
* {@link #getWheelDeltas} instead.
* @return {Number} The mouse wheel y-delta
*/
getWheelDelta : function(){
var deltas = this.getWheelDeltas();
return deltas.y;
},
/**
* Returns true if the target of this event is a child of el. Unless the allowEl parameter is set, it will return false if if the target is el.
* Example usage:<pre><code>
// Handle click on any child of an element
Ext.getBody().on('click', function(e){
if(e.within('some-el')){
alert('Clicked on a child of some-el!');
}
});
// Handle click directly on an element, ignoring clicks on child nodes
Ext.getBody().on('click', function(e,t){
if((t.id == 'some-el') && !e.within(t, true)){
alert('Clicked directly on some-el!');
}
});
</code></pre>
* @param {String/HTMLElement/Ext.Element} el The id, DOM element or Ext.Element to check
* @param {Boolean} related (optional) true to test if the related target is within el instead of the target
* @param {Boolean} allowEl (optional) true to also check if the passed element is the target or related target
* @return {Boolean}
*/
within : function(el, related, allowEl){
if(el){
var t = related ? this.getRelatedTarget() : this.getTarget(),
result;
if (t) {
result = Ext.fly(el).contains(t);
if (!result && allowEl) {
result = t == Ext.getDom(el);
}
return result;
}
}
return false;
},
/**
* Checks if the key pressed was a "navigation" key
* @return {Boolean} True if the press is a navigation keypress
*/
isNavKeyPress : function(){
var me = this,
k = this.normalizeKey(me.keyCode);
return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down
k == me.RETURN ||
k == me.TAB ||
k == me.ESC;
},
/**
* Checks if the key pressed was a "special" key
* @return {Boolean} True if the press is a special keypress
*/
isSpecialKey : function(){
var k = this.normalizeKey(this.keyCode);
return (this.type == 'keypress' && this.ctrlKey) ||
this.isNavKeyPress() ||
(k == this.BACKSPACE) || // Backspace
(k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
(k >= 44 && k <= 46); // Print Screen, Insert, Delete
},
/**
* Returns a point object that consists of the object coordinates.
* @return {Ext.util.Point} point
*/
getPoint : function(){
var xy = this.getXY();
return Ext.create('Ext.util.Point', xy[0], xy[1]);
},
/**
* Returns true if the control, meta, shift or alt key was pressed during this event.
* @return {Boolean}
*/
hasModifier : function(){
return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
},
/**
* Injects a DOM event using the data in this object and (optionally) a new target.
* This is a low-level technique and not likely to be used by application code. The
* currently supported event types are:
* <p><b>HTMLEvents</b></p>
* <ul>
* <li>load</li>
* <li>unload</li>
* <li>select</li>
* <li>change</li>
* <li>submit</li>
* <li>reset</li>
* <li>resize</li>
* <li>scroll</li>
* </ul>
* <p><b>MouseEvents</b></p>
* <ul>
* <li>click</li>
* <li>dblclick</li>
* <li>mousedown</li>
* <li>mouseup</li>
* <li>mouseover</li>
* <li>mousemove</li>
* <li>mouseout</li>
* </ul>
* <p><b>UIEvents</b></p>
* <ul>
* <li>focusin</li>
* <li>focusout</li>
* <li>activate</li>
* <li>focus</li>
* <li>blur</li>
* </ul>
* @param {Ext.Element/HTMLElement} target (optional) If specified, the target for the event. This
* is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
* is used to determine the target.
*/
injectEvent: function () {
var API,
dispatchers = {}; // keyed by event type (e.g., 'mousedown')
// Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
// IE9 has createEvent, but this code causes major problems with htmleditor (it
// blocks all mouse events and maybe more). TODO
if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
API = {
createHtmlEvent: function (doc, type, bubbles, cancelable) {
var event = doc.createEvent('HTMLEvents');
event.initEvent(type, bubbles, cancelable);
return event;
},
createMouseEvent: function (doc, type, bubbles, cancelable, detail,
clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
button, relatedTarget) {
var event = doc.createEvent('MouseEvents'),
view = doc.defaultView || window;
if (event.initMouseEvent) {
event.initMouseEvent(type, bubbles, cancelable, view, detail,
clientX, clientY, clientX, clientY, ctrlKey, altKey,
shiftKey, metaKey, button, relatedTarget);
} else { // old Safari
event = doc.createEvent('UIEvents');
event.initEvent(type, bubbles, cancelable);
event.view = view;
event.detail = detail;
event.screenX = clientX;
event.screenY = clientY;
event.clientX = clientX;
event.clientY = clientY;
event.ctrlKey = ctrlKey;
event.altKey = altKey;
event.metaKey = metaKey;
event.shiftKey = shiftKey;
event.button = button;
event.relatedTarget = relatedTarget;
}
return event;
},
createUIEvent: function (doc, type, bubbles, cancelable, detail) {
var event = doc.createEvent('UIEvents'),
view = doc.defaultView || window;
event.initUIEvent(type, bubbles, cancelable, view, detail);
return event;
},
fireEvent: function (target, type, event) {
target.dispatchEvent(event);
},
fixTarget: function (target) {
// Safari3 doesn't have window.dispatchEvent()
if (target == window && !target.dispatchEvent) {
return document;
}
return target;
}
};
} else if (document.createEventObject) { // else if (IE)
var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
API = {
createHtmlEvent: function (doc, type, bubbles, cancelable) {
var event = doc.createEventObject();
event.bubbles = bubbles;
event.cancelable = cancelable;
return event;
},
createMouseEvent: function (doc, type, bubbles, cancelable, detail,
clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
button, relatedTarget) {
var event = doc.createEventObject();
event.bubbles = bubbles;
event.cancelable = cancelable;
event.detail = detail;
event.screenX = clientX;
event.screenY = clientY;
event.clientX = clientX;
event.clientY = clientY;
event.ctrlKey = ctrlKey;
event.altKey = altKey;
event.shiftKey = shiftKey;
event.metaKey = metaKey;
event.button = crazyIEButtons[button] || button;
event.relatedTarget = relatedTarget; // cannot assign to/fromElement
return event;
},
createUIEvent: function (doc, type, bubbles, cancelable, detail) {
var event = doc.createEventObject();
event.bubbles = bubbles;
event.cancelable = cancelable;
return event;
},
fireEvent: function (target, type, event) {
target.fireEvent('on' + type, event);
},
fixTarget: function (target) {
if (target == document) {
// IE6,IE7 thinks window==document and doesn't have window.fireEvent()
// IE6,IE7 cannot properly call document.fireEvent()
return document.documentElement;
}
return target;
}
};
}
//----------------
// HTMLEvents
Ext.Object.each({
load: [false, false],
unload: [false, false],
select: [true, false],
change: [true, false],
submit: [true, true],
reset: [true, false],
resize: [true, false],
scroll: [true, false]
},
function (name, value) {
var bubbles = value[0], cancelable = value[1];
dispatchers[name] = function (targetEl, srcEvent) {
var e = API.createHtmlEvent(name, bubbles, cancelable);
API.fireEvent(targetEl, name, e);
};
});
//----------------
// MouseEvents
function createMouseEventDispatcher (type, detail) {
var cancelable = (type != 'mousemove');
return function (targetEl, srcEvent) {
var xy = srcEvent.getXY(),
e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
srcEvent.relatedTarget);
API.fireEvent(targetEl, type, e);
};
}
Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
function (eventName) {
dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
});
//----------------
// UIEvents
Ext.Object.each({
focusin: [true, false],
focusout: [true, false],
activate: [true, true],
focus: [false, false],
blur: [false, false]
},
function (name, value) {
var bubbles = value[0], cancelable = value[1];
dispatchers[name] = function (targetEl, srcEvent) {
var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
API.fireEvent(targetEl, name, e);
};
});
//---------
if (!API) {
// not even sure what ancient browsers fall into this category...
dispatchers = {}; // never mind all those we just built :P
API = {
fixTarget: function (t) {
return t;
}
};
}
function cannotInject (target, srcEvent) {
//<debug>
// TODO log something
//</debug>
}
return function (target) {
var me = this,
dispatcher = dispatchers[me.type] || cannotInject,
t = target ? (target.dom || target) : me.getTarget();
t = API.fixTarget(t);
dispatcher(t, me);
};
}() // call to produce method
}, function() {
Ext.EventObject = new Ext.EventObjectImpl();
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,599 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext
* @singleton
*/
(function() {
var global = this,
objectPrototype = Object.prototype,
toString = objectPrototype.toString,
enumerables = true,
enumerablesTest = { toString: 1 },
i;
if (typeof Ext === 'undefined') {
global.Ext = {};
}
Ext.global = global;
for (i in enumerablesTest) {
enumerables = null;
}
if (enumerables) {
enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
'toLocaleString', 'toString', 'constructor'];
}
/**
* An array containing extra enumerables for old browsers
* @property {String[]}
*/
Ext.enumerables = enumerables;
/**
* Copies all the properties of config to the specified object.
* Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
* {@link Ext.Object#merge} instead.
* @param {Object} object The receiver of the properties
* @param {Object} config The source of the properties
* @param {Object} defaults A different object that will also be applied for default values
* @return {Object} returns obj
*/
Ext.apply = function(object, config, defaults) {
if (defaults) {
Ext.apply(object, defaults);
}
if (object && config && typeof config === 'object') {
var i, j, k;
for (i in config) {
object[i] = config[i];
}
if (enumerables) {
for (j = enumerables.length; j--;) {
k = enumerables[j];
if (config.hasOwnProperty(k)) {
object[k] = config[k];
}
}
}
}
return object;
};
Ext.buildSettings = Ext.apply({
baseCSSPrefix: 'x-',
scopeResetCSS: false
}, Ext.buildSettings || {});
Ext.apply(Ext, {
/**
* A reusable empty function
*/
emptyFn: function() {},
baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
/**
* Copies all the properties of config to object if they don't already exist.
* @param {Object} object The receiver of the properties
* @param {Object} config The source of the properties
* @return {Object} returns obj
*/
applyIf: function(object, config) {
var property;
if (object) {
for (property in config) {
if (object[property] === undefined) {
object[property] = config[property];
}
}
}
return object;
},
/**
* Iterates either an array or an object. This method delegates to
* {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
*
* @param {Object/Array} object The object or array to be iterated.
* @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
* {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
* type that is being iterated.
* @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
* Defaults to the object being iterated itself.
* @markdown
*/
iterate: function(object, fn, scope) {
if (Ext.isEmpty(object)) {
return;
}
if (scope === undefined) {
scope = object;
}
if (Ext.isIterable(object)) {
Ext.Array.each.call(Ext.Array, object, fn, scope);
}
else {
Ext.Object.each.call(Ext.Object, object, fn, scope);
}
}
});
Ext.apply(Ext, {
/**
* This method deprecated. Use {@link Ext#define Ext.define} instead.
* @method
* @param {Function} superclass
* @param {Object} overrides
* @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
* @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
*/
extend: function() {
// inline overrides
var objectConstructor = objectPrototype.constructor,
inlineOverrides = function(o) {
for (var m in o) {
if (!o.hasOwnProperty(m)) {
continue;
}
this[m] = o[m];
}
};
return function(subclass, superclass, overrides) {
// First we check if the user passed in just the superClass with overrides
if (Ext.isObject(superclass)) {
overrides = superclass;
superclass = subclass;
subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
superclass.apply(this, arguments);
};
}
//<debug>
if (!superclass) {
Ext.Error.raise({
sourceClass: 'Ext',
sourceMethod: 'extend',
msg: 'Attempting to extend from a class which has not been loaded on the page.'
});
}
//</debug>
// We create a new temporary class
var F = function() {},
subclassProto, superclassProto = superclass.prototype;
F.prototype = superclassProto;
subclassProto = subclass.prototype = new F();
subclassProto.constructor = subclass;
subclass.superclass = superclassProto;
if (superclassProto.constructor === objectConstructor) {
superclassProto.constructor = superclass;
}
subclass.override = function(overrides) {
Ext.override(subclass, overrides);
};
subclassProto.override = inlineOverrides;
subclassProto.proto = subclassProto;
subclass.override(overrides);
subclass.extend = function(o) {
return Ext.extend(subclass, o);
};
return subclass;
};
}(),
/**
* Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
Ext.define('My.cool.Class', {
sayHi: function() {
alert('Hi!');
}
}
Ext.override(My.cool.Class, {
sayHi: function() {
alert('About to say...');
this.callOverridden();
}
});
var cool = new My.cool.Class();
cool.sayHi(); // alerts 'About to say...'
// alerts 'Hi!'
* Please note that `this.callOverridden()` only works if the class was previously
* created with {@link Ext#define)
*
* @param {Object} cls The class to override
* @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
* containing one or more methods.
* @method override
* @markdown
*/
override: function(cls, overrides) {
if (cls.prototype.$className) {
return cls.override(overrides);
}
else {
Ext.apply(cls.prototype, overrides);
}
}
});
// A full set of static methods to do type checking
Ext.apply(Ext, {
/**
* Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
* value (second argument) otherwise.
*
* @param {Object} value The value to test
* @param {Object} defaultValue The value to return if the original value is empty
* @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
* @return {Object} value, if non-empty, else defaultValue
*/
valueFrom: function(value, defaultValue, allowBlank){
return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
},
/**
* Returns the type of the given variable in string format. List of possible values are:
*
* - `undefined`: If the given value is `undefined`
* - `null`: If the given value is `null`
* - `string`: If the given value is a string
* - `number`: If the given value is a number
* - `boolean`: If the given value is a boolean value
* - `date`: If the given value is a `Date` object
* - `function`: If the given value is a function reference
* - `object`: If the given value is an object
* - `array`: If the given value is an array
* - `regexp`: If the given value is a regular expression
* - `element`: If the given value is a DOM Element
* - `textnode`: If the given value is a DOM text node and contains something other than whitespace
* - `whitespace`: If the given value is a DOM text node and contains only whitespace
*
* @param {Object} value
* @return {String}
* @markdown
*/
typeOf: function(value) {
if (value === null) {
return 'null';
}
var type = typeof value;
if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
return type;
}
var typeToString = toString.call(value);
switch(typeToString) {
case '[object Array]':
return 'array';
case '[object Date]':
return 'date';
case '[object Boolean]':
return 'boolean';
case '[object Number]':
return 'number';
case '[object RegExp]':
return 'regexp';
}
if (type === 'function') {
return 'function';
}
if (type === 'object') {
if (value.nodeType !== undefined) {
if (value.nodeType === 3) {
return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
}
else {
return 'element';
}
}
return 'object';
}
//<debug error>
Ext.Error.raise({
sourceClass: 'Ext',
sourceMethod: 'typeOf',
msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
});
//</debug>
},
/**
* Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
*
* - `null`
* - `undefined`
* - a zero-length array
* - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
*
* @param {Object} value The value to test
* @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
* @return {Boolean}
* @markdown
*/
isEmpty: function(value, allowEmptyString) {
return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
},
/**
* Returns true if the passed value is a JavaScript Array, false otherwise.
*
* @param {Object} target The target to test
* @return {Boolean}
* @method
*/
isArray: ('isArray' in Array) ? Array.isArray : function(value) {
return toString.call(value) === '[object Array]';
},
/**
* Returns true if the passed value is a JavaScript Date object, false otherwise.
* @param {Object} object The object to test
* @return {Boolean}
*/
isDate: function(value) {
return toString.call(value) === '[object Date]';
},
/**
* Returns true if the passed value is a JavaScript Object, false otherwise.
* @param {Object} value The value to test
* @return {Boolean}
* @method
*/
isObject: (toString.call(null) === '[object Object]') ?
function(value) {
// check ownerDocument here as well to exclude DOM nodes
return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
} :
function(value) {
return toString.call(value) === '[object Object]';
},
/**
* Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
* @param {Object} value The value to test
* @return {Boolean}
*/
isPrimitive: function(value) {
var type = typeof value;
return type === 'string' || type === 'number' || type === 'boolean';
},
/**
* Returns true if the passed value is a JavaScript Function, false otherwise.
* @param {Object} value The value to test
* @return {Boolean}
* @method
*/
isFunction:
// Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
// Object.prorotype.toString (slower)
(typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
return toString.call(value) === '[object Function]';
} : function(value) {
return typeof value === 'function';
},
/**
* Returns true if the passed value is a number. Returns false for non-finite numbers.
* @param {Object} value The value to test
* @return {Boolean}
*/
isNumber: function(value) {
return typeof value === 'number' && isFinite(value);
},
/**
* Validates that a value is numeric.
* @param {Object} value Examples: 1, '1', '2.34'
* @return {Boolean} True if numeric, false otherwise
*/
isNumeric: function(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
},
/**
* Returns true if the passed value is a string.
* @param {Object} value The value to test
* @return {Boolean}
*/
isString: function(value) {
return typeof value === 'string';
},
/**
* Returns true if the passed value is a boolean.
*
* @param {Object} value The value to test
* @return {Boolean}
*/
isBoolean: function(value) {
return typeof value === 'boolean';
},
/**
* Returns true if the passed value is an HTMLElement
* @param {Object} value The value to test
* @return {Boolean}
*/
isElement: function(value) {
return value ? value.nodeType === 1 : false;
},
/**
* Returns true if the passed value is a TextNode
* @param {Object} value The value to test
* @return {Boolean}
*/
isTextNode: function(value) {
return value ? value.nodeName === "#text" : false;
},
/**
* Returns true if the passed value is defined.
* @param {Object} value The value to test
* @return {Boolean}
*/
isDefined: function(value) {
return typeof value !== 'undefined';
},
/**
* Returns true if the passed value is iterable, false otherwise
* @param {Object} value The value to test
* @return {Boolean}
*/
isIterable: function(value) {
return (value && typeof value !== 'string') ? value.length !== undefined : false;
}
});
Ext.apply(Ext, {
/**
* Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
* @param {Object} item The variable to clone
* @return {Object} clone
*/
clone: function(item) {
if (item === null || item === undefined) {
return item;
}
// DOM nodes
// TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
// recursively
if (item.nodeType && item.cloneNode) {
return item.cloneNode(true);
}
var type = toString.call(item);
// Date
if (type === '[object Date]') {
return new Date(item.getTime());
}
var i, j, k, clone, key;
// Array
if (type === '[object Array]') {
i = item.length;
clone = [];
while (i--) {
clone[i] = Ext.clone(item[i]);
}
}
// Object
else if (type === '[object Object]' && item.constructor === Object) {
clone = {};
for (key in item) {
clone[key] = Ext.clone(item[key]);
}
if (enumerables) {
for (j = enumerables.length; j--;) {
k = enumerables[j];
clone[k] = item[k];
}
}
}
return clone || item;
},
/**
* @private
* Generate a unique reference of Ext in the global scope, useful for sandboxing
*/
getUniqueGlobalNamespace: function() {
var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
if (uniqueGlobalNamespace === undefined) {
var i = 0;
do {
uniqueGlobalNamespace = 'ExtBox' + (++i);
} while (Ext.global[uniqueGlobalNamespace] !== undefined);
Ext.global[uniqueGlobalNamespace] = Ext;
this.uniqueGlobalNamespace = uniqueGlobalNamespace;
}
return uniqueGlobalNamespace;
},
/**
* @private
*/
functionFactory: function() {
var args = Array.prototype.slice.call(arguments);
if (args.length > 0) {
args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
args[args.length - 1];
}
return Function.prototype.constructor.apply(Function.prototype, args);
}
});
/**
* Old alias to {@link Ext#typeOf}
* @deprecated 4.0.0 Use {@link Ext#typeOf} instead
* @method
* @alias Ext#typeOf
*/
Ext.type = Ext.typeOf;
})();

View File

@@ -0,0 +1,597 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.is
*
* Determines information about the current platform the application is running on.
*
* @singleton
*/
Ext.is = {
init : function(navigator) {
var platforms = this.platforms,
ln = platforms.length,
i, platform;
navigator = navigator || window.navigator;
for (i = 0; i < ln; i++) {
platform = platforms[i];
this[platform.identity] = platform.regex.test(navigator[platform.property]);
}
/**
* @property Desktop True if the browser is running on a desktop machine
* @type {Boolean}
*/
this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
/**
* @property Tablet True if the browser is running on a tablet (iPad)
*/
this.Tablet = this.iPad;
/**
* @property Phone True if the browser is running on a phone.
* @type {Boolean}
*/
this.Phone = !this.Desktop && !this.Tablet;
/**
* @property iOS True if the browser is running on iOS
* @type {Boolean}
*/
this.iOS = this.iPhone || this.iPad || this.iPod;
/**
* @property Standalone Detects when application has been saved to homescreen.
* @type {Boolean}
*/
this.Standalone = !!window.navigator.standalone;
},
/**
* @property iPhone True when the browser is running on a iPhone
* @type {Boolean}
*/
platforms: [{
property: 'platform',
regex: /iPhone/i,
identity: 'iPhone'
},
/**
* @property iPod True when the browser is running on a iPod
* @type {Boolean}
*/
{
property: 'platform',
regex: /iPod/i,
identity: 'iPod'
},
/**
* @property iPad True when the browser is running on a iPad
* @type {Boolean}
*/
{
property: 'userAgent',
regex: /iPad/i,
identity: 'iPad'
},
/**
* @property Blackberry True when the browser is running on a Blackberry
* @type {Boolean}
*/
{
property: 'userAgent',
regex: /Blackberry/i,
identity: 'Blackberry'
},
/**
* @property Android True when the browser is running on an Android device
* @type {Boolean}
*/
{
property: 'userAgent',
regex: /Android/i,
identity: 'Android'
},
/**
* @property Mac True when the browser is running on a Mac
* @type {Boolean}
*/
{
property: 'platform',
regex: /Mac/i,
identity: 'Mac'
},
/**
* @property Windows True when the browser is running on Windows
* @type {Boolean}
*/
{
property: 'platform',
regex: /Win/i,
identity: 'Windows'
},
/**
* @property Linux True when the browser is running on Linux
* @type {Boolean}
*/
{
property: 'platform',
regex: /Linux/i,
identity: 'Linux'
}]
};
Ext.is.init();
/**
* @class Ext.supports
*
* Determines information about features are supported in the current environment
*
* @singleton
*/
Ext.supports = {
init : function() {
var doc = document,
div = doc.createElement('div'),
tests = this.tests,
ln = tests.length,
i, test;
div.innerHTML = [
'<div style="height:30px;width:50px;">',
'<div style="height:20px;width:20px;"></div>',
'</div>',
'<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
'<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
'</div>',
'<div style="float:left; background-color:transparent;"></div>'
].join('');
doc.body.appendChild(div);
for (i = 0; i < ln; i++) {
test = tests[i];
this[test.identity] = test.fn.call(this, doc, div);
}
doc.body.removeChild(div);
},
/**
* @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
* @type {Boolean}
*/
CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
/**
* @property ClassList True if document environment supports the HTML5 classList API.
* @type {Boolean}
*/
ClassList: !!document.documentElement.classList,
/**
* @property OrientationChange True if the device supports orientation change
* @type {Boolean}
*/
OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
/**
* @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
* @type {Boolean}
*/
DeviceMotion: ('ondevicemotion' in window),
/**
* @property Touch True if the device supports touch
* @type {Boolean}
*/
// is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
// and Safari 4.0 (they all have 'ontouchstart' in the window object).
Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
tests: [
/**
* @property Transitions True if the device supports CSS3 Transitions
* @type {Boolean}
*/
{
identity: 'Transitions',
fn: function(doc, div) {
var prefix = [
'webkit',
'Moz',
'o',
'ms',
'khtml'
],
TE = 'TransitionEnd',
transitionEndName = [
prefix[0] + TE,
'transitionend', //Moz bucks the prefixing convention
prefix[2] + TE,
prefix[3] + TE,
prefix[4] + TE
],
ln = prefix.length,
i = 0,
out = false;
div = Ext.get(div);
for (; i < ln; i++) {
if (div.getStyle(prefix[i] + "TransitionProperty")) {
Ext.supports.CSS3Prefix = prefix[i];
Ext.supports.CSS3TransitionEnd = transitionEndName[i];
out = true;
break;
}
}
return out;
}
},
/**
* @property RightMargin True if the device supports right margin.
* See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
* @type {Boolean}
*/
{
identity: 'RightMargin',
fn: function(doc, div) {
var view = doc.defaultView;
return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
}
},
/**
* @property DisplayChangeInputSelectionBug True if INPUT elements lose their
* selection when their display style is changed. Essentially, if a text input
* has focus and its display style is changed, the I-beam disappears.
*
* This bug is encountered due to the work around in place for the {@link #RightMargin}
* bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
* in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
* version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
*/
{
identity: 'DisplayChangeInputSelectionBug',
fn: function() {
var webKitVersion = Ext.webKitVersion;
// WebKit but older than Safari 5 or Chrome 6:
return 0 < webKitVersion && webKitVersion < 533;
}
},
/**
* @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
* selection when their display style is changed. Essentially, if a text area has
* focus and its display style is changed, the I-beam disappears.
*
* This bug is encountered due to the work around in place for the {@link #RightMargin}
* bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
* be fixed in Chrome 11.
*/
{
identity: 'DisplayChangeTextAreaSelectionBug',
fn: function() {
var webKitVersion = Ext.webKitVersion;
/*
Has bug w/textarea:
(Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
Safari/534.16
(Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
Safari/533.21.1
No bug:
(Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
Safari/534.24
*/
return 0 < webKitVersion && webKitVersion < 534.24;
}
},
/**
* @property TransparentColor True if the device supports transparent color
* @type {Boolean}
*/
{
identity: 'TransparentColor',
fn: function(doc, div, view) {
view = doc.defaultView;
return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
}
},
/**
* @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
* @type {Boolean}
*/
{
identity: 'ComputedStyle',
fn: function(doc, div, view) {
view = doc.defaultView;
return view && view.getComputedStyle;
}
},
/**
* @property SVG True if the device supports SVG
* @type {Boolean}
*/
{
identity: 'Svg',
fn: function(doc) {
return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
}
},
/**
* @property Canvas True if the device supports Canvas
* @type {Boolean}
*/
{
identity: 'Canvas',
fn: function(doc) {
return !!doc.createElement('canvas').getContext;
}
},
/**
* @property VML True if the device supports VML
* @type {Boolean}
*/
{
identity: 'Vml',
fn: function(doc) {
var d = doc.createElement("div");
d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
return (d.childNodes.length == 2);
}
},
/**
* @property Float True if the device supports CSS float
* @type {Boolean}
*/
{
identity: 'Float',
fn: function(doc, div) {
return !!div.lastChild.style.cssFloat;
}
},
/**
* @property AudioTag True if the device supports the HTML5 audio tag
* @type {Boolean}
*/
{
identity: 'AudioTag',
fn: function(doc) {
return !!doc.createElement('audio').canPlayType;
}
},
/**
* @property History True if the device supports HTML5 history
* @type {Boolean}
*/
{
identity: 'History',
fn: function() {
return !!(window.history && history.pushState);
}
},
/**
* @property CSS3DTransform True if the device supports CSS3DTransform
* @type {Boolean}
*/
{
identity: 'CSS3DTransform',
fn: function() {
return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
}
},
/**
* @property CSS3LinearGradient True if the device supports CSS3 linear gradients
* @type {Boolean}
*/
{
identity: 'CSS3LinearGradient',
fn: function(doc, div) {
var property = 'background-image:',
webkit = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
w3c = 'linear-gradient(left top, black, white)',
moz = '-moz-' + w3c,
options = [property + webkit, property + w3c, property + moz];
div.style.cssText = options.join(';');
return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
}
},
/**
* @property CSS3BorderRadius True if the device supports CSS3 border radius
* @type {Boolean}
*/
{
identity: 'CSS3BorderRadius',
fn: function(doc, div) {
var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
pass = false,
i;
for (i = 0; i < domPrefixes.length; i++) {
if (document.body.style[domPrefixes[i]] !== undefined) {
return true;
}
}
return pass;
}
},
/**
* @property GeoLocation True if the device supports GeoLocation
* @type {Boolean}
*/
{
identity: 'GeoLocation',
fn: function() {
return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
}
},
/**
* @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
* @type {Boolean}
*/
{
identity: 'MouseEnterLeave',
fn: function(doc, div){
return ('onmouseenter' in div && 'onmouseleave' in div);
}
},
/**
* @property MouseWheel True if the browser supports the mousewheel event
* @type {Boolean}
*/
{
identity: 'MouseWheel',
fn: function(doc, div) {
return ('onmousewheel' in div);
}
},
/**
* @property Opacity True if the browser supports normal css opacity
* @type {Boolean}
*/
{
identity: 'Opacity',
fn: function(doc, div){
// Not a strict equal comparison in case opacity can be converted to a number.
if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
return false;
}
div.firstChild.style.cssText = 'opacity:0.73';
return div.firstChild.style.opacity == '0.73';
}
},
/**
* @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
* @type {Boolean}
*/
{
identity: 'Placeholder',
fn: function(doc) {
return 'placeholder' in doc.createElement('input');
}
},
/**
* @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight,
* getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
* @type {Boolean}
*/
{
identity: 'Direct2DBug',
fn: function() {
return Ext.isString(document.body.style.msTransformOrigin);
}
},
/**
* @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
* @type {Boolean}
*/
{
identity: 'BoundingClientRect',
fn: function(doc, div) {
return Ext.isFunction(div.getBoundingClientRect);
}
},
{
identity: 'IncludePaddingInWidthCalculation',
fn: function(doc, div){
var el = Ext.get(div.childNodes[1].firstChild);
return el.getWidth() == 210;
}
},
{
identity: 'IncludePaddingInHeightCalculation',
fn: function(doc, div){
var el = Ext.get(div.childNodes[1].firstChild);
return el.getHeight() == 210;
}
},
/**
* @property ArraySort True if the Array sort native method isn't bugged.
* @type {Boolean}
*/
{
identity: 'ArraySort',
fn: function() {
var a = [1,2,3,4,5].sort(function(){ return 0; });
return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
}
},
/**
* @property Range True if browser support document.createRange native method.
* @type {Boolean}
*/
{
identity: 'Range',
fn: function() {
return !!document.createRange;
}
},
/**
* @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
* @type {Boolean}
*/
{
identity: 'CreateContextualFragment',
fn: function() {
var range = Ext.supports.Range ? document.createRange() : false;
return range && !!range.createContextualFragment;
}
},
/**
* @property WindowOnError True if browser supports window.onerror.
* @type {Boolean}
*/
{
identity: 'WindowOnError',
fn: function () {
// sadly, we cannot feature detect this...
return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
}
}
]
};

View File

@@ -0,0 +1,783 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @author Jacky Nguyen <jacky@sencha.com>
* @docauthor Jacky Nguyen <jacky@sencha.com>
* @class Ext.Base
*
* The root of all classes created with {@link Ext#define}.
*
* Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
* All prototype and static members of this class are inherited by all other classes.
*/
(function(flexSetter) {
var Base = Ext.Base = function() {};
Base.prototype = {
$className: 'Ext.Base',
$class: Base,
/**
* Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
* `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
* for a detailed comparison
*
* Ext.define('My.Cat', {
* statics: {
* speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
* },
*
* constructor: function() {
* alert(this.self.speciesName); / dependent on 'this'
*
* return this;
* },
*
* clone: function() {
* return new this.self();
* }
* });
*
*
* Ext.define('My.SnowLeopard', {
* extend: 'My.Cat',
* statics: {
* speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
* }
* });
*
* var cat = new My.Cat(); // alerts 'Cat'
* var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
*
* var clone = snowLeopard.clone();
* alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
*
* @type Ext.Class
* @protected
*/
self: Base,
// Default constructor, simply returns `this`
constructor: function() {
return this;
},
//<feature classSystem.config>
/**
* Initialize configuration for this class. a typical example:
*
* Ext.define('My.awesome.Class', {
* // The default config
* config: {
* name: 'Awesome',
* isAwesome: true
* },
*
* constructor: function(config) {
* this.initConfig(config);
*
* return this;
* }
* });
*
* var awesome = new My.awesome.Class({
* name: 'Super Awesome'
* });
*
* alert(awesome.getName()); // 'Super Awesome'
*
* @protected
* @param {Object} config
* @return {Object} mixins The mixin prototypes as key - value pairs
*/
initConfig: function(config) {
if (!this.$configInited) {
this.config = Ext.Object.merge({}, this.config || {}, config || {});
this.applyConfig(this.config);
this.$configInited = true;
}
return this;
},
/**
* @private
*/
setConfig: function(config) {
this.applyConfig(config || {});
return this;
},
/**
* @private
*/
applyConfig: flexSetter(function(name, value) {
var setter = 'set' + Ext.String.capitalize(name);
if (typeof this[setter] === 'function') {
this[setter].call(this, value);
}
return this;
}),
//</feature>
/**
* Call the parent's overridden method. For example:
*
* Ext.define('My.own.A', {
* constructor: function(test) {
* alert(test);
* }
* });
*
* Ext.define('My.own.B', {
* extend: 'My.own.A',
*
* constructor: function(test) {
* alert(test);
*
* this.callParent([test + 1]);
* }
* });
*
* Ext.define('My.own.C', {
* extend: 'My.own.B',
*
* constructor: function() {
* alert("Going to call parent's overriden constructor...");
*
* this.callParent(arguments);
* }
* });
*
* var a = new My.own.A(1); // alerts '1'
* var b = new My.own.B(1); // alerts '1', then alerts '2'
* var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
* // alerts '2', then alerts '3'
*
* @protected
* @param {Array/Arguments} args The arguments, either an array or the `arguments` object
* from the current method, for example: `this.callParent(arguments)`
* @return {Object} Returns the result from the superclass' method
*/
callParent: function(args) {
var method = this.callParent.caller,
parentClass, methodName;
if (!method.$owner) {
//<debug error>
if (!method.caller) {
Ext.Error.raise({
sourceClass: Ext.getClassName(this),
sourceMethod: "callParent",
msg: "Attempting to call a protected method from the public scope, which is not allowed"
});
}
//</debug>
method = method.caller;
}
parentClass = method.$owner.superclass;
methodName = method.$name;
//<debug error>
if (!(methodName in parentClass)) {
Ext.Error.raise({
sourceClass: Ext.getClassName(this),
sourceMethod: methodName,
msg: "this.callParent() was called but there's no such method (" + methodName +
") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
});
}
//</debug>
return parentClass[methodName].apply(this, args || []);
},
/**
* Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
* `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
* `this` points to during run-time
*
* Ext.define('My.Cat', {
* statics: {
* totalCreated: 0,
* speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
* },
*
* constructor: function() {
* var statics = this.statics();
*
* alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
* // equivalent to: My.Cat.speciesName
*
* alert(this.self.speciesName); // dependent on 'this'
*
* statics.totalCreated++;
*
* return this;
* },
*
* clone: function() {
* var cloned = new this.self; // dependent on 'this'
*
* cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
*
* return cloned;
* }
* });
*
*
* Ext.define('My.SnowLeopard', {
* extend: 'My.Cat',
*
* statics: {
* speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
* },
*
* constructor: function() {
* this.callParent();
* }
* });
*
* var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
*
* var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
*
* var clone = snowLeopard.clone();
* alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
* alert(clone.groupName); // alerts 'Cat'
*
* alert(My.Cat.totalCreated); // alerts 3
*
* @protected
* @return {Ext.Class}
*/
statics: function() {
var method = this.statics.caller,
self = this.self;
if (!method) {
return self;
}
return method.$owner;
},
/**
* Call the original method that was previously overridden with {@link Ext.Base#override}
*
* Ext.define('My.Cat', {
* constructor: function() {
* alert("I'm a cat!");
*
* return this;
* }
* });
*
* My.Cat.override({
* constructor: function() {
* alert("I'm going to be a cat!");
*
* var instance = this.callOverridden();
*
* alert("Meeeeoooowwww");
*
* return instance;
* }
* });
*
* var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
* // alerts "I'm a cat!"
* // alerts "Meeeeoooowwww"
*
* @param {Array/Arguments} args The arguments, either an array or the `arguments` object
* @return {Object} Returns the result after calling the overridden method
* @protected
*/
callOverridden: function(args) {
var method = this.callOverridden.caller;
//<debug error>
if (!method.$owner) {
Ext.Error.raise({
sourceClass: Ext.getClassName(this),
sourceMethod: "callOverridden",
msg: "Attempting to call a protected method from the public scope, which is not allowed"
});
}
if (!method.$previous) {
Ext.Error.raise({
sourceClass: Ext.getClassName(this),
sourceMethod: "callOverridden",
msg: "this.callOverridden was called in '" + method.$name +
"' but this method has never been overridden"
});
}
//</debug>
return method.$previous.apply(this, args || []);
},
destroy: function() {}
};
// These static properties will be copied to every newly created class with {@link Ext#define}
Ext.apply(Ext.Base, {
/**
* Create a new instance of this Class.
*
* Ext.define('My.cool.Class', {
* ...
* });
*
* My.cool.Class.create({
* someConfig: true
* });
*
* All parameters are passed to the constructor of the class.
*
* @return {Object} the created instance.
* @static
* @inheritable
*/
create: function() {
return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
},
/**
* @private
* @inheritable
*/
own: function(name, value) {
if (typeof value == 'function') {
this.ownMethod(name, value);
}
else {
this.prototype[name] = value;
}
},
/**
* @private
* @inheritable
*/
ownMethod: function(name, fn) {
var originalFn;
if (typeof fn.$owner !== 'undefined' && fn !== Ext.emptyFn) {
originalFn = fn;
fn = function() {
return originalFn.apply(this, arguments);
};
}
//<debug>
var className;
className = Ext.getClassName(this);
if (className) {
fn.displayName = className + '#' + name;
}
//</debug>
fn.$owner = this;
fn.$name = name;
this.prototype[name] = fn;
},
/**
* Add / override static properties of this class.
*
* Ext.define('My.cool.Class', {
* ...
* });
*
* My.cool.Class.addStatics({
* someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
* method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
* method2: function() { ... } // My.cool.Class.method2 = function() { ... };
* });
*
* @param {Object} members
* @return {Ext.Base} this
* @static
* @inheritable
*/
addStatics: function(members) {
for (var name in members) {
if (members.hasOwnProperty(name)) {
this[name] = members[name];
}
}
return this;
},
/**
* @private
* @param {Object} members
*/
addInheritableStatics: function(members) {
var inheritableStatics,
hasInheritableStatics,
prototype = this.prototype,
name, member;
inheritableStatics = prototype.$inheritableStatics;
hasInheritableStatics = prototype.$hasInheritableStatics;
if (!inheritableStatics) {
inheritableStatics = prototype.$inheritableStatics = [];
hasInheritableStatics = prototype.$hasInheritableStatics = {};
}
//<debug>
var className = Ext.getClassName(this);
//</debug>
for (name in members) {
if (members.hasOwnProperty(name)) {
member = members[name];
//<debug>
if (typeof member == 'function') {
member.displayName = className + '.' + name;
}
//</debug>
this[name] = member;
if (!hasInheritableStatics[name]) {
hasInheritableStatics[name] = true;
inheritableStatics.push(name);
}
}
}
return this;
},
/**
* Add methods / properties to the prototype of this class.
*
* Ext.define('My.awesome.Cat', {
* constructor: function() {
* ...
* }
* });
*
* My.awesome.Cat.implement({
* meow: function() {
* alert('Meowww...');
* }
* });
*
* var kitty = new My.awesome.Cat;
* kitty.meow();
*
* @param {Object} members
* @static
* @inheritable
*/
implement: function(members) {
var prototype = this.prototype,
enumerables = Ext.enumerables,
name, i, member;
//<debug>
var className = Ext.getClassName(this);
//</debug>
for (name in members) {
if (members.hasOwnProperty(name)) {
member = members[name];
if (typeof member === 'function') {
member.$owner = this;
member.$name = name;
//<debug>
if (className) {
member.displayName = className + '#' + name;
}
//</debug>
}
prototype[name] = member;
}
}
if (enumerables) {
for (i = enumerables.length; i--;) {
name = enumerables[i];
if (members.hasOwnProperty(name)) {
member = members[name];
member.$owner = this;
member.$name = name;
prototype[name] = member;
}
}
}
},
/**
* Borrow another class' members to the prototype of this class.
*
* Ext.define('Bank', {
* money: '$$$',
* printMoney: function() {
* alert('$$$$$$$');
* }
* });
*
* Ext.define('Thief', {
* ...
* });
*
* Thief.borrow(Bank, ['money', 'printMoney']);
*
* var steve = new Thief();
*
* alert(steve.money); // alerts '$$$'
* steve.printMoney(); // alerts '$$$$$$$'
*
* @param {Ext.Base} fromClass The class to borrow members from
* @param {String/String[]} members The names of the members to borrow
* @return {Ext.Base} this
* @static
* @inheritable
*/
borrow: function(fromClass, members) {
var fromPrototype = fromClass.prototype,
i, ln, member;
members = Ext.Array.from(members);
for (i = 0, ln = members.length; i < ln; i++) {
member = members[i];
this.own(member, fromPrototype[member]);
}
return this;
},
/**
* Override prototype members of this class. Overridden methods can be invoked via
* {@link Ext.Base#callOverridden}
*
* Ext.define('My.Cat', {
* constructor: function() {
* alert("I'm a cat!");
*
* return this;
* }
* });
*
* My.Cat.override({
* constructor: function() {
* alert("I'm going to be a cat!");
*
* var instance = this.callOverridden();
*
* alert("Meeeeoooowwww");
*
* return instance;
* }
* });
*
* var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
* // alerts "I'm a cat!"
* // alerts "Meeeeoooowwww"
*
* @param {Object} members
* @return {Ext.Base} this
* @static
* @inheritable
*/
override: function(members) {
var prototype = this.prototype,
enumerables = Ext.enumerables,
name, i, member, previous;
if (arguments.length === 2) {
name = members;
member = arguments[1];
if (typeof member == 'function') {
if (typeof prototype[name] == 'function') {
previous = prototype[name];
member.$previous = previous;
}
this.ownMethod(name, member);
}
else {
prototype[name] = member;
}
return this;
}
for (name in members) {
if (members.hasOwnProperty(name)) {
member = members[name];
if (typeof member === 'function') {
if (typeof prototype[name] === 'function') {
previous = prototype[name];
member.$previous = previous;
}
this.ownMethod(name, member);
}
else {
prototype[name] = member;
}
}
}
if (enumerables) {
for (i = enumerables.length; i--;) {
name = enumerables[i];
if (members.hasOwnProperty(name)) {
if (typeof prototype[name] !== 'undefined') {
previous = prototype[name];
members[name].$previous = previous;
}
this.ownMethod(name, members[name]);
}
}
}
return this;
},
//<feature classSystem.mixins>
/**
* Used internally by the mixins pre-processor
* @private
* @inheritable
*/
mixin: function(name, cls) {
var mixin = cls.prototype,
my = this.prototype,
key, fn;
for (key in mixin) {
if (mixin.hasOwnProperty(key)) {
if (typeof my[key] === 'undefined' && key !== 'mixins' && key !== 'mixinId') {
if (typeof mixin[key] === 'function') {
fn = mixin[key];
if (typeof fn.$owner === 'undefined') {
this.ownMethod(key, fn);
}
else {
my[key] = fn;
}
}
else {
my[key] = mixin[key];
}
}
//<feature classSystem.config>
else if (key === 'config' && my.config && mixin.config) {
Ext.Object.merge(my.config, mixin.config);
}
//</feature>
}
}
if (typeof mixin.onClassMixedIn !== 'undefined') {
mixin.onClassMixedIn.call(cls, this);
}
if (!my.hasOwnProperty('mixins')) {
if ('mixins' in my) {
my.mixins = Ext.Object.merge({}, my.mixins);
}
else {
my.mixins = {};
}
}
my.mixins[name] = mixin;
},
//</feature>
/**
* Get the current class' name in string format.
*
* Ext.define('My.cool.Class', {
* constructor: function() {
* alert(this.self.getName()); // alerts 'My.cool.Class'
* }
* });
*
* My.cool.Class.getName(); // 'My.cool.Class'
*
* @return {String} className
* @static
* @inheritable
*/
getName: function() {
return Ext.getClassName(this);
},
/**
* Create aliases for existing prototype methods. Example:
*
* Ext.define('My.cool.Class', {
* method1: function() { ... },
* method2: function() { ... }
* });
*
* var test = new My.cool.Class();
*
* My.cool.Class.createAlias({
* method3: 'method1',
* method4: 'method2'
* });
*
* test.method3(); // test.method1()
*
* My.cool.Class.createAlias('method5', 'method3');
*
* test.method5(); // test.method3() -> test.method1()
*
* @param {String/Object} alias The new method name, or an object to set multiple aliases. See
* {@link Ext.Function#flexSetter flexSetter}
* @param {String/Object} origin The original method name
* @static
* @inheritable
* @method
*/
createAlias: flexSetter(function(alias, origin) {
this.prototype[alias] = function() {
return this[origin].apply(this, arguments);
}
})
});
})(Ext.Function.flexSetter);

View File

@@ -0,0 +1,559 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @author Jacky Nguyen <jacky@sencha.com>
* @docauthor Jacky Nguyen <jacky@sencha.com>
* @class Ext.Class
*
* Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
* should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
* features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
*
* If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
* {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
*
* Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
* from, see {@link Ext.Base}.
*/
(function() {
var Class,
Base = Ext.Base,
baseStaticProperties = [],
baseStaticProperty;
for (baseStaticProperty in Base) {
if (Base.hasOwnProperty(baseStaticProperty)) {
baseStaticProperties.push(baseStaticProperty);
}
}
/**
* @method constructor
* Creates new class.
* @param {Object} classData An object represent the properties of this class
* @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
* Note that the creation process can be asynchronous depending on the pre-processors used.
* @return {Ext.Base} The newly created class
*/
Ext.Class = Class = function(newClass, classData, onClassCreated) {
if (typeof newClass != 'function') {
onClassCreated = classData;
classData = newClass;
newClass = function() {
return this.constructor.apply(this, arguments);
};
}
if (!classData) {
classData = {};
}
var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
registeredPreprocessors = Class.getPreprocessors(),
index = 0,
preprocessors = [],
preprocessor, staticPropertyName, process, i, j, ln;
for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
staticPropertyName = baseStaticProperties[i];
newClass[staticPropertyName] = Base[staticPropertyName];
}
delete classData.preprocessors;
for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
preprocessor = preprocessorStack[j];
if (typeof preprocessor == 'string') {
preprocessor = registeredPreprocessors[preprocessor];
if (!preprocessor.always) {
if (classData.hasOwnProperty(preprocessor.name)) {
preprocessors.push(preprocessor.fn);
}
}
else {
preprocessors.push(preprocessor.fn);
}
}
else {
preprocessors.push(preprocessor);
}
}
classData.onClassCreated = onClassCreated || Ext.emptyFn;
classData.onBeforeClassCreated = function(cls, data) {
onClassCreated = data.onClassCreated;
delete data.onBeforeClassCreated;
delete data.onClassCreated;
cls.implement(data);
onClassCreated.call(cls, cls);
};
process = function(cls, data) {
preprocessor = preprocessors[index++];
if (!preprocessor) {
data.onBeforeClassCreated.apply(this, arguments);
return;
}
if (preprocessor.call(this, cls, data, process) !== false) {
process.apply(this, arguments);
}
};
process.call(Class, newClass, classData);
return newClass;
};
Ext.apply(Class, {
/** @private */
preprocessors: {},
/**
* Register a new pre-processor to be used during the class creation process
*
* @member Ext.Class
* @param {String} name The pre-processor's name
* @param {Function} fn The callback function to be executed. Typical format:
*
* function(cls, data, fn) {
* // Your code here
*
* // Execute this when the processing is finished.
* // Asynchronous processing is perfectly ok
* if (fn) {
* fn.call(this, cls, data);
* }
* });
*
* @param {Function} fn.cls The created class
* @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
* @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
* regardless of whether the processing is synchronous or aynchronous
*
* @return {Ext.Class} this
* @static
*/
registerPreprocessor: function(name, fn, always) {
this.preprocessors[name] = {
name: name,
always: always || false,
fn: fn
};
return this;
},
/**
* Retrieve a pre-processor callback function by its name, which has been registered before
*
* @param {String} name
* @return {Function} preprocessor
* @static
*/
getPreprocessor: function(name) {
return this.preprocessors[name];
},
getPreprocessors: function() {
return this.preprocessors;
},
/**
* Retrieve the array stack of default pre-processors
*
* @return {Function[]} defaultPreprocessors
* @static
*/
getDefaultPreprocessors: function() {
return this.defaultPreprocessors || [];
},
/**
* Set the default array stack of default pre-processors
*
* @param {Function/Function[]} preprocessors
* @return {Ext.Class} this
* @static
*/
setDefaultPreprocessors: function(preprocessors) {
this.defaultPreprocessors = Ext.Array.from(preprocessors);
return this;
},
/**
* Inserts this pre-processor at a specific position in the stack, optionally relative to
* any existing pre-processor. For example:
*
* Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
* // Your code here
*
* if (fn) {
* fn.call(this, cls, data);
* }
* }).setDefaultPreprocessorPosition('debug', 'last');
*
* @param {String} name The pre-processor name. Note that it needs to be registered with
* {@link #registerPreprocessor registerPreprocessor} before this
* @param {String} offset The insertion position. Four possible values are:
* 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
* @param {String} relativeName
* @return {Ext.Class} this
* @static
*/
setDefaultPreprocessorPosition: function(name, offset, relativeName) {
var defaultPreprocessors = this.defaultPreprocessors,
index;
if (typeof offset == 'string') {
if (offset === 'first') {
defaultPreprocessors.unshift(name);
return this;
}
else if (offset === 'last') {
defaultPreprocessors.push(name);
return this;
}
offset = (offset === 'after') ? 1 : -1;
}
index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
if (index !== -1) {
Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
}
return this;
}
});
/**
* @cfg {String} extend
* The parent class that this class extends. For example:
*
* Ext.define('Person', {
* say: function(text) { alert(text); }
* });
*
* Ext.define('Developer', {
* extend: 'Person',
* say: function(text) { this.callParent(["print "+text]); }
* });
*/
Class.registerPreprocessor('extend', function(cls, data) {
var extend = data.extend,
base = Ext.Base,
basePrototype = base.prototype,
prototype = function() {},
parent, i, k, ln, staticName, parentStatics,
parentPrototype, clsPrototype;
if (extend && extend !== Object) {
parent = extend;
}
else {
parent = base;
}
parentPrototype = parent.prototype;
prototype.prototype = parentPrototype;
clsPrototype = cls.prototype = new prototype();
if (!('$class' in parent)) {
for (i in basePrototype) {
if (!parentPrototype[i]) {
parentPrototype[i] = basePrototype[i];
}
}
}
clsPrototype.self = cls;
cls.superclass = clsPrototype.superclass = parentPrototype;
delete data.extend;
//<feature classSystem.inheritableStatics>
// Statics inheritance
parentStatics = parentPrototype.$inheritableStatics;
if (parentStatics) {
for (k = 0, ln = parentStatics.length; k < ln; k++) {
staticName = parentStatics[k];
if (!cls.hasOwnProperty(staticName)) {
cls[staticName] = parent[staticName];
}
}
}
//</feature>
//<feature classSystem.config>
// Merge the parent class' config object without referencing it
if (parentPrototype.config) {
clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
}
else {
clsPrototype.config = {};
}
//</feature>
//<feature classSystem.onClassExtended>
if (clsPrototype.$onExtended) {
clsPrototype.$onExtended.call(cls, cls, data);
}
if (data.onClassExtended) {
clsPrototype.$onExtended = data.onClassExtended;
delete data.onClassExtended;
}
//</feature>
}, true);
//<feature classSystem.statics>
/**
* @cfg {Object} statics
* List of static methods for this class. For example:
*
* Ext.define('Computer', {
* statics: {
* factory: function(brand) {
* // 'this' in static methods refer to the class itself
* return new this(brand);
* }
* },
*
* constructor: function() { ... }
* });
*
* var dellComputer = Computer.factory('Dell');
*/
Class.registerPreprocessor('statics', function(cls, data) {
cls.addStatics(data.statics);
delete data.statics;
});
//</feature>
//<feature classSystem.inheritableStatics>
/**
* @cfg {Object} inheritableStatics
* List of inheritable static methods for this class.
* Otherwise just like {@link #statics} but subclasses inherit these methods.
*/
Class.registerPreprocessor('inheritableStatics', function(cls, data) {
cls.addInheritableStatics(data.inheritableStatics);
delete data.inheritableStatics;
});
//</feature>
//<feature classSystem.config>
/**
* @cfg {Object} config
* List of configuration options with their default values, for which automatically
* accessor methods are generated. For example:
*
* Ext.define('SmartPhone', {
* config: {
* hasTouchScreen: false,
* operatingSystem: 'Other',
* price: 500
* },
* constructor: function(cfg) {
* this.initConfig(cfg);
* }
* });
*
* var iPhone = new SmartPhone({
* hasTouchScreen: true,
* operatingSystem: 'iOS'
* });
*
* iPhone.getPrice(); // 500;
* iPhone.getOperatingSystem(); // 'iOS'
* iPhone.getHasTouchScreen(); // true;
* iPhone.hasTouchScreen(); // true
*/
Class.registerPreprocessor('config', function(cls, data) {
var prototype = cls.prototype;
Ext.Object.each(data.config, function(name) {
var cName = name.charAt(0).toUpperCase() + name.substr(1),
pName = name,
apply = 'apply' + cName,
setter = 'set' + cName,
getter = 'get' + cName;
if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
data[apply] = function(val) {
return val;
};
}
if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
data[setter] = function(val) {
var ret = this[apply].call(this, val, this[pName]);
if (typeof ret != 'undefined') {
this[pName] = ret;
}
return this;
};
}
if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
data[getter] = function() {
return this[pName];
};
}
});
Ext.Object.merge(prototype.config, data.config);
delete data.config;
});
//</feature>
//<feature classSystem.mixins>
/**
* @cfg {Object} mixins
* List of classes to mix into this class. For example:
*
* Ext.define('CanSing', {
* sing: function() {
* alert("I'm on the highway to hell...")
* }
* });
*
* Ext.define('Musician', {
* extend: 'Person',
*
* mixins: {
* canSing: 'CanSing'
* }
* })
*/
Class.registerPreprocessor('mixins', function(cls, data) {
var mixins = data.mixins,
name, mixin, i, ln;
delete data.mixins;
Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
if (mixins instanceof Array) {
for (i = 0,ln = mixins.length; i < ln; i++) {
mixin = mixins[i];
name = mixin.prototype.mixinId || mixin.$className;
cls.mixin(name, mixin);
}
}
else {
for (name in mixins) {
if (mixins.hasOwnProperty(name)) {
cls.mixin(name, mixins[name]);
}
}
}
});
});
//</feature>
Class.setDefaultPreprocessors([
'extend'
//<feature classSystem.statics>
,'statics'
//</feature>
//<feature classSystem.inheritableStatics>
,'inheritableStatics'
//</feature>
//<feature classSystem.config>
,'config'
//</feature>
//<feature classSystem.mixins>
,'mixins'
//</feature>
]);
//<feature classSystem.backwardsCompatible>
// Backwards compatible
Ext.extend = function(subclass, superclass, members) {
if (arguments.length === 2 && Ext.isObject(superclass)) {
members = superclass;
superclass = subclass;
subclass = null;
}
var cls;
if (!superclass) {
Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
}
members.extend = superclass;
members.preprocessors = [
'extend'
//<feature classSystem.statics>
,'statics'
//</feature>
//<feature classSystem.inheritableStatics>
,'inheritableStatics'
//</feature>
//<feature classSystem.mixins>
,'mixins'
//</feature>
//<feature classSystem.config>
,'config'
//</feature>
];
if (subclass) {
cls = new Class(subclass, members);
}
else {
cls = new Class(members);
}
cls.prototype.override = function(o) {
for (var m in o) {
if (o.hasOwnProperty(m)) {
this[m] = o[m];
}
}
};
return cls;
};
//</feature>
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.CompositeElement
* @extends Ext.CompositeElementLite
* <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
* members, or to perform collective actions upon the whole set.</p>
* <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
* {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
* <p>All methods return <i>this</i> and can be chained.</p>
* Usage:
<pre><code>
var els = Ext.select("#some-el div.some-class", true);
// or select directly from an existing element
var el = Ext.get('some-el');
el.select('div.some-class', true);
els.setWidth(100); // all elements become 100 width
els.hide(true); // all elements fade out and hide
// or
els.setWidth(100).hide(true);
</code></pre>
*/
Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
constructor : function(els, root){
this.elements = [];
this.add(els, root);
},
// private
getElement : function(el){
// In this case just return it, since we already have a reference to it
return el;
},
// private
transformElement : function(el){
return Ext.get(el);
}
});
/**
* Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
* to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
* {@link Ext.CompositeElementLite CompositeElementLite} object.
* @param {String/HTMLElement[]} selector The CSS selector or an array of elements
* @param {Boolean} [unique] true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
* @param {HTMLElement/String} [root] The root element of the query or id of the root
* @return {Ext.CompositeElementLite/Ext.CompositeElement}
* @member Ext.Element
* @method select
*/
Ext.Element.select = function(selector, unique, root){
var els;
if(typeof selector == "string"){
els = Ext.Element.selectorFunction(selector, root);
}else if(selector.length !== undefined){
els = selector;
}else{
//<debug>
Ext.Error.raise({
sourceClass: "Ext.Element",
sourceMethod: "select",
selector: selector,
unique: unique,
root: root,
msg: "Invalid selector specified: " + selector
});
//</debug>
}
return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
};
/**
* Shorthand of {@link Ext.Element#select}.
* @member Ext
* @method select
* @alias Ext.Element#select
*/
Ext.select = Ext.Element.select;

View File

@@ -0,0 +1,84 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.CompositeElementLite
*/
Ext.apply(Ext.CompositeElementLite.prototype, {
addElements : function(els, root){
if(!els){
return this;
}
if(typeof els == "string"){
els = Ext.Element.selectorFunction(els, root);
}
var yels = this.elements;
Ext.each(els, function(e) {
yels.push(Ext.get(e));
});
return this;
},
/**
* Returns the first Element
* @return {Ext.Element}
*/
first : function(){
return this.item(0);
},
/**
* Returns the last Element
* @return {Ext.Element}
*/
last : function(){
return this.item(this.getCount()-1);
},
/**
* Returns true if this composite contains the passed element
* @param el {String/HTMLElement/Ext.Element/Number} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
* @return Boolean
*/
contains : function(el){
return this.indexOf(el) != -1;
},
/**
* Removes the specified element(s).
* @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
* or an array of any of those.
* @param {Boolean} removeDom (optional) True to also remove the element from the document
* @return {Ext.CompositeElement} this
*/
removeElement : function(keys, removeDom){
var me = this,
els = this.elements,
el;
Ext.each(keys, function(val){
if ((el = (els[val] || els[val = me.indexOf(val)]))) {
if(removeDom){
if(el.dom){
el.remove();
}else{
Ext.removeNode(el);
}
}
Ext.Array.erase(els, val, 1);
}
});
return this;
}
});

View File

@@ -0,0 +1,338 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.CompositeElementLite
* <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
* members, or to perform collective actions upon the whole set.</p>
* <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
* {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
* Example:<pre><code>
var els = Ext.select("#some-el div.some-class");
// or select directly from an existing element
var el = Ext.get('some-el');
el.select('div.some-class');
els.setWidth(100); // all elements become 100 width
els.hide(true); // all elements fade out and hide
// or
els.setWidth(100).hide(true);
</code></pre>
*/
Ext.CompositeElementLite = function(els, root){
/**
* <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
* <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
* to augment the capabilities of the CompositeElementLite class may use it when adding
* methods to the class.</p>
* <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
* following siblings of selected elements, the code would be</p><code><pre>
Ext.override(Ext.CompositeElementLite, {
nextAll: function() {
var els = this.elements, i, l = els.length, n, r = [], ri = -1;
// Loop through all elements in this Composite, accumulating
// an Array of all siblings.
for (i = 0; i < l; i++) {
for (n = els[i].nextSibling; n; n = n.nextSibling) {
r[++ri] = n;
}
}
// Add all found siblings to this Composite
return this.add(r);
}
});</pre></code>
* @property {HTMLElement} elements
*/
this.elements = [];
this.add(els, root);
this.el = new Ext.Element.Flyweight();
};
Ext.CompositeElementLite.prototype = {
isComposite: true,
// private
getElement : function(el){
// Set the shared flyweight dom property to the current element
var e = this.el;
e.dom = el;
e.id = el.id;
return e;
},
// private
transformElement : function(el){
return Ext.getDom(el);
},
/**
* Returns the number of elements in this Composite.
* @return Number
*/
getCount : function(){
return this.elements.length;
},
/**
* Adds elements to this Composite object.
* @param {HTMLElement[]/Ext.CompositeElement} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
* @return {Ext.CompositeElement} This Composite object.
*/
add : function(els, root){
var me = this,
elements = me.elements;
if(!els){
return this;
}
if(typeof els == "string"){
els = Ext.Element.selectorFunction(els, root);
}else if(els.isComposite){
els = els.elements;
}else if(!Ext.isIterable(els)){
els = [els];
}
for(var i = 0, len = els.length; i < len; ++i){
elements.push(me.transformElement(els[i]));
}
return me;
},
invoke : function(fn, args){
var me = this,
els = me.elements,
len = els.length,
e,
i;
for(i = 0; i < len; i++) {
e = els[i];
if(e){
Ext.Element.prototype[fn].apply(me.getElement(e), args);
}
}
return me;
},
/**
* Returns a flyweight Element of the dom element object at the specified index
* @param {Number} index
* @return {Ext.Element}
*/
item : function(index){
var me = this,
el = me.elements[index],
out = null;
if(el){
out = me.getElement(el);
}
return out;
},
// fixes scope with flyweight
addListener : function(eventName, handler, scope, opt){
var els = this.elements,
len = els.length,
i, e;
for(i = 0; i<len; i++) {
e = els[i];
if(e) {
Ext.EventManager.on(e, eventName, handler, scope || e, opt);
}
}
return this;
},
/**
* <p>Calls the passed function for each element in this composite.</p>
* @param {Function} fn The function to call. The function is passed the following parameters:<ul>
* <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
* <b>This is the flyweight (shared) Ext.Element instance, so if you require a
* a reference to the dom node, use el.dom.</b></div></li>
* <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
* <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
* </ul>
* @param {Object} [scope] The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
* @return {Ext.CompositeElement} this
*/
each : function(fn, scope){
var me = this,
els = me.elements,
len = els.length,
i, e;
for(i = 0; i<len; i++) {
e = els[i];
if(e){
e = this.getElement(e);
if(fn.call(scope || e, e, me, i) === false){
break;
}
}
}
return me;
},
/**
* Clears this Composite and adds the elements passed.
* @param {HTMLElement[]/Ext.CompositeElement} els Either an array of DOM elements, or another Composite from which to fill this Composite.
* @return {Ext.CompositeElement} this
*/
fill : function(els){
var me = this;
me.elements = [];
me.add(els);
return me;
},
/**
* Filters this composite to only elements that match the passed selector.
* @param {String/Function} selector A string CSS selector or a comparison function.
* The comparison function will be called with the following arguments:<ul>
* <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
* <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
* </ul>
* @return {Ext.CompositeElement} this
*/
filter : function(selector){
var els = [],
me = this,
fn = Ext.isFunction(selector) ? selector
: function(el){
return el.is(selector);
};
me.each(function(el, self, i) {
if (fn(el, i) !== false) {
els[els.length] = me.transformElement(el);
}
});
me.elements = els;
return me;
},
/**
* Find the index of the passed element within the composite collection.
* @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
* @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
*/
indexOf : function(el){
return Ext.Array.indexOf(this.elements, this.transformElement(el));
},
/**
* Replaces the specified element with the passed element.
* @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the element in this composite
* to replace.
* @param {String/Ext.Element} replacement The id of an element or the Element itself.
* @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
* @return {Ext.CompositeElement} this
*/
replaceElement : function(el, replacement, domReplace){
var index = !isNaN(el) ? el : this.indexOf(el),
d;
if(index > -1){
replacement = Ext.getDom(replacement);
if(domReplace){
d = this.elements[index];
d.parentNode.insertBefore(replacement, d);
Ext.removeNode(d);
}
Ext.Array.splice(this.elements, index, 1, replacement);
}
return this;
},
/**
* Removes all elements.
*/
clear : function(){
this.elements = [];
}
};
Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
/**
* @private
* Copies all of the functions from Ext.Element's prototype onto CompositeElementLite's prototype.
* This is called twice - once immediately below, and once again after additional Ext.Element
* are added in Ext JS
*/
Ext.CompositeElementLite.importElementMethods = function() {
var fnName,
ElProto = Ext.Element.prototype,
CelProto = Ext.CompositeElementLite.prototype;
for (fnName in ElProto) {
if (typeof ElProto[fnName] == 'function'){
(function(fnName) {
CelProto[fnName] = CelProto[fnName] || function() {
return this.invoke(fnName, arguments);
};
}).call(CelProto, fnName);
}
}
};
Ext.CompositeElementLite.importElementMethods();
if(Ext.DomQuery){
Ext.Element.selectorFunction = Ext.DomQuery.select;
}
/**
* Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
* to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
* {@link Ext.CompositeElementLite CompositeElementLite} object.
* @param {String/HTMLElement[]} selector The CSS selector or an array of elements
* @param {HTMLElement/String} root (optional) The root element of the query or id of the root
* @return {Ext.CompositeElementLite/Ext.CompositeElement}
* @member Ext.Element
* @method select
*/
Ext.Element.select = function(selector, root){
var els;
if(typeof selector == "string"){
els = Ext.Element.selectorFunction(selector, root);
}else if(selector.length !== undefined){
els = selector;
}else{
//<debug>
Ext.Error.raise({
sourceClass: "Ext.Element",
sourceMethod: "select",
selector: selector,
root: root,
msg: "Invalid selector specified: " + selector
});
//</debug>
}
return new Ext.CompositeElementLite(els);
};
/**
* Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
* to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
* {@link Ext.CompositeElementLite CompositeElementLite} object.
* @param {String/HTMLElement[]} selector The CSS selector or an array of elements
* @param {HTMLElement/String} root (optional) The root element of the query or id of the root
* @return {Ext.CompositeElementLite/Ext.CompositeElement}
* @member Ext
* @method select
*/
Ext.select = Ext.Element.select;

View File

@@ -0,0 +1,567 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.DomHelper
* @alternateClassName Ext.core.DomHelper
*
* <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
* elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
* from your DOM building code.</p>
*
* <p><b><u>DomHelper element specification object</u></b></p>
* <p>A specification object is used when creating elements. Attributes of this object
* are assumed to be element attributes, except for 4 special attributes:
* <div class="mdetail-params"><ul>
* <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
* <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
* same kind of element definition objects to be created and appended. These can be nested
* as deep as you want.</div></li>
* <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
* This will end up being either the "class" attribute on a HTML fragment or className
* for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
* <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
* </ul></div></p>
* <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
* HTML-escaped prior to building the element's HTML string. This means that if your attribute value
* contains special characters that would not normally be allowed in a double-quoted attribute value,
* you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
* malformed HTML being created. This behavior may change in a future release.</p>
*
* <p><b><u>Insertion methods</u></b></p>
* <p>Commonly used insertion methods:
* <div class="mdetail-params"><ul>
* <li><tt>{@link #append}</tt> : <div class="sub-desc"></div></li>
* <li><tt>{@link #insertBefore}</tt> : <div class="sub-desc"></div></li>
* <li><tt>{@link #insertAfter}</tt> : <div class="sub-desc"></div></li>
* <li><tt>{@link #overwrite}</tt> : <div class="sub-desc"></div></li>
* <li><tt>{@link #createTemplate}</tt> : <div class="sub-desc"></div></li>
* <li><tt>{@link #insertHtml}</tt> : <div class="sub-desc"></div></li>
* </ul></div></p>
*
* <p><b><u>Example</u></b></p>
* <p>This is an example, where an unordered list with 3 children items is appended to an existing
* element with id <tt>'my-div'</tt>:<br>
<pre><code>
var dh = Ext.DomHelper; // create shorthand alias
// specification object
var spec = {
id: 'my-ul',
tag: 'ul',
cls: 'my-list',
// append children after creating
children: [ // may also specify 'cn' instead of 'children'
{tag: 'li', id: 'item0', html: 'List Item 0'},
{tag: 'li', id: 'item1', html: 'List Item 1'},
{tag: 'li', id: 'item2', html: 'List Item 2'}
]
};
var list = dh.append(
'my-div', // the context element 'my-div' can either be the id or the actual node
spec // the specification object
);
</code></pre></p>
* <p>Element creation specification parameters in this class may also be passed as an Array of
* specification objects. This can be used to insert multiple sibling nodes into an existing
* container very efficiently. For example, to add more list items to the example above:<pre><code>
dh.append('my-ul', [
{tag: 'li', id: 'item3', html: 'List Item 3'},
{tag: 'li', id: 'item4', html: 'List Item 4'}
]);
* </code></pre></p>
*
* <p><b><u>Templating</u></b></p>
* <p>The real power is in the built-in templating. Instead of creating or appending any elements,
* <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
* insert new elements. Revisiting the example above, we could utilize templating this time:
* <pre><code>
// create the node
var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
// get template
var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
for(var i = 0; i < 5, i++){
tpl.append(list, [i]); // use template to append to the actual node
}
* </code></pre></p>
* <p>An example using a template:<pre><code>
var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
var tpl = new Ext.DomHelper.createTemplate(html);
tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed&#39;s Site"]);
tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
* </code></pre></p>
*
* <p>The same example using named parameters:<pre><code>
var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
var tpl = new Ext.DomHelper.createTemplate(html);
tpl.append('blog-roll', {
id: 'link1',
url: 'http://www.edspencer.net/',
text: "Ed&#39;s Site"
});
tpl.append('blog-roll', {
id: 'link2',
url: 'http://www.dustindiaz.com/',
text: "Dustin&#39;s Site"
});
* </code></pre></p>
*
* <p><b><u>Compiling Templates</u></b></p>
* <p>Templates are applied using regular expressions. The performance is great, but if
* you are adding a bunch of DOM elements using the same template, you can increase
* performance even further by {@link Ext.Template#compile "compiling"} the template.
* The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
* broken up at the different variable points and a dynamic function is created and eval'ed.
* The generated function performs string concatenation of these parts and the passed
* variables instead of using regular expressions.
* <pre><code>
var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
var tpl = new Ext.DomHelper.createTemplate(html);
tpl.compile();
//... use template like normal
* </code></pre></p>
*
* <p><b><u>Performance Boost</u></b></p>
* <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
* of DOM can significantly boost performance.</p>
* <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
* then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
* results in the creation of a text node. Usage:</p>
* <pre><code>
Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
* </code></pre>
* @singleton
*/
Ext.ns('Ext.core');
Ext.core.DomHelper = Ext.DomHelper = function(){
var tempTableEl = null,
emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
tableRe = /^table|tbody|tr|td$/i,
confRe = /tag|children|cn|html$/i,
tableElRe = /td|tr|tbody/i,
endRe = /end/i,
pub,
// kill repeat to save bytes
afterbegin = 'afterbegin',
afterend = 'afterend',
beforebegin = 'beforebegin',
beforeend = 'beforeend',
ts = '<table>',
te = '</table>',
tbs = ts+'<tbody>',
tbe = '</tbody>'+te,
trs = tbs + '<tr>',
tre = '</tr>'+tbe;
// private
function doInsert(el, o, returnElement, pos, sibling, append){
el = Ext.getDom(el);
var newNode;
if (pub.useDom) {
newNode = createDom(o, null);
if (append) {
el.appendChild(newNode);
} else {
(sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
}
} else {
newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
}
return returnElement ? Ext.get(newNode, true) : newNode;
}
function createDom(o, parentNode){
var el,
doc = document,
useSet,
attr,
val,
cn;
if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
for (var i = 0, l = o.length; i < l; i++) {
createDom(o[i], el);
}
} else if (typeof o == 'string') { // Allow a string as a child spec.
el = doc.createTextNode(o);
} else {
el = doc.createElement( o.tag || 'div' );
useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
for (attr in o) {
if(!confRe.test(attr)){
val = o[attr];
if(attr == 'cls'){
el.className = val;
}else{
if(useSet){
el.setAttribute(attr, val);
}else{
el[attr] = val;
}
}
}
}
Ext.DomHelper.applyStyles(el, o.style);
if ((cn = o.children || o.cn)) {
createDom(cn, el);
} else if (o.html) {
el.innerHTML = o.html;
}
}
if(parentNode){
parentNode.appendChild(el);
}
return el;
}
// build as innerHTML where available
function createHtml(o){
var b = '',
attr,
val,
key,
cn,
i;
if(typeof o == "string"){
b = o;
} else if (Ext.isArray(o)) {
for (i=0; i < o.length; i++) {
if(o[i]) {
b += createHtml(o[i]);
}
}
} else {
b += '<' + (o.tag = o.tag || 'div');
for (attr in o) {
val = o[attr];
if(!confRe.test(attr)){
if (typeof val == "object") {
b += ' ' + attr + '="';
for (key in val) {
b += key + ':' + val[key] + ';';
}
b += '"';
}else{
b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
}
}
}
// Now either just close the tag or try to add children and close the tag.
if (emptyTags.test(o.tag)) {
b += '/>';
} else {
b += '>';
if ((cn = o.children || o.cn)) {
b += createHtml(cn);
} else if(o.html){
b += o.html;
}
b += '</' + o.tag + '>';
}
}
return b;
}
function ieTable(depth, s, h, e){
tempTableEl.innerHTML = [s, h, e].join('');
var i = -1,
el = tempTableEl,
ns;
while(++i < depth){
el = el.firstChild;
}
// If the result is multiple siblings, then encapsulate them into one fragment.
ns = el.nextSibling;
if (ns){
var df = document.createDocumentFragment();
while(el){
ns = el.nextSibling;
df.appendChild(el);
el = ns;
}
el = df;
}
return el;
}
/**
* @ignore
* Nasty code for IE's broken table implementation
*/
function insertIntoTable(tag, where, el, html) {
var node,
before;
tempTableEl = tempTableEl || document.createElement('div');
if(tag == 'td' && (where == afterbegin || where == beforeend) ||
!tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
return null;
}
before = where == beforebegin ? el :
where == afterend ? el.nextSibling :
where == afterbegin ? el.firstChild : null;
if (where == beforebegin || where == afterend) {
el = el.parentNode;
}
if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
node = ieTable(4, trs, html, tre);
} else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
(tag == 'tr' && (where == beforebegin || where == afterend))) {
node = ieTable(3, tbs, html, tbe);
} else {
node = ieTable(2, ts, html, te);
}
el.insertBefore(node, before);
return node;
}
/**
* @ignore
* Fix for IE9 createContextualFragment missing method
*/
function createContextualFragment(html){
var div = document.createElement("div"),
fragment = document.createDocumentFragment(),
i = 0,
length, childNodes;
div.innerHTML = html;
childNodes = div.childNodes;
length = childNodes.length;
for (; i < length; i++) {
fragment.appendChild(childNodes[i].cloneNode(true));
}
return fragment;
}
pub = {
/**
* Returns the markup for the passed Element(s) config.
* @param {Object} o The DOM object spec (and children)
* @return {String}
*/
markup : function(o){
return createHtml(o);
},
/**
* Applies a style specification to an element.
* @param {String/HTMLElement} el The element to apply styles to
* @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
* a function which returns such a specification.
*/
applyStyles : function(el, styles){
if (styles) {
el = Ext.fly(el);
if (typeof styles == "function") {
styles = styles.call();
}
if (typeof styles == "string") {
styles = Ext.Element.parseStyles(styles);
}
if (typeof styles == "object") {
el.setStyle(styles);
}
}
},
/**
* Inserts an HTML fragment into the DOM.
* @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
*
* For example take the following HTML: `<div>Contents</div>`
*
* Using different `where` values inserts element to the following places:
*
* - beforeBegin: `<HERE><div>Contents</div>`
* - afterBegin: `<div><HERE>Contents</div>`
* - beforeEnd: `<div>Contents<HERE></div>`
* - afterEnd: `<div>Contents</div><HERE>`
*
* @param {HTMLElement/TextNode} el The context element
* @param {String} html The HTML fragment
* @return {HTMLElement} The new node
*/
insertHtml : function(where, el, html){
var hash = {},
hashVal,
range,
rangeEl,
setStart,
frag,
rs;
where = where.toLowerCase();
// add these here because they are used in both branches of the condition.
hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
hash[afterend] = ['AfterEnd', 'nextSibling'];
// if IE and context element is an HTMLElement
if (el.insertAdjacentHTML) {
if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
return rs;
}
// add these two to the hash.
hash[afterbegin] = ['AfterBegin', 'firstChild'];
hash[beforeend] = ['BeforeEnd', 'lastChild'];
if ((hashVal = hash[where])) {
el.insertAdjacentHTML(hashVal[0], html);
return el[hashVal[1]];
}
// if (not IE and context element is an HTMLElement) or TextNode
} else {
// we cannot insert anything inside a textnode so...
if (Ext.isTextNode(el)) {
where = where === 'afterbegin' ? 'beforebegin' : where;
where = where === 'beforeend' ? 'afterend' : where;
}
range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
if (hash[where]) {
if (range) {
range[setStart](el);
frag = range.createContextualFragment(html);
} else {
frag = createContextualFragment(html);
}
el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
} else {
rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
if (el.firstChild) {
if (range) {
range[setStart](el[rangeEl]);
frag = range.createContextualFragment(html);
} else {
frag = createContextualFragment(html);
}
if(where == afterbegin){
el.insertBefore(frag, el.firstChild);
}else{
el.appendChild(frag);
}
} else {
el.innerHTML = html;
}
return el[rangeEl];
}
}
//<debug>
Ext.Error.raise({
sourceClass: 'Ext.DomHelper',
sourceMethod: 'insertHtml',
htmlToInsert: html,
targetElement: el,
msg: 'Illegal insertion point reached: "' + where + '"'
});
//</debug>
},
/**
* Creates new DOM element(s) and inserts them before el.
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
* @param {Boolean} returnElement (optional) true to return a Ext.Element
* @return {HTMLElement/Ext.Element} The new node
*/
insertBefore : function(el, o, returnElement){
return doInsert(el, o, returnElement, beforebegin);
},
/**
* Creates new DOM element(s) and inserts them after el.
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object} o The DOM object spec (and children)
* @param {Boolean} returnElement (optional) true to return a Ext.Element
* @return {HTMLElement/Ext.Element} The new node
*/
insertAfter : function(el, o, returnElement){
return doInsert(el, o, returnElement, afterend, 'nextSibling');
},
/**
* Creates new DOM element(s) and inserts them as the first child of el.
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
* @param {Boolean} returnElement (optional) true to return a Ext.Element
* @return {HTMLElement/Ext.Element} The new node
*/
insertFirst : function(el, o, returnElement){
return doInsert(el, o, returnElement, afterbegin, 'firstChild');
},
/**
* Creates new DOM element(s) and appends them to el.
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
* @param {Boolean} returnElement (optional) true to return a Ext.Element
* @return {HTMLElement/Ext.Element} The new node
*/
append : function(el, o, returnElement){
return doInsert(el, o, returnElement, beforeend, '', true);
},
/**
* Creates new DOM element(s) and overwrites the contents of el with them.
* @param {String/HTMLElement/Ext.Element} el The context element
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
* @param {Boolean} returnElement (optional) true to return a Ext.Element
* @return {HTMLElement/Ext.Element} The new node
*/
overwrite : function(el, o, returnElement){
el = Ext.getDom(el);
el.innerHTML = createHtml(o);
return returnElement ? Ext.get(el.firstChild) : el.firstChild;
},
createHtml : createHtml,
/**
* Creates new DOM element(s) without inserting them to the document.
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
* @return {HTMLElement} The new uninserted node
* @method
*/
createDom: createDom,
/** True to force the use of DOM instead of html fragments @type Boolean */
useDom : false,
/**
* Creates a new Ext.Template from the DOM object spec.
* @param {Object} o The DOM object spec (and children)
* @return {Ext.Template} The new template
*/
createTemplate : function(o){
var html = Ext.DomHelper.createHtml(o);
return Ext.create('Ext.Template', html);
}
};
return pub;
}();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Element
*/
Ext.Element.addMethods((function(){
var focusRe = /button|input|textarea|select|object/;
return {
/**
* Monitors this Element for the mouse leaving. Calls the function after the specified delay only if
* the mouse was not moved back into the Element within the delay. If the mouse <i>was</i> moved
* back in, the function is not called.
* @param {Number} delay The delay <b>in milliseconds</b> to wait for possible mouse re-entry before calling the handler function.
* @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time.
* @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to this Element.
* @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage:<pre><code>
// Hide the menu if the mouse moves out for 250ms or more
this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this);
...
// Remove mouseleave monitor on menu destroy
this.menuEl.un(this.mouseLeaveMonitor);
</code></pre>
*/
monitorMouseLeave: function(delay, handler, scope) {
var me = this,
timer,
listeners = {
mouseleave: function(e) {
timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
},
mouseenter: function() {
clearTimeout(timer);
},
freezeEvent: true
};
me.on(listeners);
return listeners;
},
/**
* Stops the specified event(s) from bubbling and optionally prevents the default action
* @param {String/String[]} eventName an event / array of events to stop from bubbling
* @param {Boolean} preventDefault (optional) true to prevent the default action too
* @return {Ext.Element} this
*/
swallowEvent : function(eventName, preventDefault) {
var me = this;
function fn(e) {
e.stopPropagation();
if (preventDefault) {
e.preventDefault();
}
}
if (Ext.isArray(eventName)) {
Ext.each(eventName, function(e) {
me.on(e, fn);
});
return me;
}
me.on(eventName, fn);
return me;
},
/**
* Create an event handler on this element such that when the event fires and is handled by this element,
* it will be relayed to another object (i.e., fired again as if it originated from that object instead).
* @param {String} eventName The type of event to relay
* @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
* for firing the relayed event
*/
relayEvent : function(eventName, observable) {
this.on(eventName, function(e) {
observable.fireEvent(eventName, e);
});
},
/**
* Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
* @param {Boolean} forceReclean (optional) By default the element
* keeps track if it has been cleaned already so
* you can call this over and over. However, if you update the element and
* need to force a reclean, you can pass true.
*/
clean : function(forceReclean) {
var me = this,
dom = me.dom,
n = dom.firstChild,
nx,
ni = -1;
if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
return me;
}
while (n) {
nx = n.nextSibling;
if (n.nodeType == 3) {
// Remove empty/whitespace text nodes
if (!(/\S/.test(n.nodeValue))) {
dom.removeChild(n);
// Combine adjacent text nodes
} else if (nx && nx.nodeType == 3) {
n.appendData(Ext.String.trim(nx.data));
dom.removeChild(nx);
nx = n.nextSibling;
n.nodeIndex = ++ni;
}
} else {
// Recursively clean
Ext.fly(n).clean();
n.nodeIndex = ++ni;
}
n = nx;
}
Ext.Element.data(dom, 'isCleaned', true);
return me;
},
/**
* Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
* parameter as {@link Ext.ElementLoader#load}
* @return {Ext.Element} this
*/
load : function(options) {
this.getLoader().load(options);
return this;
},
/**
* Gets this element's {@link Ext.ElementLoader ElementLoader}
* @return {Ext.ElementLoader} The loader
*/
getLoader : function() {
var dom = this.dom,
data = Ext.Element.data,
loader = data(dom, 'loader');
if (!loader) {
loader = Ext.create('Ext.ElementLoader', {
target: this
});
data(dom, 'loader', loader);
}
return loader;
},
/**
* Update the innerHTML of this element, optionally searching for and processing scripts
* @param {String} html The new HTML
* @param {Boolean} [loadScripts=false] True to look for and process scripts
* @param {Function} [callback] For async script loading you can be notified when the update completes
* @return {Ext.Element} this
*/
update : function(html, loadScripts, callback) {
var me = this,
id,
dom,
interval;
if (!me.dom) {
return me;
}
html = html || '';
dom = me.dom;
if (loadScripts !== true) {
dom.innerHTML = html;
Ext.callback(callback, me);
return me;
}
id = Ext.id();
html += '<span id="' + id + '"></span>';
interval = setInterval(function(){
if (!document.getElementById(id)) {
return false;
}
clearInterval(interval);
var DOC = document,
hd = DOC.getElementsByTagName("head")[0],
re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
srcRe = /\ssrc=([\'\"])(.*?)\1/i,
typeRe = /\stype=([\'\"])(.*?)\1/i,
match,
attrs,
srcMatch,
typeMatch,
el,
s;
while ((match = re.exec(html))) {
attrs = match[1];
srcMatch = attrs ? attrs.match(srcRe) : false;
if (srcMatch && srcMatch[2]) {
s = DOC.createElement("script");
s.src = srcMatch[2];
typeMatch = attrs.match(typeRe);
if (typeMatch && typeMatch[2]) {
s.type = typeMatch[2];
}
hd.appendChild(s);
} else if (match[2] && match[2].length > 0) {
if (window.execScript) {
window.execScript(match[2]);
} else {
window.eval(match[2]);
}
}
}
el = DOC.getElementById(id);
if (el) {
Ext.removeNode(el);
}
Ext.callback(callback, me);
}, 20);
dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
return me;
},
// inherit docs, overridden so we can add removeAnchor
removeAllListeners : function() {
this.removeAnchor();
Ext.EventManager.removeAll(this.dom);
return this;
},
/**
* Gets the parent node of the current element taking into account Ext.scopeResetCSS
* @protected
* @return {HTMLElement} The parent element
*/
getScopeParent: function(){
var parent = this.dom.parentNode;
return Ext.scopeResetCSS ? parent.parentNode : parent;
},
/**
* Creates a proxy element of this element
* @param {String/Object} config The class name of the proxy element or a DomHelper config object
* @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to (defaults to document.body)
* @param {Boolean} [matchBox=false] True to align and size the proxy to this element now.
* @return {Ext.Element} The new proxy element
*/
createProxy : function(config, renderTo, matchBox) {
config = (typeof config == 'object') ? config : {tag : "div", cls: config};
var me = this,
proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
Ext.DomHelper.insertBefore(me.dom, config, true);
proxy.setVisibilityMode(Ext.Element.DISPLAY);
proxy.hide();
if (matchBox && me.setBox && me.getBox) { // check to make sure Element.position.js is loaded
proxy.setBox(me.getBox());
}
return proxy;
},
/**
* Checks whether this element can be focused.
* @return {Boolean} True if the element is focusable
*/
focusable: function(){
var dom = this.dom,
nodeName = dom.nodeName.toLowerCase(),
canFocus = false,
hasTabIndex = !isNaN(dom.tabIndex);
if (!dom.disabled) {
if (focusRe.test(nodeName)) {
canFocus = true;
} else {
canFocus = nodeName == 'a' ? dom.href || hasTabIndex : hasTabIndex;
}
}
return canFocus && this.isVisible(true);
}
};
})());
Ext.Element.prototype.clearListeners = Ext.Element.prototype.removeAllListeners;

View File

@@ -0,0 +1,397 @@
/*
This file is part of Ext JS 4
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @class Ext.Element
*/
Ext.Element.addMethods({
/**
* Gets the x,y coordinates specified by the anchor position on the element.
* @param {String} [anchor='c'] The specified anchor position. See {@link #alignTo}
* for details on supported anchor positions.
* @param {Boolean} [local] True to get the local (element top/left-relative) anchor position instead
* of page coordinates
* @param {Object} [size] An object containing the size to use for calculating anchor position
* {width: (target width), height: (target height)} (defaults to the element's current size)
* @return {Number[]} [x, y] An array containing the element's x and y coordinates
*/
getAnchorXY : function(anchor, local, s){
//Passing a different size is useful for pre-calculating anchors,
//especially for anchored animations that change the el size.
anchor = (anchor || "tl").toLowerCase();
s = s || {};
var me = this,
vp = me.dom == document.body || me.dom == document,
w = s.width || vp ? Ext.Element.getViewWidth() : me.getWidth(),
h = s.height || vp ? Ext.Element.getViewHeight() : me.getHeight(),
xy,
r = Math.round,
o = me.getXY(),
scroll = me.getScroll(),
extraX = vp ? scroll.left : !local ? o[0] : 0,
extraY = vp ? scroll.top : !local ? o[1] : 0,
hash = {
c : [r(w * 0.5), r(h * 0.5)],
t : [r(w * 0.5), 0],
l : [0, r(h * 0.5)],
r : [w, r(h * 0.5)],
b : [r(w * 0.5), h],
tl : [0, 0],
bl : [0, h],
br : [w, h],
tr : [w, 0]
};
xy = hash[anchor];
return [xy[0] + extraX, xy[1] + extraY];
},
/**
* Anchors an element to another element and realigns it when the window is resized.
* @param {String/HTMLElement/Ext.Element} element The element to align to.
* @param {String} position The position to align to.
* @param {Number[]} [offsets] Offset the positioning by [x, y]
* @param {Boolean/Object} [animate] True for the default animation or a standard Element animation config object
* @param {Boolean/Number} [monitorScroll] True to monitor body scroll and reposition. If this parameter
* is a number, it is used as the buffer delay (defaults to 50ms).
* @param {Function} [callback] The function to call after the animation finishes
* @return {Ext.Element} this
*/
anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
var me = this,
dom = me.dom,
scroll = !Ext.isEmpty(monitorScroll),
action = function(){
Ext.fly(dom).alignTo(el, alignment, offsets, animate);
Ext.callback(callback, Ext.fly(dom));
},
anchor = this.getAnchor();
// previous listener anchor, remove it
this.removeAnchor();
Ext.apply(anchor, {
fn: action,
scroll: scroll
});
Ext.EventManager.onWindowResize(action, null);
if(scroll){
Ext.EventManager.on(window, 'scroll', action, null,
{buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
}
action.call(me); // align immediately
return me;
},
/**
* Remove any anchor to this element. See {@link #anchorTo}.
* @return {Ext.Element} this
*/
removeAnchor : function(){
var me = this,
anchor = this.getAnchor();
if(anchor && anchor.fn){
Ext.EventManager.removeResizeListener(anchor.fn);
if(anchor.scroll){
Ext.EventManager.un(window, 'scroll', anchor.fn);
}
delete anchor.fn;
}
return me;
},
// private
getAnchor : function(){
var data = Ext.Element.data,
dom = this.dom;
if (!dom) {
return;
}
var anchor = data(dom, '_anchor');
if(!anchor){
anchor = data(dom, '_anchor', {});
}
return anchor;
},
getAlignVector: function(el, spec, offset) {
var me = this,
side = {t:"top", l:"left", r:"right", b: "bottom"},
thisRegion = me.getRegion(),
elRegion;
el = Ext.get(el);
if(!el || !el.dom){
//<debug>
Ext.Error.raise({
sourceClass: 'Ext.Element',
sourceMethod: 'getAlignVector',
msg: 'Attempted to align an element that doesn\'t exist'
});
//</debug>
}
elRegion = el.getRegion();
},
/**
* Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
* supported position values.
* @param {String/HTMLElement/Ext.Element} element The element to align to.
* @param {String} [position="tl-bl?"] The position to align to (defaults to )
* @param {Number[]} [offsets] Offset the positioning by [x, y]
* @return {Number[]} [x, y]
*/
getAlignToXY : function(el, p, o){
el = Ext.get(el);
if(!el || !el.dom){
//<debug>
Ext.Error.raise({
sourceClass: 'Ext.Element',
sourceMethod: 'getAlignToXY',
msg: 'Attempted to align an element that doesn\'t exist'
});
//</debug>
}
o = o || [0,0];
p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
var me = this,
d = me.dom,
a1,
a2,
x,
y,
//constrain the aligned el to viewport if necessary
w,
h,
r,
dw = Ext.Element.getViewWidth() -10, // 10px of margin for ie
dh = Ext.Element.getViewHeight()-10, // 10px of margin for ie
p1y,
p1x,
p2y,
p2x,
swapY,
swapX,
doc = document,
docElement = doc.documentElement,
docBody = doc.body,
scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
c = false, //constrain to viewport
p1 = "",
p2 = "",
m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
if(!m){
//<debug>
Ext.Error.raise({
sourceClass: 'Ext.Element',
sourceMethod: 'getAlignToXY',
el: el,
position: p,
offset: o,
msg: 'Attemmpted to align an element with an invalid position: "' + p + '"'
});
//</debug>
}
p1 = m[1];
p2 = m[2];
c = !!m[3];
//Subtract the aligned el's internal xy from the target's offset xy
//plus custom offset to get the aligned el's new offset xy
a1 = me.getAnchorXY(p1, true);
a2 = el.getAnchorXY(p2, false);
x = a2[0] - a1[0] + o[0];
y = a2[1] - a1[1] + o[1];
if(c){
w = me.getWidth();
h = me.getHeight();
r = el.getRegion();
//If we are at a viewport boundary and the aligned el is anchored on a target border that is
//perpendicular to the vp border, allow the aligned el to slide on that border,
//otherwise swap the aligned el to the opposite border of the target.
p1y = p1.charAt(0);
p1x = p1.charAt(p1.length-1);
p2y = p2.charAt(0);
p2x = p2.charAt(p2.length-1);
swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
if (x + w > dw + scrollX) {
x = swapX ? r.left-w : dw+scrollX-w;
}
if (x < scrollX) {
x = swapX ? r.right : scrollX;
}
if (y + h > dh + scrollY) {
y = swapY ? r.top-h : dh+scrollY-h;
}
if (y < scrollY){
y = swapY ? r.bottom : scrollY;
}
}
return [x,y];
},
/**
* Aligns this element with another element relative to the specified anchor points. If the other element is the
* document it aligns it to the viewport.
* The position parameter is optional, and can be specified in any one of the following formats:
* <ul>
* <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
* <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
* The element being aligned will position its top-left corner (tl) to that point. <i>This method has been
* deprecated in favor of the newer two anchor syntax below</i>.</li>
* <li><b>Two anchors</b>: If two values from the table below are passed separated by a dash, the first value is used as the
* element's anchor point, and the second value is used as the target's anchor point.</li>
* </ul>
* In addition to the anchor points, the position parameter also supports the "?" character. If "?" is passed at the end of
* the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
* the viewport if necessary. Note that the element being aligned might be swapped to align to a different position than
* that specified in order to enforce the viewport constraints.
* Following are all of the supported anchor positions:
<pre>
Value Description
----- -----------------------------
tl The top left corner (default)
t The center of the top edge
tr The top right corner
l The center of the left edge
c In the center of the element
r The center of the right edge
bl The bottom left corner
b The center of the bottom edge
br The bottom right corner
</pre>
Example Usage:
<pre><code>
// align el to other-el using the default positioning ("tl-bl", non-constrained)
el.alignTo("other-el");
// align the top left corner of el with the top right corner of other-el (constrained to viewport)
el.alignTo("other-el", "tr?");
// align the bottom right corner of el with the center left edge of other-el
el.alignTo("other-el", "br-l?");
// align the center of el with the bottom left corner of other-el and
// adjust the x position by -6 pixels (and the y position by 0)
el.alignTo("other-el", "c-bl", [-6, 0]);
</code></pre>
* @param {String/HTMLElement/Ext.Element} element The element to align to.
* @param {String} [position="tl-bl?"] The position to align to
* @param {Number[]} [offsets] Offset the positioning by [x, y]
* @param {Boolean/Object} [animate] true for the default animation or a standard Element animation config object
* @return {Ext.Element} this
*/
alignTo : function(element, position, offsets, animate){
var me = this;
return me.setXY(me.getAlignToXY(element, position, offsets),
me.anim && !!animate ? me.anim(animate) : false);
},
// private ==> used outside of core
adjustForConstraints : function(xy, parent) {
var vector = this.getConstrainVector(parent, xy);
if (vector) {
xy[0] += vector[0];
xy[1] += vector[1];
}
return xy;
},
/**
* <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
* to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
* <p>Priority is given to constraining the top and left within the constraint.</p>
* <p>The constraint may either be an existing element into which this element is to be constrained, or
* an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
* @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
* @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
* of using this Element's current position;
* @returns {Number[]/Boolean} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
* vector by which this element must be translated. Otherwise, <code>false</code>.
*/
getConstrainVector: function(constrainTo, proposedPosition) {
if (!(constrainTo instanceof Ext.util.Region)) {
constrainTo = Ext.get(constrainTo).getViewRegion();
}
var thisRegion = this.getRegion(),
vector = [0, 0],
shadowSize = this.shadow && this.shadow.offset,
overflowed = false;
// Shift this region to occupy the proposed position
if (proposedPosition) {
thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
}
// Reduce the constrain region to allow for shadow
// TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
if (shadowSize) {
constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
}
// Constrain the X coordinate by however much this Element overflows
if (thisRegion.right > constrainTo.right) {
overflowed = true;
vector[0] = (constrainTo.right - thisRegion.right); // overflowed the right
}
if (thisRegion.left + vector[0] < constrainTo.left) {
overflowed = true;
vector[0] = (constrainTo.left - thisRegion.left); // overflowed the left
}
// Constrain the Y coordinate by however much this Element overflows
if (thisRegion.bottom > constrainTo.bottom) {
overflowed = true;
vector[1] = (constrainTo.bottom - thisRegion.bottom); // overflowed the bottom
}
if (thisRegion.top + vector[1] < constrainTo.top) {
overflowed = true;
vector[1] = (constrainTo.top - thisRegion.top); // overflowed the top
}
return overflowed ? vector : false;
},
/**
* Calculates the x, y to center this element on the screen
* @return {Number[]} The x, y values [x, y]
*/
getCenterXY : function(){
return this.getAlignToXY(document, 'c-c');
},
/**
* Centers the Element in either the viewport, or another Element.
* @param {String/HTMLElement/Ext.Element} centerIn (optional) The element in which to center the element.
*/
center : function(centerIn){
return this.alignTo(centerIn || document, 'c-c');
}
});

Some files were not shown because too many files have changed in this diff Show More