# 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 = {} # Wires JS to elements with data-pwnfx attributes. # # @param [Element] root the element whose content is wired; use document at # load time wire: (root) -> 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("Effect name {attrPrefix} already registered") @effects.push [attrPrefix, klass] # Finds a scoping container. # # @param [String] scopeId the scope ID to look for # @param [Element] element the element where the lookup starts # @return [Element] 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 != null && element.getAttribute('data-pwnfx-scope') != scopeId element = element.parentElement element || document # Performs a scoped querySelectAll. # # @param [Element] 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 != 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 if scopeMatches matches = Array.prototype.slice.call scope.querySelectorAll(selector) matches.push scope matches else scope.querySelectorAll selector # Singleton instance. PwnFx = new PwnFxClass # Moves an element using data-pwnfx-move. # # Attributes: # data-pwnfx-move: an identifier connecting the move's target element # data-pwnfx-move-target: set to the same value as data-pwnfx-move on the # element that will receive the moved element as its last child class PwnFxMove constructor: (element, identifier, scopeId) -> scope = PwnFx.resolveScope scopeId, element target = document.querySelector "[data-pwnfx-move-target=\"#{identifier}\"]" target.appendChild element PwnFx.registerEffect 'move', PwnFxMove # Renders the contents of a template into a DOM element. # # Attributes: # data-pwnfx-render: identifier for the render operation # data-pwnfx-render-where: insertAdjacentHTML position argument; can be # beforebegin, afterbegin, beforeend, afterend; defaults to beforeend # data-pwnfx-render-randomize: regexp pattern whose matches will be replaced # with a random string; useful for generating unique IDs # data-pwnfx-render-target: set on the element(s) receiving the rendered HTML; # set to the identifier in data-pwnfx-render # data-pwnfx-render-source: set on the