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
$.data
delegate
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;
});