packages feed

heist-async (empty) → 0.3.0.0

raw patch · 14 files changed

+1307/−0 lines, 14 filesdep +basedep +heistdep +template-haskellsetup-changed

Dependencies added: base, heist, template-haskell, text, xmlhtml

Files

+ Heist/Splices/Async.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction, TemplateHaskell #-}++module Heist.Splices.Async where+  +import            Text.Templating.Heist+import qualified  Data.Text as T+import qualified  Text.XmlHtml as X+import            Data.Maybe (fromMaybe)++import Heist.Splices.Async.TH (loadJS)++$(loadJS)++-- | heistAsyncSplices provides the six basic splices with sensible defaults+-- These are:+-- a-async: a link that loads it's results asynchronously and replaces parts of the page based on the contents. A normal anchor tag in all ways.+-- form-async: a form that submits asynchronously and replaces parts of the page with the results. A normal form tag otherwise.+-- div-async: a div that can be replaced or replace content on the page. It takes a "name" attribute that is it's unique identifier. When sending back content to replace, any div-asyncs present will replace existing div-asyncs on the page (identified by the name attribute)+-- div-async-append: a special div-async that instead of replacing the corresponding one on the page, it appends it's contents inside the existing div-async-append. Note: div-async's and div-async-appends are not interchangeable. This is so that it is easy to see what is going to happen from looking at the templates. If you need this jund of behavior, wrap you div-async-append inside a div-async.+-- redirect-async: this tag allows you to cause a client-side redirect. This is necessary because if you do a regular redirect, it will be followed by the browser and the result (the new page) will be handed back as if it were the page fragment response. It takes a "url" attribute that specifies where to redirect to.+-- activate-async: this is a convenience tag that will include all the necessary javascript. Feel free to copy the files yourself from tho js directory - by having separate files, they can be cached, which will mean less network transfer. Of course, the intention with this tag is you can get this running as quickly as possible. It can occur any number of times on the page, but will only actually include the javascript the first time.+heistAsyncSplices = [ ("a-async", aAsync)+                    , ("form-async", formAsync)+                    , ("div-async", divAsync)+                    , ("div-async-append", divAppendAsync)+                    , ("redirect-async", redirectAsync)+                    , ("activate-async", activateAsync)+                    ]+-- | aAsync: the actual splice used for "a-async" in heistAsyncSplices, in case you want to bind it to another tag.+aAsync :: Monad m => Splice m+aAsync = do+  node <- getParamNode+  return [X.setAttribute "rel" "async" $ X.Element "a" (X.elementAttrs node) (X.elementChildren node)]++-- | formAsync: the actual splice used for "form-async" in heistAsyncSplices, in case you want to bind it to another tag.+formAsync :: Monad m => Splice m+formAsync = do+  node <- getParamNode+  return [X.setAttribute "data-async" "1" $ X.Element "form" (X.elementAttrs node) (X.elementChildren node)]+++-- | divAsync: the actual splice used for "div-async" in heistAsyncSplices, in case you want to bind it to another tag.+divAsync :: Monad m => Splice m+divAsync = do+  node <- getParamNode+  let name = fromMaybe "undefined" $ X.getAttribute "name" node+  return [X.setAttribute "data-splice-name" name $ X.Element "div" (filter ((/= "name").fst) $ X.elementAttrs node) (X.elementChildren node)]++-- | divAsyncAppend: the actual splice used for "div-async-append" in heistAsyncSplices, in case you want to bind it to another tag.+divAppendAsync :: Monad m => Splice m+divAppendAsync = do+  node <- getParamNode+  let name = fromMaybe "undefined" $ X.getAttribute "name" node+  return [X.setAttribute "data-append-name" name $ X.Element "div" (filter ((/= "name").fst) $ X.elementAttrs node) (X.elementChildren node)]+  +-- | redirectAsync: the actual splice used for "redirect-async" in heistAsyncSplices, in case you want to bind it to another tag.+redirectAsync :: Monad m => Splice m+redirectAsync = do+  node <- getParamNode+  case X.getAttribute "url" node of+    Nothing -> return []+    Just url -> return [X.Element "div" [("data-redirect-url", url)] []]+++-- | activateAsync: the actual splice used for "activate-async" in heistAsyncSplices, in case you want to bind it to another tag.+activateAsync :: Monad m => Splice m+activateAsync = do+  -- make sure that only the first call to this does anything.+  modifyTS $ bindSplice "activate-async" (return [])+  return [X.Element "script" [("type","text/javascript")] [X.TextNode js]]+    where js = T.pack fileContents
+ Heist/Splices/Async/TH.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE TemplateHaskell #-}+module Heist.Splices.Async.TH +  (loadJS)+where++import qualified  Data.Text as T+import qualified  Data.Text.IO as TIO+import            Language.Haskell.TH+import            Language.Haskell.TH.Syntax++-- | loadJS: this template haskell function put's the contents of the javascript files into fileContents, so that it can be included with activateAsync+loadJS = do let fname = mkName "fileContents"+            typeSig <- SigD fname `fmap` [t| String |]+            v <- valD (varP fname) (normalB $ loadJSFiles) []+            return [typeSig, v]++loadJSFiles = do fs <- runIO $ mapM readFile ["js/valentine.min.js", "js/reqwest.min.js", "js/qwery.min.js", "js/heist-async.min.js"]+                 lift $ unlines fs
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Daniel Patterson++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Daniel Patterson nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,66 @@+# heist-async: Adding asynchronous updates of pages using heist.++## This provides six splices, exported in heistAsyncSplices++**\<activate-async/\>** - This brings in the required javascript - mainly, the three small javascript libraries (in total weighing 12k) and the cade to make the splice replacement work (in \<1k). Minified (with uglifyjs) and regular versions are available in the js folder, and for production, serving those files would be preferable to including inline (as that way they could be cached), and implementations of heist-async.js using bigger frameworks (jQuery, prototype, etc) would be appreciated. Ideally they would be made available both as standalone files (ie, heist-async-jquery.js) and as splices (\<activate-async-jquery/\>). This splice will only run once (the first place it is called), so feel free to include it in various places throughout your templates, if that suits you needs better.++**\<a-async\>** - this is a regular \<a\> tag, except that it will add an extra attribute (rel=async) that will allow the javascript to capture the clicks, so it can be used any way a regular tag would be used. Because of this, it will automatically fall back to functioning as a normal link in the absence of javascript support. When it is clicked, the server is sent a request at the url provided in the href attribute, and then the response (which should be a list of top-level \<div-async\>s) is used to replace the corresponding \<div-async\>s on the page. This tag takes an option "data-loading-div" that is a div that will have it's contents replaced with "<div class='loading'></div>". This loading div can be styled to have, for example, a loading gif as it's background, to show that something is happening while the content loads. (note, you will obviously want to be replacing whatever div you specify with the result of the async call, because there is no "undo" and the <div class="loading"></div> will remain there forever otherwise)+  +**\<form-async\>** - analogously to \<a-async\>, this is a normal \<form\> tag that has an extra attribute added (data-async=1) to allow the javascript to capture the submits. Again, this means that it will fall back to a normal submit in the absence of javascript support. If the form is submitted with a button, the button will have the class "processing" added to it when the form has been submitted. This allows the button to be styled to indicate that something has happened. This is also used to prevent multiple submits (clicking on a button that has that class will do nothing), so be sure to replace the form / at least button in the response to the form. +  +**\<div-async name="something"\>** - this will result in a normal div tag that has an extra attribute identifying it as a div that can be replaced asynchronously (it is, data-splice-name). When you generate the original page, you put in \<div-async\>'s in all the places where you may want to replace content (and they do not have to be empty; in the case of pagination, they could contain the first page's worth of data), and then in the responses to the \<a-async\> or \<form-async\> request to the server, return \<div-async\>'s as the top level elements and the existing ones (if they exist) will be replaced by the now ones.++**\<div-async-append name="something"\>** - this will append the contents inside an existing \<div-async-append\>. ++**\<redirect-async url="/path/to/redirect"\>** - this will cause a full page redirect on the client. This is important because by default the ajax request will follow all redirects so you will end up with the resulting full page without a clear way of figuring out what happened (except introspecting and looking for suspicious elements). +++## The idea behind this:+The immediate inspiration for this is Facebook's Primer (and some of the code is derived from what they have made available), and more specifically the idea of controlling what content to replace client side on the server. So, when a client makes a request (via \<a-async\> or \<form-async\>) the server decides what \<div-async\>'s to pass back and therefore what content to replace client on the client. Depending on factors that the client could know nothing about, these div's could be different. The server could also choose to hide a specific div by passing back a \<div-async style="display:none"/\>. Additionally, this means that the rendering code can be identical, because if you have the code to render a given part of a page in a separate template, if it is a non-async request then you can render the whole page, and if it is an async request you can render just that part, without having to change how you are rendering, and without having to duplicate anything.++## Other thoughts:+This is really early software, and I have intentionally tried to keep it really minimal, to see how far it can go with just these three tags. There are a lot of other possibilities, but I'm not sure if they are actually necessary, and I wanted to stay on the minimal end to begin with.++## Usage example ++(this is using Snap, though the library in no way depends upon Snap):++test.tpl:++    <html>+      <head>+        <activate-async/>+      </head>+      <body>+        <a-async href="/ajax/a">Test link</a-async>+    +        <form-async action="/ajax/form">+        <input name="test" type="text"/>+        <input type="submit" value="submit"/>+        </form-async>+    +        <div-async name="new-entry" id="something">Starting value</div-async>+      </body>+    </html>++testa.tpl:++    <div-async name="new-entry">New value</div-async>++testform.tpl:++    <div-async name="new-entry">New value with data: <t/></div-async>++handler (this is a partial example, but it should be obvious how to apply it):++    import qualified  Data.Text.Encoding as TE+    import            Snap.Types+    import            Heist.Splices.Async+    +    testForm = do t <- getParam "test"+                  heistLocal (bindString "t" (maybe "Nothing" TE.decodeUtf8 t)) $ render "testform"+    +    site = route [ ("/test",      heistLocal (bindSplices heistAsyncSplices) $ render "test")+                 , ("/ajax/a",    heistLocal (bindSplices heistAsyncSplices) $ render "testa")+                 , ("/ajax/form", heistLocal (bindSplices heistAsyncSplices) $ testForm)+                 ]
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ heist-async.cabal view
@@ -0,0 +1,48 @@+Name:                heist-async+Version:             0.3.0.0+Synopsis:            Adding support for asynchronous updates ("AJAX") with heist+Description:         This package provides six splices and some accompanying javascript to allow declarative ajax programming that involves no javascript programming.+Homepage:            http://github.com/dbp/heist-async+License:             BSD3+License-file:        LICENSE+Author:              Daniel Patterson+Maintainer:          dbp@riseup.net+-- Copyright:           ++Category:            Web++Build-type:          Simple++-- Extra files to be distributed with the package, such as examples or+-- a README.+Extra-source-files: README.md,+                    js/heist-async.js,+                    js/heist-async.min.js,+                    js/valentine.js,+                    js/valentine.min.js,+                    js/reqwest.js,+                    js/reqwest.min.js,+                    js/qwery.js,+                    js/qwery.min.js+                     +-- Constraint on the version of Cabal needed to build this package.+Cabal-version:       >=1.2+++Library+  -- Modules exported by the library.+  Exposed-modules: Heist.Splices.Async, Heist.Splices.Async.TH+    +  Build-depends:+    base >= 4 && <= 5,+    xmlhtml >= 0.1 && <= 0.2,+    heist >= 0.5 && <= 0.6,+    text >= 0.11 && <= 0.12,+    template-haskell+  +  -- Modules not exported by this package.+  -- Other-modules:       +  +  -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+  -- Build-tools:         +  
+ js/heist-async.js view
@@ -0,0 +1,101 @@+!function() {+  // this code based on facebook's Primer, provided to world at https://gist.github.com/376039+  var doc     = document,+      htm     = doc.documentElement,+      lct     = null,   // last click target                                              +      nearest = function(elm, tag) {+        while (elm && elm.nodeName != tag) {+          elm = elm.parentNode;+        }+        return elm;+  };++  var replace_splices = function (resp) {+    var div = document.createElement('div');+    div.innerHTML = resp;+    var elements = div.childNodes;+    for (i=0; i<elements.length; i++) {+      if (elements[i].nodeName == "DIV" && elements[i].hasAttribute("data-splice-name")) {+        var rep = qwery("div[data-splice-name=" + elements[i].getAttribute("data-splice-name") + "]");+        if (rep && rep[0]) {+          rep[0].parentNode.replaceChild(elements[i],rep[0]);+        }+      } else if (elements[i].nodeName == "DIV" && elements[i].hasAttribute("data-append-name")) {+        var rep = qwery("div[data-append-name=" + elements[i].getAttribute("data-append-name") + "]");+        if (rep && rep[0] && elements[i].childNodes) {+          for(n=0; n < elements[i].childNodes.length; n++) {+            if (elements[i].childNodes[n].nodeType == 1) {+              rep[0].appendChild(elements[i].childNodes[n]);+            }+          }+        }+      } else if (elements[i].nodeName == "DIV" && elements[i].hasAttribute("data-redirect-url")) {+        // this is a redirect, so do it!+        document.location.href = elements[i].getAttribute("data-redirect-url");+        return;+      }+    };+    // now run any included javascript+    var scripts, scriptsFinder=/<script[^>]*>([\s\S]+)<\/script>/gi;+    while(scripts=scriptsFinder.exec(resp))+    {+       eval(scripts[1]);+    }+  };++  // Listeners for most common interations                                            +  htm.onclick = function(e) {+    e = e || window.event;+    lct = e.target || e.srcElement;++    if (lct && lct.nodeName === "BUTTON") {+      bonzo(lct).addClass("processing");+      return;+    }++    var elem = nearest(lct, 'A') || htm,+        href = elem.href;++    switch (elem.rel) {+    case 'async':+    case 'async-post':+      if (elem.getAttribute("data-loading-div")) {+        if (qwery(elem.getAttribute("data-loading-div"))[0]) {+          qwery(elem.getAttribute("data-loading-div"))[0].innerHTML = "<div class='loading'></div>";+        }+      };+      reqwest({+        url:href, +        type: 'html',+        success: replace_splices+      });+      break;+    default:+      return;+    }++    return false;+  };++  htm.onsubmit = function(e) {+    e = e || window.event;+    var elem = e.target || e.srcElement;++    if (!elem || elem.nodeName !== 'FORM' || !elem.getAttribute('data-async')) {+      return;+    }+    if (elem.getAttribute("data-loading-div")) {+      if (qwery(elem.getAttribute("data-loading-div"))[0]) {+        qwery(elem.getAttribute("data-loading-div"))[0].innerHTML = "<div class='loading'></div>";+      }+    }+    reqwest({+          url: elem.getAttribute('action') || "",+          data: reqwest.serialize(elem),+          method: elem.getAttribute('method') || 'post',+          type: 'html',+          success: replace_splices+        });+    return false;+  };+}();
+ js/heist-async.min.js view
@@ -0,0 +1,1 @@+!function(){var doc=document,htm=doc.documentElement,lct=null,nearest=function(a,b){while(a&&a.nodeName!=b)a=a.parentNode;return a},replace_splices=function(resp){var div=document.createElement("div");div.innerHTML=resp;var elements=div.childNodes;for(i=0;i<elements.length;i++)if(elements[i].nodeName=="DIV"&&elements[i].hasAttribute("data-splice-name")){var rep=qwery("div[data-splice-name="+elements[i].getAttribute("data-splice-name")+"]");rep&&rep[0]&&rep[0].parentNode.replaceChild(elements[i],rep[0])}else if(elements[i].nodeName=="DIV"&&elements[i].hasAttribute("data-append-name")){var rep=qwery("div[data-append-name="+elements[i].getAttribute("data-append-name")+"]");if(rep&&rep[0]&&elements[i].childNodes)for(n=0;n<elements[i].childNodes.length;n++)elements[i].childNodes[n].nodeType==1&&rep[0].appendChild(elements[i].childNodes[n])}else if(elements[i].nodeName=="DIV"&&elements[i].hasAttribute("data-redirect-url")){document.location.href=elements[i].getAttribute("data-redirect-url");return}var scripts,scriptsFinder=/<script[^>]*>([\s\S]+)<\/script>/gi;while(scripts=scriptsFinder.exec(resp))eval(scripts[1])};htm.onclick=function(a){a=a||window.event,lct=a.target||a.srcElement;if(lct&&lct.nodeName==="BUTTON")bonzo(lct).addClass("processing");else{var b=nearest(lct,"A")||htm,c=b.href;switch(b.rel){case"async":case"async-post":b.getAttribute("data-loading-div")&&qwery(b.getAttribute("data-loading-div"))[0]&&(qwery(b.getAttribute("data-loading-div"))[0].innerHTML="<div class='loading'></div>"),reqwest({url:c,type:"html",success:replace_splices});break;default:return}return!1}},htm.onsubmit=function(a){a=a||window.event;var b=a.target||a.srcElement;if(!!b&&b.nodeName==="FORM"&&!!b.getAttribute("data-async")){b.getAttribute("data-loading-div")&&qwery(b.getAttribute("data-loading-div"))[0]&&(qwery(b.getAttribute("data-loading-div"))[0].innerHTML="<div class='loading'></div>"),reqwest({url:b.getAttribute("action")||"",data:reqwest.serialize(b),method:b.getAttribute("method")||"post",type:"html",success:replace_splices});return!1}}}()
+ js/qwery.js view
@@ -0,0 +1,257 @@+/*!+  * Qwery - A Blazing Fast query selector engine+  * https://github.com/ded/qwery+  * copyright Dustin Diaz & Jacob Thornton 2011+  * MIT License+  */++!function (context, doc) {++  var c, i, j, k, l, m, o, p, r, v,+      el, node, len, found, classes, item, items, token,+      id = /#([\w\-]+)/,+      clas = /\.[\w\-]+/g,+      idOnly = /^#([\w\-]+$)/,+      classOnly = /^\.([\w\-]+)$/,+      tagOnly = /^([\w\-]+)$/,+      tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/,+      html = doc.documentElement,+      tokenizr = /\s(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\])/,+      specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g,+      simple = /^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,+      attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,+      chunker = new RegExp(simple.source + '(' + attr.source + ')?');++  function array(ar) {+    r = [];+    for (i = 0, len = ar.length; i < len; i++) {+      r[i] = ar[i];+    }+    return r;+  }++  var cache = function () {+    this.c = {};+  };+  cache.prototype = {+    g: function (k) {+      return this.c[k] || undefined;+    },+    s: function (k, v) {+      this.c[k] = v;+      return v;+    }+  };++  var classCache = new cache(),+      cleanCache = new cache(),+      attrCache = new cache(),+      tokenCache = new cache();++  function q(query) {+    return query.match(chunker);+  }++  function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value) {+    var m, c, k;+    if (tag && this.tagName.toLowerCase() !== tag) {+      return false;+    }+    if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) {+      return false;+    }+    if (idsAndClasses && (classes = idsAndClasses.match(clas))) {+      for (i = classes.length; i--;) {+        c = classes[i].slice(1);+        if (!(classCache.g(c) || classCache.s(c, new RegExp('(^|\\s+)' + c + '(\\s+|$)'))).test(this.className)) {+          return false;+        }+      }+    }+    if (wholeAttribute && !value) {+      o = this.attributes;+      for (k in o) {+        if (Object.prototype.hasOwnProperty.call(o, k) && (o[k].name || k) == attribute) {+          return this;+        }+      }+    }+    if (wholeAttribute && !checkAttr(qualifier, this.getAttribute(attribute) || '', value)) {+      return false;+    }+    return this;+  }++  function loopAll(tokens) {+    var r = [], token = tokens.pop(), intr = q(token), tag = intr[1] || '*', i, l, els,+        root = tokens.length && (m = tokens[0].match(idOnly)) ? doc.getElementById(m[1]) : doc;+    if (!root) {+      return r;+    }+    els = root.getElementsByTagName(tag);+    for (i = 0, l = els.length; i < l; i++) {+      el = els[i];+      if (item = interpret.apply(el, intr)) {+        r.push(item);+      }+    }+    return r;+  }++  function clean(s) {+    return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1'));+  }++  function checkAttr(qualify, actual, val) {+    switch (qualify) {+    case '=':+      return actual == val;+    case '^=':+      return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, new RegExp('^' + clean(val))));+    case '$=':+      return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, new RegExp(clean(val) + '$')));+    case '*=':+      return actual.match(attrCache.g(val) || attrCache.s(val, new RegExp(clean(val))));+    case '~=':+      return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, new RegExp('(?:^|\\s+)' + clean(val) + '(?:\\s+|$)')));+    case '|=':+      return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, new RegExp('^' + clean(val) + '(-|$)')));+    }+    return false;+  }++  function _qwery(selector) {+    var r = [], ret = [], i, l,+        tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr));+    tokens = tokens.slice(0);+    if (!tokens.length) {+      return r;+    }+    r = loopAll(tokens);+    if (!tokens.length) {+      return r;+    }+    // loop through all descendent tokens+    for (j = 0, l = r.length, k = 0; j < l; j++) {+      node = r[j];+      p = node;+      // loop through each token+      for (i = tokens.length; i--;) {+        z: // loop through parent nodes+        while (p !== html && (p = p.parentNode)) {+          if (found = interpret.apply(p, q(tokens[i]))) {+            break z;+          }+        }+      }+      found && (ret[k++] = node);+    }+    return ret;+  }++  function boilerPlate(selector, _root, fn) {+    var root = (typeof _root == 'string') ? fn(_root)[0] : (_root || doc);+    if (selector === window || isNode(selector)) {+      return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : [];+    }+    if (selector && typeof selector === 'object' && isFinite(selector.length)) {+      return array(selector);+    }+    if (m = selector.match(idOnly)) {+      return (el = doc.getElementById(m[1])) ? [el] : [];+    }+    if (m = selector.match(tagOnly)) {+      return array(root.getElementsByTagName(m[1]));+    }+    return false;+  }++  function isNode(el) {+    return (el && el.nodeType && (el.nodeType == 1 || el.nodeType == 9));+  }++  function uniq(ar) {+    var a = [], i, j;+    label:+    for (i = 0; i < ar.length; i++) {+      for (j = 0; j < a.length; j++) {+        if (a[j] == ar[i]) {+          continue label;+        }+      }+      a[a.length] = ar[i];+    }+    return a;+  }++  function qwery(selector, _root) {+    var root = (typeof _root == 'string') ? qwery(_root)[0] : (_root || doc);+    if (!root || !selector) {+      return [];+    }+    if (m = boilerPlate(selector, _root, qwery)) {+      return m;+    }+    return select(selector, root);+  }++  var isAncestor = 'compareDocumentPosition' in html ?+    function (element, container) {+      return (container.compareDocumentPosition(element) & 16) == 16;+    } : 'contains' in html ?+    function (element, container) {+      container = container == doc || container == window ? html : container;+      return container !== element && container.contains(element);+    } :+    function (element, container) {+      while (element = element.parentNode) {+        if (element === container) {+          return 1;+        }+      }+      return 0;+    },++  select = (doc.querySelector && doc.querySelectorAll) ?+    function (selector, root) {+      if (doc.getElementsByClassName && (m = selector.match(classOnly))) {+        return array((root).getElementsByClassName(m[1]));+      }+      return array((root).querySelectorAll(selector));+    } :+    function (selector, root) {+      var result = [], collection, collections = [], i;+      if (m = selector.match(tagAndOrClass)) {+        items = root.getElementsByTagName(m[1] || '*');+        r = classCache.g(m[2]) || classCache.s(m[2], new RegExp('(^|\\s+)' + m[2] + '(\\s+|$)'));+        for (i = 0, l = items.length, j = 0; i < l; i++) {+          r.test(items[i].className) && (result[j++] = items[i]);+        }+        return result;+      }+      for (i = 0, items = selector.split(','), l = items.length; i < l; i++) {+        collections[i] = _qwery(items[i]);+      }+      for (i = 0, l = collections.length; i < l && (collection = collections[i]); i++) {+        var ret = collection;+        if (root !== doc) {+          ret = [];+          for (j = 0, m = collection.length; j < m && (element = collection[j]); j++) {+            // make sure element is a descendent of root+            isAncestor(element, root) && ret.push(element);+          }+        }+        result = result.concat(ret);+      }+      return uniq(result);+    };++  qwery.uniq = uniq;+  var oldQwery = context.qwery;+  qwery.noConflict = function () {+    context.qwery = oldQwery;+    return this;+  };+  context['qwery'] = qwery;++}(this, document);
+ js/qwery.min.js view
@@ -0,0 +1,6 @@+/*!+  * Qwery - A Blazing Fast query selector engine+  * https://github.com/ded/qwery+  * copyright Dustin Diaz & Jacob Thornton 2011+  * MIT License+  */!function(a,b){function V(a,c){var d=typeof c=="string"?V(c)[0]:c||b;if(!d||!a)return[];if(h=S(a,c,V))return h;return X(a,d)}function U(a){var b=[],c,d;label:for(c=0;c<a.length;c++){for(d=0;d<b.length;d++)if(b[d]==a[c])continue label;b[b.length]=a[c]}return b}function T(a){return a&&a.nodeType&&(a.nodeType==1||a.nodeType==9)}function S(a,c,d){var e=typeof c=="string"?d(c)[0]:c||b;if(a===window||T(a))return!c||a!==window&&T(e)&&W(a,e)?[a]:[];if(a&&typeof a=="object"&&isFinite(a.length))return G(a);if(h=a.match(w))return(m=b.getElementById(h[1]))?[m]:[];if(h=a.match(y))return G(e.getElementsByTagName(h[1]));return!1}function R(a){var b=[],c=[],d,g,h=L.g(a)||L.s(a,a.split(B));h=h.slice(0);if(!h.length)return b;b=O(h);if(!h.length)return b;for(e=0,g=b.length,f=0;e<g;e++){n=b[e],j=n;for(d=h.length;d--;)z:while(j!==A&&(j=j.parentNode))if(p=N.apply(j,M(h[d])))break z;p&&(c[f++]=n)}return c}function Q(a,b,c){switch(a){case"=":return b==c;case"^=":return b.match(K.g("^="+c)||K.s("^="+c,new RegExp("^"+P(c))));case"$=":return b.match(K.g("$="+c)||K.s("$="+c,new RegExp(P(c)+"$")));case"*=":return b.match(K.g(c)||K.s(c,new RegExp(P(c))));case"~=":return b.match(K.g("~="+c)||K.s("~="+c,new RegExp("(?:^|\\s+)"+P(c)+"(?:\\s+|$)")));case"|=":return b.match(K.g("|="+c)||K.s("|="+c,new RegExp("^"+P(c)+"(-|$)")))}return!1}function P(a){return J.g(a)||J.s(a,a.replace(C,"\\$1"))}function O(a){var c=[],d=a.pop(),e=M(d),f=e[1]||"*",g,i,j,k=a.length&&(h=a[0].match(w))?b.getElementById(h[1]):b;if(!k)return c;j=k.getElementsByTagName(f);for(g=0,i=j.length;g<i;g++)m=j[g],(r=N.apply(m,e))&&c.push(r);return c}function N(a,b,c,e,f,g,h){var j,k,l;if(b&&this.tagName.toLowerCase()!==b)return!1;if(c&&(j=c.match(u))&&j[1]!==this.id)return!1;if(c&&(q=c.match(v)))for(d=q.length;d--;){k=q[d].slice(1);if(!(I.g(k)||I.s(k,new RegExp("(^|\\s+)"+k+"(\\s+|$)"))).test(this.className))return!1}if(e&&!h){i=this.attributes;for(l in i)if(Object.prototype.hasOwnProperty.call(i,l)&&(i[l].name||l)==f)return this}if(e&&!Q(g,this.getAttribute(f)||"",h))return!1;return this}function M(a){return a.match(F)}function G(a){k=[];for(d=0,o=a.length;d<o;d++)k[d]=a[d];return k}var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u=/#([\w\-]+)/,v=/\.[\w\-]+/g,w=/^#([\w\-]+$)/,x=/^\.([\w\-]+)$/,y=/^([\w\-]+)$/,z=/^([\w]+)?\.([\w\-]+)$/,A=b.documentElement,B=/\s(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\])/,C=/([.*+?\^=!:${}()|\[\]\/\\])/g,D=/^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,E=/\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,F=new RegExp(D.source+"("+E.source+")?"),H=function(){this.c={}};H.prototype={g:function(a){return this.c[a]||undefined},s:function(a,b){this.c[a]=b;return b}};var I=new H,J=new H,K=new H,L=new H,W="compareDocumentPosition"in A?function(a,b){return(b.compareDocumentPosition(a)&16)==16}:"contains"in A?function(a,c){c=c==b||c==window?A:c;return c!==a&&c.contains(a)}:function(a,b){while(a=a.parentNode)if(a===b)return 1;return 0},X=b.querySelector&&b.querySelectorAll?function(a,c){if(b.getElementsByClassName&&(h=a.match(x)))return G(c.getElementsByClassName(h[1]));return G(c.querySelectorAll(a))}:function(a,c){var d=[],f,i=[],j;if(h=a.match(z)){s=c.getElementsByTagName(h[1]||"*"),k=I.g(h[2])||I.s(h[2],new RegExp("(^|\\s+)"+h[2]+"(\\s+|$)"));for(j=0,g=s.length,e=0;j<g;j++)k.test(s[j].className)&&(d[e++]=s[j]);return d}for(j=0,s=a.split(","),g=s.length;j<g;j++)i[j]=R(s[j]);for(j=0,g=i.length;j<g&&(f=i[j]);j++){var l=f;if(c!==b){l=[];for(e=0,h=f.length;e<h&&(element=f[e]);e++)W(element,c)&&l.push(element)}d=d.concat(l)}return U(d)};V.uniq=U;var Y=a.qwery;V.noConflict=function(){a.qwery=Y;return this},a.qwery=V}(this,document)
+ js/reqwest.js view
@@ -0,0 +1,256 @@+/*!+  * Reqwest! A x-browser general purpose XHR connection manager+  * copyright Dustin Diaz 2011+  * https://github.com/ded/reqwest+  * license MIT+  */+!function (window) {+  var twoHundo = /^20\d$/,+      doc = document,+      byTag = 'getElementsByTagName',+      head = doc[byTag]('head')[0],+      xhr = ('XMLHttpRequest' in window) ?+        function () {+          return new XMLHttpRequest();+        } :+        function () {+          return new ActiveXObject('Microsoft.XMLHTTP');+        };++  var uniqid = 0;+  // data stored by the most recent JSONP callback+  var lastValue;++  function readyState(o, success, error) {+    return function () {+      if (o && o.readyState == 4) {+        if (twoHundo.test(o.status)) {+          success(o);+        } else {+          error(o);+        }+      }+    };+  }++  function setHeaders(http, options) {+    var headers = options.headers || {};+    headers.Accept = headers.Accept || 'text/javascript, text/html, application/xml, text/xml, */*';+    headers['X-Requested-With'] = headers['X-Requested-With'] || 'XMLHttpRequest';+    if (options.data) {+      headers['Content-type'] = headers['Content-type'] || 'application/x-www-form-urlencoded';+      for (var h in headers) {+        headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h], false);+      }+    }+  }++  function getCallbackName(o) {+    var callbackVar = o.jsonpCallback || "callback";+    if (o.url.slice(-(callbackVar.length + 2)) == (callbackVar + "=?")) {+      // Generate a guaranteed unique callback name+      var callbackName = "reqwest_" + uniqid++;++      // Replace the ? in the URL with the generated name+      o.url = o.url.substr(0, o.url.length - 1) + callbackName;+      return callbackName;+    } else {+      // Find the supplied callback name+      var regex = new RegExp(callbackVar + "=([\\w]+)");+      return o.url.match(regex)[1];+    }+  }++  // Store the data returned by the most recent callback+  function generalCallback(data) {+    lastValue = data;+  }++  function getRequest(o, fn, err) {+    if (o.type == 'jsonp') {+      var script = doc.createElement('script');++      // Add the global callback+      window[getCallbackName(o)] = generalCallback;++      // Setup our script element+      script.type = "text/javascript";+      script.src = o.url;+      script.async = true;++      var onload = function () {+        // Call the user callback with the last value stored+        // and clean up values and scripts.+        o.success && o.success(lastValue);+        lastValue = undefined;+        head.removeChild(script);+      };++      script.onload = onload;+      // onload for IE+      script.onreadystatechange = function () {+        /^loaded|complete$/.test(script.readyState) && onload();+      };++      // Add the script to the DOM head+      head.appendChild(script);+    } else {+      var http = xhr();+      http.open(o.method || 'GET', typeof o == 'string' ? o : o.url, true);+      setHeaders(http, o);+      http.onreadystatechange = readyState(http, fn, err);+      o.before && o.before(http);+      http.send(o.data || null);+      return http;+    }+  }++  function Reqwest(o, fn) {+    this.o = o;+    this.fn = fn;+    init.apply(this, arguments);+  }++  function setType(url) {+    if (/\.json$/.test(url)) {+      return 'json';+    }+    if (/\.jsonp$/.test(url)) {+      return 'jsonp';+    }+    if (/\.js$/.test(url)) {+      return 'js';+    }+    if (/\.html?$/.test(url)) {+      return 'html';+    }+    if (/\.xml$/.test(url)) {+      return 'xml';+    }+    return 'js';+  }++  function init(o, fn) {+    this.url = typeof o == 'string' ? o : o.url;+    this.timeout = null;+    var type = o.type || setType(this.url), self = this;+    fn = fn || function () {};++    if (o.timeout) {+      this.timeout = setTimeout(function () {+        self.abort();+        error();+      }, o.timeout);+    }++    function complete(resp) {+      o.complete && o.complete(resp);+    }++    function success(resp) {+      o.timeout && clearTimeout(self.timeout) && (self.timeout = null);+      var r = resp.responseText, JSON;++      switch (type) {+      case 'json':+        resp = JSON ? JSON.parse(r) : eval('(' + r + ')');+        break;+      case 'js':+        resp = eval(r);+        break;+      case 'html':+        resp = r;+        break;+      // default is the response from server+      }++      fn(resp);+      o.success && o.success(resp);+      complete(resp);+    }++    function error(resp) {+      o.error && o.error(resp);+      complete(resp);+    }++    this.request = getRequest(o, success, error);+  }++  Reqwest.prototype = {+    abort: function () {+      this.request.abort();+    },++    retry: function () {+      init.call(this, this.o, this.fn);+    }+  };++  function reqwest(o, fn) {+    return new Reqwest(o, fn);+  }++  function enc(v) {+    return encodeURIComponent(v);+  }++  function serial(el) {+    var n = el.name;+    // don't serialize elements that are disabled or without a name+    if (el.disabled || !n) {+      return '';+    }+    n = enc(n);+    switch (el.tagName.toLowerCase()) {+    case 'input':+      switch (el.type) {+      // silly wabbit+      case 'reset':+      case 'button':+      case 'image':+      case 'file':+        return '';+      case 'checkbox':+      case 'radio':+        return el.checked ? n + '=' + (el.value ? enc(el.value) : true) + '&' : '';+      default: // text hidden password submit+        return n + '=' + (el.value ? enc(el.value) : '') + '&';+      }+      break;+    case 'textarea':+      return n + '=' + enc(el.value) + '&';+    case 'select':+      // @todo refactor beyond basic single selected value case+      return n + '=' + enc(el.options[el.selectedIndex].value) + '&';+    }+    return '';+  }++  reqwest.serialize = function (form) {+    var inputs = form[byTag]('input'),+        selects = form[byTag]('select'),+        texts = form[byTag]('textarea');+    return (v(inputs).chain().toArray().map(serial).value().join('') ++    v(selects).chain().toArray().map(serial).value().join('') ++    v(texts).chain().toArray().map(serial).value().join('')).replace(/&$/, '');+  };++  reqwest.serializeArray = function (f) {+    for (var pairs = this.serialize(f).split('&'), i = 0, l = pairs.length, r = [], o; i < l; i++) {+      pairs[i] && (o = pairs[i].split('=')) && r.push({name: o[0], value: o[1]});+    }+    return r;+  };++  var old = window.reqwest;+  reqwest.noConflict = function () {+    window.reqwest = old;+    return this;+  };++  // defined as extern for Closure Compilation+  // do not change to (dot) '.' syntax+  window['reqwest'] = reqwest;++}(this);
+ js/reqwest.min.js view
@@ -0,0 +1,6 @@+/*!+  * Reqwest! A x-browser general purpose XHR connection manager+  * copyright Dustin Diaz 2011+  * https://github.com/ded/reqwest+  * license MIT+  */!function(window){function serial(a){var b=a.name;if(a.disabled||!b)return"";b=enc(b);switch(a.tagName.toLowerCase()){case"input":switch(a.type){case"reset":case"button":case"image":case"file":return"";case"checkbox":case"radio":return a.checked?b+"="+(a.value?enc(a.value):!0)+"&":"";default:return b+"="+(a.value?enc(a.value):"")+"&"}break;case"textarea":return b+"="+enc(a.value)+"&";case"select":return b+"="+enc(a.options[a.selectedIndex].value)+"&"}return""}function enc(a){return encodeURIComponent(a)}function reqwest(a,b){return new Reqwest(a,b)}function init(o,fn){function error(a){o.error&&o.error(a),complete(a)}function success(resp){o.timeout&&clearTimeout(self.timeout)&&(self.timeout=null);var r=resp.responseText;switch(type){case"json":resp=eval("("+r+")");break;case"js":resp=eval(r);break;case"html":resp=r}fn(resp),o.success&&o.success(resp),complete(resp)}function complete(a){o.complete&&o.complete(a)}this.url=typeof o=="string"?o:o.url,this.timeout=null;var type=o.type||setType(this.url),self=this;fn=fn||function(){},o.timeout&&(this.timeout=setTimeout(function(){self.abort(),error()},o.timeout)),this.request=getRequest(o,success,error)}function setType(a){if(/\.json$/.test(a))return"json";if(/\.jsonp$/.test(a))return"jsonp";if(/\.js$/.test(a))return"js";if(/\.html?$/.test(a))return"html";if(/\.xml$/.test(a))return"xml";return"js"}function Reqwest(a,b){this.o=a,this.fn=b,init.apply(this,arguments)}function getRequest(a,b,c){if(a.type!="jsonp"){var f=xhr();f.open(a.method||"GET",typeof a=="string"?a:a.url,!0),setHeaders(f,a),f.onreadystatechange=readyState(f,b,c),a.before&&a.before(f),f.send(a.data||null);return f}var d=doc.createElement("script");window[getCallbackName(a)]=generalCallback,d.type="text/javascript",d.src=a.url,d.async=!0;var e=function(){a.success&&a.success(lastValue),lastValue=undefined,head.removeChild(d)};d.onload=e,d.onreadystatechange=function(){d.readyState=="loaded"&&e()},head.appendChild(d)}function generalCallback(a){lastValue=a}function getCallbackName(a){var b=a.jsonpCallback||"callback";if(a.url.slice(-(b.length+2))==b+"=?"){var c="reqwest_"+uniqid++;a.url=a.url.substr(0,a.url.length-1)+c;return c}var d=new RegExp(b+"=([\\w]+)");return a.url.match(d)[1]}function setHeaders(a,b){var c=b.headers||{};c.Accept="text/javascript, text/html, application/xml, text/xml, */*",c["X-Requested-With"]=c["X-Requested-With"]||"XMLHttpRequest";if(b.data){c["Content-type"]="application/x-www-form-urlencoded";for(var d in c)c.hasOwnProperty(d)&&a.setRequestHeader(d,c[d],!1)}}function readyState(a,b,c){return function(){a&&a.readyState==4&&(twoHundo.test(a.status)?b(a):c(a))}}var twoHundo=/^20\d$/,doc=document,byTag="getElementsByTagName",head=doc[byTag]("head")[0],xhr="XMLHttpRequest"in window?function(){return new XMLHttpRequest}:function(){return new ActiveXObject("Microsoft.XMLHTTP")},uniqid=0,lastValue;Reqwest.prototype={abort:function(){this.request.abort()},retry:function(){init.call(this,this.o,this.fn)}},reqwest.serialize=function(a){var b=a[byTag]("input"),c=a[byTag]("select"),d=a[byTag]("textarea");return(v(b).chain().toArray().map(serial).value().join("")+v(c).chain().toArray().map(serial).value().join("")+v(d).chain().toArray().map(serial).value().join("")).replace(/&$/,"")},reqwest.serializeArray=function(a){for(var b=this.serialize(a).split("&"),c=0,d=b.length,e=[],f;c<d;c++)b[c]&&(f=b[c].split("="))&&e.push({name:f[0],value:f[1]});return e};var old=window.reqwest;reqwest.noConflict=function(){window.reqwest=old;return this},window.reqwest=reqwest}(this)
+ js/valentine.js view
@@ -0,0 +1,439 @@+/*!+  * Valentine: JavaScript's Sister+  * copyright Dustin Diaz 2011 (@ded)+  * https://github.com/ded/valentine+  * License MIT+  */++!function (context) {++  var v = function (a, scope) {+        return new Valentine(a, scope);+      },+      ap = [],+      op = {},+      slice = ap.slice,+      nativ = 'map' in ap,+      nativ18 = 'reduce' in ap,+      trimReplace = /(^\s*|\s*$)/g;++  var iters = {+    each: nativ ?+      function (a, fn, scope) {+        ap.forEach.call(a, fn, scope);+      } :+      function (a, fn, scope) {+        for (var i = 0, l = a.length; i < l; i++) {+          i in a && fn.call(scope, a[i], i, a);+        }+      },+    map: nativ ?+      function (a, fn, scope) {+        return ap.map.call(a, fn, scope);+      } :+      function (a, fn, scope) {+        var r = [], i;+        for (i = 0, l = a.length; i < l; i++) {+          i in a && (r[i] = fn.call(scope, a[i], i, a));+        }+        return r;+      },+    some: nativ ?+      function (a, fn, scope) {+        return a.some(fn, scope);+      } :+      function (a, fn, scope) {+        for (var i = 0, l = a.length; i < l; i++) {+          if (i in a && fn.call(scope, a[i], i, a)) {+            return true;+          }+        }+        return false;+      },+    every: nativ ?+      function (a, fn, scope) {+        return a.every(fn, scope);+      } :+      function (a, fn, scope) {+        for (var i = 0, l = a.length; i < l; i++) {+          if (i in a && !fn.call(scope, a[i], i, a)) {+            return false;+          }+        }+        return true;+      },+    filter: nativ ?+      function (a, fn, scope) {+        return a.filter(fn, scope);+      } :+      function (a, fn, scope) {+        var r = [];+        for (var i = 0, j = 0, l = a.length; i < l; i++) {+          if (i in a) {+            if (!fn.call(scope, a[i], i, a)) {+              continue;+            }+            r[j++] = a[i];+          }+        }+        return r;+      },+    indexOf: nativ ?+      function (a, el, start) {+        return a.indexOf(el, isFinite(start) ? start : 0);+      } :+      function (a, el, start) {+        start = start || 0;+        for (var i = 0; i < a.length; i++) {+          if (i in a && a[i] === el) {+            return i;+          }+        }+        return -1;+      },++    lastIndexOf: nativ ?+      function (a, el, start) {+        return a.lastIndexOf(el, isFinite(start) ? start : a.length);+      } :+      function (a, el, start) {+        start = start || a.length;+        start = start >= a.length ? a.length :+          start < 0 ? a.length + start : start;+        for (var i = start; i >= 0; --i) {+          if (i in a && a[i] === el) {+            return i;+          }+        }+        return -1;+      },++    reduce: nativ18 ?+      function (o, i, m, c) {+        return ap.reduce.call(o, i, m, c);+      } :+      function (obj, iterator, memo, context) {+        !obj && (obj = []);+        var i = 0, l = obj.length;+        if (arguments.length < 3) {+          do {+            if (i in obj) {+              memo = obj[i++];+              break;+            }+            if (++i >= l) {+              throw new TypeError('Empty array');+            }+          } while (1);+        }+        for (; i < l; i++) {+          if (i in obj) {+            memo = iterator.call(context, memo, obj[i], i, obj);+          }+        }+        return memo;+      },++    reduceRight: nativ18 ?+      function (o, i, m, c) {+        return ap.reduceRight.call(o, i, m, c);+      } :+      function (obj, iterator, memo, context) {+        !obj && (obj = []);+        var l = obj.length, i = l - 1;+        if (arguments.length < 3) {+          do {+            if (i in obj) {+              memo = obj[i--];+              break;+            }+            if (--i < 0) {+              throw new TypeError('Empty array');+            }+          } while (1);+        }+        for (; i >= 0; i--) {+          if (i in obj) {+            memo = iterator.call(context, memo, obj[i], i, obj);+          }+        }+        return memo;+      },++    find: function (obj, iterator, context) {+      var result;+      iters.some(obj, function (value, index, list) {+        if (iterator.call(context, value, index, list)) {+          result = value;+          return true;+        }+      });+      return result;+    },++    reject: function (a, fn, scope) {+      var r = [];+      for (var i = 0, j = 0, l = a.length; i < l; i++) {+        if (i in a) {+          if (fn.call(scope, a[i], i, a)) {+            continue;+          }+          r[j++] = a[i];+        }+      }+      return r;+    },++    size: function (a) {+      return o.toArray(a).length;+    },++    pluck: function (a, k) {+      return iters.map(a, function (el) {+        return el[k];+      });+    },++    compact: function (a) {+      return iters.filter(a, function (value) {+        return !!value;+      });+    },++    flatten: function (a) {+      return iters.reduce(a, function (memo, value) {+        if (is.arr(value)) {+          return memo.concat(iters.flatten(value));+        }+        memo[memo.length] = value;+        return memo;+      }, []);+    },++    uniq: function (ar) {+      var a = [], i, j;+      label:+      for (i = 0; i < ar.length; i++) {+        for (j = 0; j < a.length; j++) {+          if (a[j] == ar[i]) {+            continue label;+          }+        }+        a[a.length] = ar[i];+      }+      return a;+    }++  };++  function aug(o, o2) {+    for (var k in o2) {+      o[k] = o2[k];+    }+  }++  var is = {+    fun: function (f) {+      return typeof f === 'function';+    },++    str: function (s) {+      return typeof s === 'string';+    },++    ele: function (el) {+      !!(el && el.nodeType && el.nodeType == 1);+    },++    arr: function (ar) {+      return ar instanceof Array;+    },++    arrLike: function (ar) {+      return (ar && ar.length && isFinite(ar.length));+    },++    num: function (n) {+      return typeof n === 'number';+    },++    bool: function (b) {+      return (b === true) || (b === false);+    },++    args: function (a) {+      return !!(a && op.hasOwnProperty.call(a, 'callee'));+    },++    emp: function (o) {+      var i = 0;+      return is.arr(o) ? o.length === 0 :+        is.obj(o) ? (function () {+          for (var k in o) {+            i++;+            break;+          }+          return (i === 0);+        }()) :+        o === '';+    },++    dat: function (d) {+      return !!(d && d.getTimezoneOffset && d.setUTCFullYear);+    },++    reg: function (r) {+      return !!(r && r.test && r.exec && (r.ignoreCase || r.ignoreCase === false));+    },++    nan: function (n) {+      return n !== n;+    },++    nil: function (o) {+      return o === null;+    },++    und: function (o) {+      return typeof o === 'undefined';+    },++    obj: function (o) {+      return o instanceof Object && !is.fun(o) && !is.arr(o);+    }+  };++  var o = {+    each: function (a, fn, scope) {+      is.arrLike(a) ?+        iters.each(a, fn, scope) : (function () {+          for (var k in a) {+            op.hasOwnProperty.call(a, k) && fn.call(scope, k, a[k], a);+          }+        }());+    },++    map: function (a, fn, scope) {+      var r = [], i = 0;+      return is.arrLike(a) ?+        iters.map(a, fn, scope) : !function () {+          for (var k in a) {+            op.hasOwnProperty.call(a, k) && (r[i++] = fn.call(scope, k, a[k], a));+          }+        }() && r;+    },++    toArray: function (a) {+      if (!a) {+        return [];+      }+      if (a.toArray) {+        return a.toArray();+      }+      if (is.arr(a)) {+        return a;+      }+      if (is.args(a)) {+        return slice.call(a);+      }+      return iters.map(a, function (k) {+        return k;+      });+    },++    first: function (a) {+      return a[0];+    },++    last: function (a) {+      return a[a.length - 1];+    },++    keys: Object.keys ?+      function (o) {+        return Object.keys(o);+      } :+      function (obj) {+        var keys = [];+        for (var key in obj) {+          op.hasOwnProperty.call(obj, key) && (keys[keys.length] = key);+        }+        return keys;+      },++    values: function (ob) {+      return o.map(ob, function (k, v) {+        return v;+      });+    },++    extend: function (ob) {+      o.each(slice.call(arguments, 1), function (source) {+        for (var prop in source) {+          !is.und(source[prop]) && (ob[prop] = source[prop]);+        }+      });+      return ob;+    },++    trim: String.prototype.trim ?+      function (s) {+        return s.trim();+      } :+      function (s) {+        return s.replace(trimReplace, '');+      },++    bind: function (scope, fn) {+      return function () {+        fn.apply(scope, arguments);+      };+    }++  };++  aug(v, iters);+  aug(v, o);+  v.is = is;++  // love thyself+  v.v = v;++  // peoples like the object style+  var Valentine = function (a, scope) {+    this.val = a;+    this._scope = scope || null;+    this._chained = 0;+  };++  v.each(v.extend({}, iters, o), function (name, fn) {+    Valentine.prototype[name] = function () {+      var a = v.toArray(arguments);+      a.unshift(this.val);+      var ret = fn.apply(this._scope, a);+      this.val = ret;+      return this._chained ? this : ret;+    };+  });++  // back compact to underscore (peoples like chaining)+  Valentine.prototype.chain = function () {+    this._chained = 1;+    return this;+  };++  Valentine.prototype.value = function () {+    return this.val;+  };++  var old = context.v;+  v.noConflict = function () {+    context.v = old;+    return this;+  };++  (typeof module !== 'undefined') && module.exports ?+    (module.exports = v) :+    (context['v'] = v);++}(this);
+ js/valentine.min.js view
@@ -0,0 +1,6 @@+/*!+  * Valentine: JavaScript's Sister+  * copyright Dustin Diaz 2011 (@ded)+  * https://github.com/ded/valentine+  * License MIT+  */!function(a){function j(a,b){for(var c in b)a[c]=b[c]}var b=function(a,b){return new n(a,b)},c=[],d={},e=c.slice,f="map"in c,g="reduce"in c,h=/(^\s*|\s*$)/g,i={each:f?function(a,b,d){c.forEach.call(a,b,d)}:function(a,b,c){for(var d=0,e=a.length;d<e;d++)d in a&&b.call(c,a[d],d,a)},map:f?function(a,b,d){return c.map.call(a,b,d)}:function(a,b,c){var d=[],e;for(e=0,l=a.length;e<l;e++)e in a&&(d[e]=b.call(c,a[e],e,a));return d},some:f?function(a,b,c){return a.some(b,c)}:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(d in a&&b.call(c,a[d],d,a))return!0;return!1},every:f?function(a,b,c){return a.every(b,c)}:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(d in a&&!b.call(c,a[d],d,a))return!1;return!0},filter:f?function(a,b,c){return a.filter(b,c)}:function(a,b,c){var d=[];for(var e=0,f=0,g=a.length;e<g;e++)if(e in a){if(!b.call(c,a[e],e,a))continue;d[f++]=a[e]}return d},indexOf:f?function(a,b,c){return a.indexOf(b,isFinite(c)?c:0)}:function(a,b,c){c=c||0;for(var d=0;d<a.length;d++)if(d in a&&a[d]===b)return d;return-1},lastIndexOf:f?function(a,b,c){return a.lastIndexOf(b,isFinite(c)?c:a.length)}:function(a,b,c){c=c||a.length,c=c>=a.length?a.length:c<0?a.length+c:c;for(var d=c;d>=0;--d)if(d in a&&a[d]===b)return d;return-1},reduce:g?function(a,b,d,e){return c.reduce.call(a,b,d,e)}:function(a,b,c,d){!a&&(a=[]);var e=0,f=a.length;if(arguments.length<3)do{if(e in a){c=a[e++];break}if(++e>=f)throw new TypeError("Empty array")}while(1);for(;e<f;e++)e in a&&(c=b.call(d,c,a[e],e,a));return c},reduceRight:g?function(a,b,d,e){return c.reduceRight.call(a,b,d,e)}:function(a,b,c,d){!a&&(a=[]);var e=a.length,f=e-1;if(arguments.length<3)do{if(f in a){c=a[f--];break}if(--f<0)throw new TypeError("Empty array")}while(1);for(;f>=0;f--)f in a&&(c=b.call(d,c,a[f],f,a));return c},find:function(a,b,c){var d;i.some(a,function(a,e,f){if(b.call(c,a,e,f)){d=a;return!0}});return d},reject:function(a,b,c){var d=[];for(var e=0,f=0,g=a.length;e<g;e++)if(e in a){if(b.call(c,a[e],e,a))continue;d[f++]=a[e]}return d},size:function(a){return m.toArray(a).length},pluck:function(a,b){return i.map(a,function(a){return a[b]})},compact:function(a){return i.filter(a,function(a){return!!a})},flatten:function(a){return i.reduce(a,function(a,b){if(k.arr(b))return a.concat(i.flatten(b));a[a.length]=b;return a},[])},uniq:function(a){var b=[],c,d;label:for(c=0;c<a.length;c++){for(d=0;d<b.length;d++)if(b[d]==a[c])continue label;b[b.length]=a[c]}return b}},k={fun:function(a){return typeof a=="function"},str:function(a){return typeof a=="string"},ele:function(a){!!a&&!!a.nodeType&&a.nodeType==1},arr:function(a){return a instanceof Array},arrLike:function(a){return a&&a.length&&isFinite(a.length)},num:function(a){return typeof a=="number"},bool:function(a){return a===!0||a===!1},args:function(a){return!!a&&!!d.hasOwnProperty.call(a,"callee")},emp:function(a){var b=0;return k.arr(a)?a.length===0:k.obj(a)?function(){for(var c in a){b++;break}return b===0}():a===""},dat:function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)},reg:function(a){return!(!(a&&a.test&&a.exec)||!a.ignoreCase&&a.ignoreCase!==!1)},nan:function(a){return a!==a},nil:function(a){return a===null},und:function(a){return typeof a=="undefined"},obj:function(a){return a instanceof Object&&!k.fun(a)&&!k.arr(a)}},m={each:function(a,b,c){k.arrLike(a)?i.each(a,b,c):function(){for(var e in a)d.hasOwnProperty.call(a,e)&&b.call(c,e,a[e],a)}()},map:function(a,b,c){var e=[],f=0;return k.arrLike(a)?i.map(a,b,c):!function(){for(var g in a)d.hasOwnProperty.call(a,g)&&(e[f++]=b.call(c,g,a[g],a))}()&&e},toArray:function(a){if(!a)return[];if(a.toArray)return a.toArray();if(k.arr(a))return a;if(k.args(a))return e.call(a);return i.map(a,function(a){return a})},first:function(a){return a[0]},last:function(a){return a[a.length-1]},keys:Object.keys?function(a){return Object.keys(a)}:function(a){var b=[];for(var c in a)d.hasOwnProperty.call(a,c)&&(b[b.length]=c);return b},values:function(a){return m.map(a,function(a,b){return b})},extend:function(a){m.each(e.call(arguments,1),function(b){for(var c in b)!k.und(b[c])&&(a[c]=b[c])});return a},trim:String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(h,"")},bind:function(a,b){return function(){b.apply(a,arguments)}}};j(b,i),j(b,m),b.is=k,b.v=b;var n=function(a,b){this.val=a,this._scope=b||null,this._chained=0};b.each(b.extend({},i,m),function(a,c){n.prototype[a]=function(){var a=b.toArray(arguments);a.unshift(this.val);var d=c.apply(this._scope,a);this.val=d;return this._chained?this:d}}),n.prototype.chain=function(){this._chained=1;return this},n.prototype.value=function(){return this.val};var o=a.v;b.noConflict=function(){a.v=o;return this},typeof module!="undefined"&&module.exports?module.exports=b:a.v=b}(this)