steal('can/util', 'can/construct', function (can) {Create organized, memory-leak free, rapidly performing, stateful controls with declarative eventing binding. Used when creating UI controls with behaviors, bound to elements on the page.
steal('can/util', 'can/construct', function (can) { var bind = function (el, ev, callback) {
can.bind.call(el, ev, callback);
return function () {
can.unbind.call(el, ev, callback);
};
},
isFunction = can.isFunction,
extend = can.extend,
each = can.each,
slice = [].slice,
paramReplacer = /\{([^\}]+)\}/g,
special = can.getObject("$.event.special", [can]) || {}, delegate = function (el, selector, ev, callback) {
can.delegate.call(el, selector, ev, callback);
return function () {
can.undelegate.call(el, selector, ev, callback);
};
}, binder = function (el, ev, callback, selector) {
return selector ?
delegate(el, can.trim(selector), ev, callback) :
bind(el, ev, callback);
},
basicProcessor;
var Control = can.Control = can.Construct(
/**
* @add can.Control
*/ /**
* @static
*/
{This function pre-processes which methods are event listeners and which are methods of
the control. It has a mechanism to allow controllers to inherit default values from super
classes, like can.Construct, and will cache functions that are action functions (see _isAction)
or functions with an underscored name.
setup: function () {
can.Construct.setup.apply(this, arguments);
if (can.Control) {
var control = this,
funcName;
control.actions = {};
for (funcName in control.prototype) {
if (control._isAction(funcName)) {
control.actions[funcName] = control._action(funcName);
}
}
}
}, _shifter: function (context, name) {
var method = typeof name === "string" ? context[name] : name;
if (!isFunction(method)) {
method = context[method];
}
return function () {
context.called = name;
return method.apply(context, [this.nodeName ? can.$(this) : this].concat(slice.call(arguments, 0)));
};
},Return true if methodName refers to an action. An action is a methodName value that
is not the constructor, and is either a function or string that refers to a function, or is
defined in special, processors. Detects whether methodName is also a valid method name.
_isAction: function (methodName) {
var val = this.prototype[methodName],
type = typeof val;
return (methodName !== 'constructor') &&
(type === "function" || (type === "string" && isFunction(this.prototype[val]))) &&
!! (special[methodName] || processors[methodName] || /[^\w]/.test(methodName));
},Takes a method name and the options passed to a control and tries to return the data necessary to pass to a processor (something that binds things).
For performance reasons, _action is called twice:
{window} foo), it returns null. For non-templated method names it returns the event binding data. That data is added to this.actions. _action: function (methodName, options) {If we don’t have options (a control instance), we’ll run this later. If we have
options, run can.sub to replace the action template {} with values from the options
or window. If a {} template resolves to an object, convertedName will be an array.
In that case, the event name we want will be the last item in that array.
paramReplacer.lastIndex = 0;
if (options || !paramReplacer.test(methodName)) {
var convertedName = options ? can.sub(methodName, this._lookup(options)) : methodName;
if (!convertedName) {!steal-remove-start
can.dev.log('can/control/control.js: No property found for handling ' + methodName);!steal-remove-end
return null;
}
var arr = can.isArray(convertedName),
name = arr ? convertedName[1] : convertedName,
parts = name.split(/\s+/g),
event = parts.pop();
return {
processor: processors[event] || basicProcessor,
parts: [name, parts.join(" "), event],
delegate: arr ? convertedName[0] : undefined
};
}
},
_lookup: function (options) {
return [options, window];
},An object of {eventName : function} pairs that Control uses to
hook up events automatically.
processors: {},A object of name-value pairs that act as default values for a control instance
defaults: {}
}, { /**
* @prototype
*/Setup is where most of the Control’s magic happens. It performs several pre-initialization steps:
this.element$.datadelegate
The final step is to return pass the element and prepareed options, to be used in init. setup: function (element, options) {
var cls = this.constructor,
pluginname = cls.pluginName || cls._fullName,
arr;Retrieve the raw element, then set the plugin name as a class there.
this.element = can.$(element);
if (pluginname && pluginname !== 'can_control') {
this.element.addClass(pluginname);
}Set up the ‘controls’ data on the element. If it does not exist, initialize it to an empty array.
arr = can.data(this.element, 'controls');
if (!arr) {
arr = [];
can.data(this.element, 'controls', arr);
}
arr.push(this);The this.options property is an Object that contains configuration data
passed to a control when it is created (new can.Control(element, options))
The options argument passed when creating the control is merged with can.Control.defaults
in [can.Control.prototype.setup setup].
If no options value is used during creation, the value in defaults is used instead
this.options = extend({}, cls.defaults, options);
this.on();
return [this.element, this.options];
},This binds an event handler for an event to a selector under the scope of this.element
If no options are specified, all events are rebound to their respective elements. The actions,
which were cached in setup, are used and all elements are bound using delegate from this.element.
on: function (el, selector, eventName, func) {
if (!el) {
this.off();
var cls = this.constructor,
bindings = this._bindings,
actions = cls.actions,
element = this.element,
destroyCB = can.Control._shifter(this, "destroy"),
funcName, ready;
for (funcName in actions) {Only push if we have the action and no option is undefined
if ( actions.hasOwnProperty(funcName) ) {
ready = actions[funcName] || cls._action(funcName, this.options, this);
if( ready ) {
bindings.control[funcName] = ready.processor(ready.delegate || element,
ready.parts[2], ready.parts[1], funcName, this);
}
}
}Set up the ability to destroy the control later.
can.bind.call(element, "removed", destroyCB);
bindings.user.push(function (el) {
can.unbind.call(el, "removed", destroyCB);
});
return bindings.user.length;
}if el is a string, use that as selector and re-set it to this control’s element…
if (typeof el === 'string') {
func = eventName;
eventName = selector;
selector = el;
el = this.element;
}…otherwise, set selector to null
if (func === undefined) {
func = eventName;
eventName = selector;
selector = null;
}
if (typeof func === 'string') {
func = can.Control._shifter(this, func);
}
this._bindings.user.push(binder(el, eventName, func, selector));
return this._bindings.user.length;
},Unbinds all event handlers on the controller. This should only be called in combination with .on()
off: function () {
var el = this.element[0],
bindings = this._bindings;
if( bindings ) {
each(bindings.user || [], function (value) {
value(el);
});
each(bindings.control || {}, function (value) {
value(el);
});
}Adds bindings.
this._bindings = {user: [], control: {}};
},Prepares a control for garbage collection.
First checks if it has already been removed. Then, removes all the bindings, data, and
the element from the Control instance.
destroy: function () {
if (this.element === null) {!steal-remove-start
can.dev.warn("can/control/control.js: Control already destroyed");!steal-remove-end
return;
}
var Class = this.constructor,
pluginName = Class.pluginName || Class._fullName,
controls;
this.off();
if (pluginName && pluginName !== 'can_control') {
this.element.removeClass(pluginName);
}
controls = can.data(this.element, "controls");
controls.splice(can.inArray(this, controls), 1);
can.trigger(this, "destroyed");
this.element = null;
}
});Processors do the binding. This basic processor binds events. Each returns a function that unbinds when called.
var processors = can.Control.processors;
basicProcessor = function (el, event, selector, methodName, control) {
return binder(el, event, can.Control._shifter(control, methodName), selector);
};Set common events to be processed as a basicProcessor
each(["change", "click", "contextmenu", "dblclick", "keydown", "keyup",
"keypress", "mousedown", "mousemove", "mouseout", "mouseover",
"mouseup", "reset", "resize", "scroll", "select", "submit", "focusin",
"focusout", "mouseenter", "mouseleave",
"touchstart", "touchmove", "touchcancel", "touchend", "touchleave",
"inserted","removed",
"dragstart", "dragenter", "dragover", "dragleave", "drag", "drop", "dragend"
], function (v) {
processors[v] = basicProcessor;
});
return Control;
});