linuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacos
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
581 lines
19 KiB
581 lines
19 KiB
9 years ago
|
/**
|
||
|
* This mixin provides its user with a `responsiveConfig` config that allows the class
|
||
|
* to conditionally control config properties.
|
||
|
*
|
||
|
* For example:
|
||
|
*
|
||
|
* Ext.define('ResponsiveClass', {
|
||
|
* mixin: [
|
||
|
* 'Ext.mixin.Responsive'
|
||
|
* ],
|
||
|
*
|
||
|
* responsiveConfig: {
|
||
|
* portrait: {
|
||
|
* },
|
||
|
*
|
||
|
* landscape: {
|
||
|
* }
|
||
|
* }
|
||
|
* });
|
||
|
*
|
||
|
* For a config to participate as a responsiveConfig it must have a "setter" method. In
|
||
|
* the below example, a "setRegion" method must exist.
|
||
|
*
|
||
|
* Ext.create({
|
||
|
* xtype: 'viewport',
|
||
|
* layout: 'border',
|
||
|
*
|
||
|
* items: [{
|
||
|
* title: 'Some Title',
|
||
|
* plugins: 'responsive',
|
||
|
*
|
||
|
* responsiveConfig: {
|
||
|
* 'width < 800': {
|
||
|
* region: 'north'
|
||
|
* },
|
||
|
* 'width >= 800': {
|
||
|
* region: 'west'
|
||
|
* }
|
||
|
* }
|
||
|
* }]
|
||
|
* });
|
||
|
*
|
||
|
* To use responsiveConfig the class must be defined using the Ext.mixin.Responsive mixin.
|
||
|
*
|
||
|
* Ext.define('App.view.Foo', {
|
||
|
* extend: 'Ext.panel.Panel',
|
||
|
* xtype: 'foo',
|
||
|
* mixins: [
|
||
|
* 'Ext.mixin.Responsive'
|
||
|
* ],
|
||
|
* ...
|
||
|
* });
|
||
|
*
|
||
|
* Otherwise, you will need to use the responsive plugin if the class is not one you authored.
|
||
|
*
|
||
|
* Ext.create('Ext.panel.Panel', {
|
||
|
* renderTo: document.body,
|
||
|
* plugins: 'responsive',
|
||
|
* ...
|
||
|
* });
|
||
|
*
|
||
|
* _Note:_ There is the exception of `Ext.container.Viewport` or other classes using `Ext.plugin.Viewport`.
|
||
|
* In those cases, the viewport plugin inherits from `Ext.plugin.Responsive`.
|
||
|
*
|
||
|
* For details see `{@link #responsiveConfig}`.
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
Ext.define('Ext.mixin.Responsive', function (Responsive) { return {
|
||
|
extend: 'Ext.Mixin',
|
||
|
requires: [
|
||
|
'Ext.GlobalEvents'
|
||
|
],
|
||
|
|
||
|
mixinConfig: {
|
||
|
id: 'responsive',
|
||
|
|
||
|
after: {
|
||
|
destroy: 'destroy'
|
||
|
}
|
||
|
},
|
||
|
|
||
|
config: {
|
||
|
/**
|
||
|
* @cfg {Object} responsiveConfig
|
||
|
* This object consists of keys that represent the conditions on which configs
|
||
|
* will be applied. For example:
|
||
|
*
|
||
|
* responsiveConfig: {
|
||
|
* landscape: {
|
||
|
* region: 'west'
|
||
|
* },
|
||
|
* portrait: {
|
||
|
* region: 'north'
|
||
|
* }
|
||
|
* }
|
||
|
*
|
||
|
* In this case the keys ("landscape" and "portrait") are the criteria (or "rules")
|
||
|
* and the object to their right contains the configs that will apply when that
|
||
|
* rule is true.
|
||
|
*
|
||
|
* These rules can be any valid JavaScript expression but the following values
|
||
|
* are considered in scope:
|
||
|
*
|
||
|
* * `landscape` - True if the device orientation is landscape (always `true` on
|
||
|
* desktop devices).
|
||
|
* * `portrait` - True if the device orientation is portrait (always `false` on
|
||
|
* desktop devices).
|
||
|
* * `tall` - True if `width` < `height` regardless of device type.
|
||
|
* * `wide` - True if `width` > `height` regardless of device type.
|
||
|
* * `width` - The width of the viewport
|
||
|
* * `height` - The height of the viewport.
|
||
|
* * `platform` - An object containing various booleans describing the platform
|
||
|
* (see `{@link Ext#platformTags Ext.platformTags}`). The properties of this
|
||
|
* object are also available implicitly (without "platform." prefix) but this
|
||
|
* sub-object may be useful to resolve ambiguity (for example, if one of the
|
||
|
* `{@link #responsiveFormulas}` overlaps and hides any of these properties).
|
||
|
* Previous to Ext JS 5.1, the `platformTags` were only available using this
|
||
|
* prefix.
|
||
|
*
|
||
|
* A more complex example:
|
||
|
*
|
||
|
* responsiveConfig: {
|
||
|
* 'desktop || width > 800': {
|
||
|
* region: 'west'
|
||
|
* },
|
||
|
*
|
||
|
* '!(desktop || width > 800)': {
|
||
|
* region: 'north'
|
||
|
* }
|
||
|
* }
|
||
|
*
|
||
|
* **NOTE**: If multiple rules set a single config (like above), it is important
|
||
|
* that the rules be mutually exclusive. That is, only one rule should set each
|
||
|
* config. If multiple rules are actively setting a single config, the order of
|
||
|
* these (and therefore the config's value) is unspecified.
|
||
|
*
|
||
|
* For a config to participate as a `responsiveConfig` it must have a "setter"
|
||
|
* method. In the above example, a "setRegion" method must exist.
|
||
|
*
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
responsiveConfig: {
|
||
|
$value: undefined,
|
||
|
|
||
|
merge: function (newValue, oldValue, target, mixinClass) {
|
||
|
if (!newValue) {
|
||
|
return oldValue;
|
||
|
}
|
||
|
|
||
|
var ret = oldValue ? Ext.Object.chain(oldValue) : {},
|
||
|
rule;
|
||
|
|
||
|
for (rule in newValue) {
|
||
|
if (!mixinClass || !(rule in ret)) {
|
||
|
ret[rule] = {
|
||
|
fn: null, // created on first evaluation of this rule
|
||
|
config: newValue[rule]
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @cfg {Object} responsiveFormulas
|
||
|
* It is common when using `responsiveConfig` to have recurring expressions that
|
||
|
* make for complex configurations. Using `responsiveFormulas` allows you to cut
|
||
|
* down on this repetition by adding new properties to the "scope" for the rules
|
||
|
* in a `responsiveConfig`.
|
||
|
*
|
||
|
* For example:
|
||
|
*
|
||
|
* Ext.define('MyApp.view.main.Main', {
|
||
|
* extend: 'Ext.container.Container',
|
||
|
*
|
||
|
* mixins: [
|
||
|
* 'Ext.mixin.Responsive'
|
||
|
* ],
|
||
|
*
|
||
|
* responsiveFormulas: {
|
||
|
* small: 'width < 600',
|
||
|
*
|
||
|
* medium: 'width >= 600 && width < 800',
|
||
|
*
|
||
|
* large: 'width >= 800',
|
||
|
*
|
||
|
* tuesday: function (context) {
|
||
|
* return (new Date()).getDay() === 2;
|
||
|
* }
|
||
|
* }
|
||
|
* });
|
||
|
*
|
||
|
* With the above declaration, any `responsiveConfig` can now use these values
|
||
|
* like so:
|
||
|
*
|
||
|
* responsiveConfig: {
|
||
|
* small: {
|
||
|
* hidden: true
|
||
|
* },
|
||
|
* medium: {
|
||
|
* hidden: false,
|
||
|
* region: 'north'
|
||
|
* },
|
||
|
* large: {
|
||
|
* hidden: false,
|
||
|
* region: 'west'
|
||
|
* }
|
||
|
* }
|
||
|
*
|
||
|
* @since 5.0.1
|
||
|
*/
|
||
|
responsiveFormulas: {
|
||
|
$value: 0,
|
||
|
|
||
|
merge: function (newValue, oldValue, target, mixinClass) {
|
||
|
return this.mergeNew(newValue, oldValue, target, mixinClass);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method removes this instance from the Responsive collection.
|
||
|
*/
|
||
|
destroy: function () {
|
||
|
Responsive.unregister(this);
|
||
|
this.callParent();
|
||
|
},
|
||
|
|
||
|
privates: {
|
||
|
statics: {
|
||
|
/**
|
||
|
* @property {Boolean} active
|
||
|
* @static
|
||
|
* @private
|
||
|
*/
|
||
|
active: false,
|
||
|
|
||
|
/**
|
||
|
* @property {Object} all
|
||
|
* The collection of all `Responsive` instances. These are the instances that
|
||
|
* will be notified when dynamic conditions change.
|
||
|
* @static
|
||
|
* @private
|
||
|
*/
|
||
|
all: {},
|
||
|
|
||
|
/**
|
||
|
* @property {Object} context
|
||
|
* This object holds the various context values passed to the rule evaluation
|
||
|
* functions.
|
||
|
* @static
|
||
|
* @private
|
||
|
*/
|
||
|
context: Ext.Object.chain(Ext.platformTags),
|
||
|
|
||
|
/**
|
||
|
* @property {Number} count
|
||
|
* The number of instances in the `all` collection.
|
||
|
* @static
|
||
|
* @private
|
||
|
*/
|
||
|
count: 0,
|
||
|
|
||
|
/**
|
||
|
* @property {Number} nextId
|
||
|
* The seed value used to assign `Responsive` instances a unique id for keying
|
||
|
* in the `all` collection.
|
||
|
* @static
|
||
|
* @private
|
||
|
*/
|
||
|
nextId: 0,
|
||
|
|
||
|
/**
|
||
|
* Activates event listeners for all `Responsive` instances. This method is
|
||
|
* called when the first instance is registered.
|
||
|
* @private
|
||
|
*/
|
||
|
activate: function () {
|
||
|
Responsive.active = true;
|
||
|
Responsive.updateContext();
|
||
|
Ext.on('resize', Responsive.onResize, Responsive);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Deactivates event listeners. This method is called when the last instance
|
||
|
* is destroyed.
|
||
|
* @private
|
||
|
*/
|
||
|
deactivate: function () {
|
||
|
Responsive.active = false;
|
||
|
Ext.un('resize', Responsive.onResize, Responsive);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Updates all registered the `Responsive` instances (found in the `all`
|
||
|
* collection).
|
||
|
* @private
|
||
|
*/
|
||
|
notify: function () {
|
||
|
var all = Responsive.all,
|
||
|
context = Responsive.context,
|
||
|
globalEvents = Ext.GlobalEvents,
|
||
|
timer = Responsive.timer,
|
||
|
id;
|
||
|
|
||
|
if (timer) {
|
||
|
Responsive.timer = null;
|
||
|
Ext.Function.cancelAnimationFrame(timer);
|
||
|
}
|
||
|
|
||
|
Responsive.updateContext();
|
||
|
|
||
|
Ext.suspendLayouts();
|
||
|
|
||
|
globalEvents.fireEvent('beforeresponsiveupdate', context);
|
||
|
|
||
|
for (id in all) {
|
||
|
all[id].setupResponsiveContext();
|
||
|
}
|
||
|
|
||
|
globalEvents.fireEvent('beginresponsiveupdate', context);
|
||
|
|
||
|
for (id in all) {
|
||
|
all[id].updateResponsiveState();
|
||
|
}
|
||
|
|
||
|
globalEvents.fireEvent('responsiveupdate', context);
|
||
|
|
||
|
Ext.resumeLayouts(true);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Handler of the window resize event. Schedules a timer so that we eventually
|
||
|
* call `notify`.
|
||
|
* @private
|
||
|
*/
|
||
|
onResize: function () {
|
||
|
if (!Responsive.timer) {
|
||
|
Responsive.timer = Ext.Function.requestAnimationFrame(Responsive.onTimer);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method is the timer handler. When called this removes the timer and
|
||
|
* calls `notify`.
|
||
|
* @private
|
||
|
*/
|
||
|
onTimer: function () {
|
||
|
Responsive.timer = null;
|
||
|
Responsive.notify();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method is called to update the internal state of a given config since
|
||
|
* the config is needed prior to `initConfig` processing the `instanceConfig`.
|
||
|
*
|
||
|
* @param {Ext.Base} instance The instance to configure.
|
||
|
* @param {Object} instanceConfig The config for the instance.
|
||
|
* @param {String} name The name of the config to process.
|
||
|
* @private
|
||
|
* @since 5.0.1
|
||
|
*/
|
||
|
processConfig: function (instance, instanceConfig, name) {
|
||
|
var value = instanceConfig && instanceConfig[name],
|
||
|
config = instance.config,
|
||
|
cfg, configurator;
|
||
|
|
||
|
// Good news is that both configs we have to handle have custom merges
|
||
|
// so we just need to get the Ext.Config instance and call it.
|
||
|
if (value) {
|
||
|
configurator = instance.getConfigurator();
|
||
|
cfg = configurator.configs[name]; // the Ext.Config instance
|
||
|
|
||
|
// Update "this.config" which is the storage for this instance.
|
||
|
config[name] = cfg.merge(value, config[name], instance);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
register: function (responder) {
|
||
|
var id = responder.$responsiveId;
|
||
|
|
||
|
if (!id) {
|
||
|
responder.$responsiveId = id = ++Responsive.nextId;
|
||
|
|
||
|
Responsive.all[id] = responder;
|
||
|
|
||
|
if (++Responsive.count === 1) {
|
||
|
Responsive.activate();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
unregister: function (responder) {
|
||
|
var id = responder.$responsiveId;
|
||
|
|
||
|
if (id in Responsive.all) {
|
||
|
responder.$responsiveId = null;
|
||
|
|
||
|
delete Responsive.all[id];
|
||
|
|
||
|
if (--Responsive.count === 0) {
|
||
|
Responsive.deactivate();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Updates the `context` object base on the current environment.
|
||
|
* @private
|
||
|
*/
|
||
|
updateContext: function () {
|
||
|
var El = Ext.Element,
|
||
|
width = El.getViewportWidth(),
|
||
|
height = El.getViewportHeight(),
|
||
|
context = Responsive.context;
|
||
|
|
||
|
context.width = width;
|
||
|
context.height = height;
|
||
|
context.tall = width < height;
|
||
|
context.wide = !context.tall;
|
||
|
|
||
|
context.landscape = context.portrait = false;
|
||
|
if (!context.platform) {
|
||
|
context.platform = Ext.platformTags;
|
||
|
}
|
||
|
|
||
|
context[Ext.dom.Element.getOrientation()] = true;
|
||
|
}
|
||
|
}, // private static
|
||
|
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* This class system hook method is called at the tail end of the mixin process.
|
||
|
* We need to see if the `targetClass` has already got a `responsiveConfig` and
|
||
|
* if so, we must add its value to the real config.
|
||
|
* @param {Ext.Class} targetClass
|
||
|
* @private
|
||
|
*/
|
||
|
afterClassMixedIn: function (targetClass) {
|
||
|
var proto = targetClass.prototype,
|
||
|
responsiveConfig = proto.responsiveConfig,
|
||
|
responsiveFormulas = proto.responsiveFormulas,
|
||
|
config;
|
||
|
|
||
|
if (responsiveConfig || responsiveFormulas) {
|
||
|
config = {};
|
||
|
|
||
|
if (responsiveConfig) {
|
||
|
delete proto.responsiveConfig;
|
||
|
config.responsiveConfig = responsiveConfig;
|
||
|
}
|
||
|
|
||
|
if (responsiveFormulas) {
|
||
|
delete proto.responsiveFormulas;
|
||
|
config.responsiveFormulas = responsiveFormulas;
|
||
|
}
|
||
|
|
||
|
targetClass.getConfigurator().add(config);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// The reason this method exists is so to convince the config system to put the
|
||
|
// "responsiveConfig" and "responsiveFormulas" in the initList. This needs to be
|
||
|
// done so that the initGetter is setup prior to calling transformInstanceConfig
|
||
|
// when we need to call the getters.
|
||
|
|
||
|
applyResponsiveConfig: function (rules) {
|
||
|
for (var rule in rules) {
|
||
|
rules[rule].fn = Ext.createRuleFn(rule);
|
||
|
}
|
||
|
return rules;
|
||
|
},
|
||
|
|
||
|
applyResponsiveFormulas: function (formulas) {
|
||
|
var ret = {},
|
||
|
fn, name;
|
||
|
|
||
|
if (formulas) {
|
||
|
for (name in formulas) {
|
||
|
if (Ext.isString(fn = formulas[name])) {
|
||
|
fn = Ext.createRuleFn(fn);
|
||
|
}
|
||
|
ret[name] = fn;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Evaluates and returns the configs based on the `responsiveConfig`. This
|
||
|
* method relies on the state being captured by the `updateContext` method.
|
||
|
* @private
|
||
|
*/
|
||
|
getResponsiveState: function () {
|
||
|
var context = Responsive.context,
|
||
|
rules = this.getResponsiveConfig(),
|
||
|
ret = {},
|
||
|
entry, rule;
|
||
|
|
||
|
if (rules) {
|
||
|
for (rule in rules) {
|
||
|
entry = rules[rule];
|
||
|
if (entry.fn.call(this, context)) {
|
||
|
Ext.merge(ret, entry.config);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
setupResponsiveContext: function () {
|
||
|
var formulas = this.getResponsiveFormulas(),
|
||
|
context = Responsive.context,
|
||
|
name;
|
||
|
|
||
|
if (formulas) {
|
||
|
for (name in formulas) {
|
||
|
context[name] = formulas[name].call(this, context);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This config system hook method is called just prior to processing the specified
|
||
|
* "instanceConfig". This hook returns the instanceConfig that will actually be
|
||
|
* processed by the config system.
|
||
|
* @param {Object} instanceConfig The user-supplied instance config object.
|
||
|
* @private
|
||
|
*/
|
||
|
transformInstanceConfig: function (instanceConfig) {
|
||
|
var me = this,
|
||
|
ret;
|
||
|
|
||
|
Responsive.register(me);
|
||
|
|
||
|
// Since we are called immediately prior to the Configurator looking at the
|
||
|
// instanceConfig, that incoming value has not yet been merged on to
|
||
|
// "this.config". We need to call getResponsiveConfig and getResponsiveFormulas
|
||
|
// and still get all that merged goodness, so we have to do the merge here.
|
||
|
|
||
|
if (instanceConfig) {
|
||
|
Responsive.processConfig(me, instanceConfig, 'responsiveConfig');
|
||
|
Responsive.processConfig(me, instanceConfig, 'responsiveFormulas');
|
||
|
}
|
||
|
|
||
|
// For updates this is done in bulk prior to updating all of the responsive
|
||
|
// objects, but for instantiation, we have to do this for ourselves now.
|
||
|
me.setupResponsiveContext();
|
||
|
|
||
|
// Now we can merge the current responsive state with the incoming config.
|
||
|
// The responsiveConfig takes priority.
|
||
|
ret = me.getResponsiveState();
|
||
|
|
||
|
if (instanceConfig) {
|
||
|
ret = Ext.merge({}, instanceConfig, ret);
|
||
|
|
||
|
// We don't want these to remain since we've already handled them.
|
||
|
delete ret.responsiveConfig;
|
||
|
delete ret.responsiveFormulas;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Evaluates and applies the `responsiveConfig` to this instance. This is called
|
||
|
* by `notify` automatically.
|
||
|
* @private
|
||
|
*/
|
||
|
updateResponsiveState: function () {
|
||
|
var config = this.getResponsiveState();
|
||
|
this.setConfig(config);
|
||
|
}
|
||
|
} // private
|
||
|
}});
|