(function($) { /** Taken from jQuery 1.5.2 for backwards compatibility */ if ($.support.changeBubbles == undefined) { $.support.changeBubbles = true; var el = document.createElement("div"); eventName = "onchange"; if (el.attachEvent) { var isSupported = (eventName in el); if (!isSupported) { el.setAttribute(eventName, "return;"); isSupported = typeof el[eventName] === "function"; } $.support.changeBubbles = isSupported; } } /* Return true if node b is the same as, or is a descendant of, node a */ if (document.compareDocumentPosition) { var is_or_contains = function(a, b) { return a && b && (a == b || !!(a.compareDocumentPosition(b) & 16)); }; } else { var is_or_contains = function(a, b) { return a && b && (a == b || (a.contains ? a.contains(b) : true)); }; } /* Add the methods to handle event binding to the Namespace class */ $.entwine.Namespace.addMethods({ build_event_proxy: function(name) { var one = this.one(name, 'func'); var prxy = function(e, data) { // For events that do not bubble we manually trigger delegation (see delegate_submit below) // If this event is a manual trigger, the event we actually want to bubble is attached as a property of the passed event e = e.delegatedEvent || e; var el = e.target; while (el && el.nodeType == 1 && !e.isPropagationStopped()) { var ret = one(el, arguments); if (ret !== undefined) e.result = ret; if (ret === false) { e.preventDefault(); e.stopPropagation(); } el = el.parentNode; } }; return prxy; }, build_mouseenterleave_proxy: function(name) { var one = this.one(name, 'func'); var prxy = function(e) { var el = e.target; var rel = e.relatedTarget; while (el && el.nodeType == 1 && !e.isPropagationStopped()) { /* We know el contained target. If it also contains relatedTarget then we didn't mouseenter / leave. What's more, every ancestor will also contan el and rel, and so we can just stop bubbling */ if (is_or_contains(el, rel)) break; var ret = one(el, arguments); if (ret !== undefined) e.result = ret; if (ret === false) { e.preventDefault(); e.stopPropagation(); } el = el.parentNode; } }; return prxy; }, build_change_proxy: function(name) { var one = this.one(name, 'func'); /* This change bubble emulation code is taken mostly from jQuery 1.6 - unfortunately we can't easily reuse any of it without duplication, so we'll have to re-migrate any bugfixes */ // Get the value of an item. Isn't supposed to be interpretable, just stable for some value, and different // once the value changes var getVal = function( elem ) { var type = elem.type, val = elem.value; if (type === "radio" || type === "checkbox") { val = elem.checked; } else if (type === "select-multiple") { val = ""; if (elem.selectedIndex > -1) { val = jQuery.map(elem.options, function(elem){ return elem.selected; }).join("-"); } } else if (jQuery.nodeName(elem, "select")) { val = elem.selectedIndex; } return val; }; // Test if a node name is a form input var rformElems = /^(?:textarea|input|select)$/i; // Check if this event is a change, and bubble the change event if it is var testChange = function(e) { var elem = e.target, data, val; if (!rformElems.test(elem.nodeName) || elem.readOnly) return; data = jQuery.data(elem, "_entwine_change_data"); val = getVal(elem); // the current data will be also retrieved by beforeactivate if (e.type !== "focusout" || elem.type !== "radio") { jQuery.data(elem, "_entwine_change_data", val); } if (data === undefined || val === data) return; if (data != null || val) { e.type = "change"; while (elem && elem.nodeType == 1 && !e.isPropagationStopped()) { var ret = one(elem, arguments); if (ret !== undefined) e.result = ret; if (ret === false) { e.preventDefault(); e.stopPropagation(); } elem = elem.parentNode; } } }; // The actual proxy - responds to several events, some of which triger a change check, some // of which just store the value for future change checks var prxy = function(e) { var event = e.type, elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; switch (event) { case 'focusout': case 'beforedeactivate': testChange.apply(this, arguments); break; case 'click': if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { testChange.apply(this, arguments); } break; // Change has to be called before submit // Keydown will be called before keypress, which is used in submit-event delegation case 'keydown': if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || type === "select-multiple" ) { testChange.apply(this, arguments); } break; // Beforeactivate happens also before the previous element is blurred // with this event you can't trigger a change event, but you can store // information case 'focusin': case 'beforeactivate': jQuery.data( elem, "_entwine_change_data", getVal(elem) ); break; } } return prxy; }, bind_event: function(selector, name, func, event) { var funcs = this.store[name] || (this.store[name] = $.entwine.RuleList()) ; var proxies = funcs.proxies || (funcs.proxies = {}); var rule = funcs.addRule(selector, name); rule.func = func; if (!proxies[name]) { switch (name) { case 'onmouseenter': proxies[name] = this.build_mouseenterleave_proxy(name); event = 'mouseover'; break; case 'onmouseleave': proxies[name] = this.build_mouseenterleave_proxy(name); event = 'mouseout'; break; case 'onchange': if (!$.support.changeBubbles) { proxies[name] = this.build_change_proxy(name); event = 'click keydown focusin focusout beforeactivate beforedeactivate'; } break; case 'onsubmit': event = 'delegatedSubmit'; break; case 'onfocus': case 'onblur': $.entwine.warn('Event '+event+' not supported - using focusin / focusout instead', $.entwine.WARN_LEVEL_IMPORTANT); } // If none of the special handlers created a proxy, use the generic proxy if (!proxies[name]) proxies[name] = this.build_event_proxy(name); $(document).bind(event.replace(/(\s+|$)/g, '.entwine$1'), proxies[name]); } } }); $.entwine.Namespace.addHandler({ order: 40, bind: function(selector, k, v){ var match, event; if ($.isFunction(v) && (match = k.match(/^on(.*)/))) { event = match[1]; this.bind_event(selector, k, v, event); return true; } } }); // Find all forms and bind onsubmit to trigger on the document too. // This is the only event that can't be grabbed via delegation var form_binding_cache = $([]); // A cache for already-handled form elements var delegate_submit = function(e, data){ var delegationEvent = $.Event('delegatedSubmit'); delegationEvent.delegatedEvent = e; return $(document).trigger(delegationEvent, data); }; $(document).bind('DOMMaybeChanged', function(){ var forms = $('form'); // Only bind to forms we haven't processed yet forms.not(form_binding_cache).bind('submit', delegate_submit); // Then remember the current set of forms form_binding_cache = forms; }); })(jQuery);