# PwnFx: AJAX sprinkles via unobtrusive JavaScript. # @author Victor Costan # The author sorely misses Rails' AJAX helpers such as observe_field. This # library provides a replacement that adheres to the new philosophy of # unobtrusive JavaScript triggered by HTML5 data- attributes. # The class of the singleton instance that tracks all the effects on the page. class PwnFxClass # Creates an instance that isn't aware of any effects. # # After defining an effect class, call registerEffect on this instance to make # it aware of the effect. constructor: -> @effects = [] @effectsByName = {} @hiddenClassName = null # @attribute {String} the default name of the DOM class used to hide # elements; by default, this is set to the data-pwnfx-hidden-class # attribute of the
element hiddenClassName: null # Wires JS to elements with data-pwnfx attributes. # # @param {Element} root the element whose content is wired; use document at # load time wire: (root) -> @hiddenClassName ||= document.body.getAttribute('data-pwnfx-hidden-class') || 'hidden' for effect in @effects attrName = "data-pwnfx-#{effect[0]}" effectClass = effect[1] scopeAttrName = "#{attrName}-scope" doneAttrName = "#{attrName}-done" attrSelector = "[#{attrName}]" for element in document.querySelectorAll(attrSelector) attrValue = element.getAttribute attrName continue unless attrValue element.removeAttribute attrName element.setAttribute doneAttrName, attrValue scopeId = element.getAttribute scopeAttrName new effectClass element, attrValue, scopeId null # Registers a PwnFx effect. # # @param {String} attrName string following data-pwnfx- in the effect's # attribute names # @param klass the class that wraps the effect's implementation registerEffect: (attrPrefix, klass) -> if @effectsByName[attrPrefix] throw new Error("PwnFx effect name {attrPrefix} already registered") @effects.push [attrPrefix, klass] # Finds a scoping container. # # @param {String} scopeId the scope ID to look for # @param {HTMLElement} element the element where the lookup starts # @return {HTMLElement} the closest parent of the given element whose # data-pwnfx-scope matches the scopeId argument; window.document is # returned if no such element exists or if scope is null resolveScope: (scopeId, element) -> element = null if scopeId is null while element isnt null && element.getAttribute('data-pwnfx-scope') isnt scopeId element = element.parentElement element || document # Performs a scoped querySelectAll. # # @param {HTMLElement} scope the DOM element serving as the search scope # @param {String} selector the CSS selector to query # @return {NodeList, Array} the elements in the scope that match the CSS # selector; the scope container can belong to the returned array queryScope: (scope, selector) -> scopeMatches = false if scope isnt document # TODO: machesSelector is in a W3C spec, but only implemented using # prefixes; the code below should be simplified once browsers # implement it without vendor prefixes if scope.matchesSelector scopeMatches = scope.matchesSelector selector else if scope.webkitMatchesSelector scopeMatches = scope.webkitMatchesSelector selector else if scope.mozMatchesSelector scopeMatches = scope.mozMatchesSelector selector if scopeMatches matches = Array.prototype.slice.call scope.querySelectorAll(selector) matches.push scope matches else scope.querySelectorAll selector # Replaces an element's contents with some HTML. # # The JavaScript inside the HTML's