define([ "require", "dojo/_base/array", // array.forEach "dojo/_base/connect", // remove for 2.0 "dojo/_base/declare", // declare "dojo/_base/lang", // lang.getObject "dojo/mouse", "dojo/on", "dojo/touch", "./_WidgetBase" ], function(require, array, connect, declare, lang, mouse, on, touch, _WidgetBase){ // module: // dijit/_AttachMixin // Map from string name like "mouseenter" to synthetic event like mouse.enter var synthEvents = lang.delegate(touch, { "mouseenter": mouse.enter, "mouseleave": mouse.leave, "keypress": connect._keypress // remove for 2.0 }); // To be lightweight, _AttachMixin doesn't require() dijit/a11yclick. // If the subclass has a template using "ondijitclick", it must load dijit/a11yclick itself. // In that case, the a11yclick variable below will get set to point to that synthetic event. var a11yclick; var _AttachMixin = declare("dijit._AttachMixin", null, { // summary: // Mixin for widgets to attach to dom nodes and setup events via // convenient data-dojo-attach-point and data-dojo-attach-event DOM attributes. // // Superclass of _TemplatedMixin, and can also be used standalone when templates are pre-rendered on the // server. // // Does not [yet] handle widgets like ContentPane with this.containerNode set. It should skip // scanning for data-dojo-attach-point and data-dojo-attach-event inside this.containerNode, but it // doesn't. /*===== // _attachPoints: [private] String[] // List of widget attribute names associated with data-dojo-attach-point=... in the // template, ex: ["containerNode", "labelNode"] _attachPoints: [], // _attachEvents: [private] Handle[] // List of connections associated with data-dojo-attach-event=... in the // template _attachEvents: [], // attachScope: [public] Object // Object to which attach points and events will be scoped. Defaults // to 'this'. attachScope: undefined, // searchContainerNode: [protected] Boolean // Search descendants of this.containerNode for data-dojo-attach-point and data-dojo-attach-event. // Should generally be left false (the default value) both for performance and to avoid failures when // this.containerNode holds other _AttachMixin instances with their own attach points and events. searchContainerNode: false, =====*/ constructor: function(/*===== params, srcNodeRef =====*/){ // summary: // Create the widget. // params: Object|null // Hash of initialization parameters for widget, including scalar values (like title, duration etc.) // and functions, typically callbacks like onClick. // The hash can contain any of the widget's properties, excluding read-only properties. // srcNodeRef: DOMNode|String? // If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree. this._attachPoints = []; this._attachEvents = []; }, buildRendering: function(){ // summary: // Attach to DOM nodes marked with special attributes. // tags: // protected this.inherited(arguments); // recurse through the node, looking for, and attaching to, our // attachment points and events, which should be defined on the template node. this._attachTemplateNodes(this.domNode); this._beforeFillContent(); // hook for _WidgetsInTemplateMixin }, _beforeFillContent: function(){ }, _attachTemplateNodes: function(rootNode){ // summary: // Iterate through the dom nodes and attach functions and nodes accordingly. // description: // Map widget properties and functions to the handlers specified in // the dom node and it's descendants. This function iterates over all // nodes and looks for these properties: // // - dojoAttachPoint/data-dojo-attach-point // - dojoAttachEvent/data-dojo-attach-event // rootNode: DomNode // The node to search for properties. All descendants will be searched. // tags: // private // DFS to process all nodes except those inside of this.containerNode var node = rootNode; while(true){ if(node.nodeType == 1 && (this._processTemplateNode(node, function(n,p){ return n.getAttribute(p); }, this._attach) || this.searchContainerNode) && node.firstChild){ node = node.firstChild; }else{ if(node == rootNode){ return; } while(!node.nextSibling){ node = node.parentNode; if(node == rootNode){ return; } } node = node.nextSibling; } } }, _processTemplateNode: function(/*DOMNode|Widget*/ baseNode, getAttrFunc, attachFunc){ // summary: // Process data-dojo-attach-point and data-dojo-attach-event for given node or widget. // Returns true if caller should process baseNode's children too. var ret = true; // Process data-dojo-attach-point var _attachScope = this.attachScope || this, attachPoint = getAttrFunc(baseNode, "dojoAttachPoint") || getAttrFunc(baseNode, "data-dojo-attach-point"); if(attachPoint){ var point, points = attachPoint.split(/\s*,\s*/); while((point = points.shift())){ if(lang.isArray(_attachScope[point])){ _attachScope[point].push(baseNode); }else{ _attachScope[point] = baseNode; } ret = (point != "containerNode"); this._attachPoints.push(point); } } // Process data-dojo-attach-event var attachEvent = getAttrFunc(baseNode, "dojoAttachEvent") || getAttrFunc(baseNode, "data-dojo-attach-event"); if(attachEvent){ // NOTE: we want to support attributes that have the form // "domEvent: nativeEvent; ..." var event, events = attachEvent.split(/\s*,\s*/); var trim = lang.trim; while((event = events.shift())){ if(event){ var thisFunc = null; if(event.indexOf(":") != -1){ // oh, if only JS had tuple assignment var funcNameArr = event.split(":"); event = trim(funcNameArr[0]); thisFunc = trim(funcNameArr[1]); }else{ event = trim(event); } if(!thisFunc){ thisFunc = event; } this._attachEvents.push(attachFunc(baseNode, event, lang.hitch(_attachScope, thisFunc))); } } } return ret; }, _attach: function(node, type, func){ // summary: // Roughly corresponding to dojo/on, this is the default function for processing a // data-dojo-attach-event. Meant to attach to DOMNodes, not to widgets. // node: DOMNode // The node to setup a listener on. // type: String // Event name like "click". // getAttrFunc: Function // Function to get the specified property for a given DomNode/Widget. // attachFunc: Function? // Attaches an event handler from the specified node/widget to specified function. // Map special type names like "mouseenter" to synthetic events. // Subclasses are responsible to require() dijit/a11yclick if they want to use it. type = type.replace(/^on/, "").toLowerCase(); if(type == "dijitclick"){ type = a11yclick || (a11yclick = require("./a11yclick")); }else{ type = synthEvents[type] || type; } return on(node, type, func); }, _detachTemplateNodes: function() { // summary: // Detach and clean up the attachments made in _attachtempalteNodes. // Delete all attach points to prevent IE6 memory leaks. var _attachScope = this.attachScope || this; array.forEach(this._attachPoints, function(point){ delete _attachScope[point]; }); this._attachPoints = []; // And same for event handlers array.forEach(this._attachEvents, function(handle){ handle.remove(); }); this._attachEvents = []; }, destroyRendering: function(){ this._detachTemplateNodes(); this.inherited(arguments); } }); // These arguments can be specified for widgets which are used in templates. // Since any widget can be specified as sub widgets in template, mix it // into the base widget class. (This is a hack, but it's effective.). // Remove for 2.0. Also, hide from API doc parser. lang.extend(_WidgetBase, /*===== {} || =====*/ { dojoAttachEvent: "", dojoAttachPoint: "" }); return _AttachMixin; });