diff --git a/build/build.css b/build/build.css index 7efc020..6781261 100644 --- a/build/build.css +++ b/build/build.css @@ -160,8 +160,6 @@ } * { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; } @@ -183,7 +181,6 @@ body { #logo > div { height: 100%; - -webkit-background-size: contain; background-size: contain; background-position: center; background-repeat: no-repeat; diff --git a/build/build.js b/build/build.js index b848bef..21bb04d 100644 --- a/build/build.js +++ b/build/build.js @@ -10,7 +10,7 @@ function require(name) { var module = require.modules[name]; if (!module) throw new Error('failed to require "' + name + '"'); - if (module.definition) { + if (!('exports' in module) && typeof module.definition === 'function') { module.client = module.component = true; module.definition.call(this, module.exports = {}, module); delete module.definition; @@ -19,6 +19,89 @@ function require(name) { return module.exports; } +/** + * Meta info, accessible in the global scope unless you use AMD option. + */ + +require.loader = 'component'; + +/** + * Internal helper object, contains a sorting function for semantiv versioning + */ +require.helper = {}; +require.helper.semVerSort = function(a, b) { + var aArray = a.version.split('.'); + var bArray = b.version.split('.'); + for (var i=0; i bLex ? 1 : -1; + continue; + } else if (aInt > bInt) { + return 1; + } else { + return -1; + } + } + return 0; +} + +/** + * Find and require a module which name starts with the provided name. + * If multiple modules exists, the highest semver is used. + * This function can only be used for remote dependencies. + + * @param {String} name - module name: `user~repo` + * @param {Boolean} returnPath - returns the canonical require path if true, + * otherwise it returns the epxorted module + */ +require.latest = function (name, returnPath) { + function showError(name) { + throw new Error('failed to find latest module of "' + name + '"'); + } + // only remotes with semvers, ignore local files conataining a '/' + var versionRegexp = /(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/; + var remoteRegexp = /(.*)~(.*)/; + if (!remoteRegexp.test(name)) showError(name); + var moduleNames = Object.keys(require.modules); + var semVerCandidates = []; + var otherCandidates = []; // for instance: name of the git branch + for (var i=0; i 0) { + var module = semVerCandidates.sort(require.helper.semVerSort).pop().name; + if (returnPath === true) { + return module; + } + return require(module); + } + // if the build contains more than one branch of the same module + // you should not use this funciton + var module = otherCandidates.pop().name; + if (returnPath === true) { + return module; + } + return require(module); +} + /** * Registered modules. */ @@ -52,8 +135,7 @@ require.define = function (name, exports) { exports: exports }; }; - -require.register("yields~keycode@1.0.0", function (exports, module) { +require.register("yields~keycode@1.1.0", function (exports, module) { /** * map @@ -61,6 +143,7 @@ require.register("yields~keycode@1.0.0", function (exports, module) { var map = { backspace: 8 + , command: 91 , tab: 9 , clear: 12 , enter: 13 @@ -77,6 +160,18 @@ var map = { , down: 40 , del: 46 , comma: 188 + , f1: 112 + , f2: 113 + , f3: 114 + , f4: 115 + , f5: 116 + , f6: 117 + , f7: 118 + , f8: 119 + , f9: 120 + , f10: 121 + , f11: 122 + , f12: 123 , ',': 188 , '.': 190 , '/': 191 @@ -98,7 +193,7 @@ var map = { */ module.exports = function(name){ - return map[name] || name.toUpperCase().charCodeAt(0); + return map[name.toLowerCase()] || name.toUpperCase().charCodeAt(0); }; }); @@ -109,7 +204,7 @@ require.register("yields~k-sequence@0.1.0", function (exports, module) { * dependencies */ -var keycode = require("yields~keycode@1.0.0"); +var keycode = require('yields~keycode@1.1.0'); /** * Export `sequence` @@ -210,8 +305,7 @@ exports.unbind = function(el, type, fn, capture){ }); -require.register("component~bind@0.0.1", function (exports, module) { - +require.register("component~bind@1.0.0", function (exports, module) { /** * Slice reference. */ @@ -230,7 +324,7 @@ var slice = [].slice; module.exports = function(obj, fn){ if ('string' == typeof fn) fn = obj[fn]; if ('function' != typeof fn) throw new Error('bind() requires a function'); - var args = [].slice.call(arguments, 2); + var args = slice.call(arguments, 2); return function(){ return fn.apply(obj, args.concat(slice.call(arguments))); } @@ -258,16 +352,16 @@ require.register("yields~k@0.6.1", function (exports, module) { * dependencies. */ -var event = require("component~event@0.1.0") - , proto = require("yields~k@0.6.1/lib/proto.js") - , bind = require("component~bind@0.0.1"); +var event = require('component~event@0.1.0') + , proto = require('yields~k@0.6.1/lib/proto.js') + , bind = require('component~bind@1.0.0'); /** * Create a new dispatcher with `el`. * * example: * - * var k = require("k")(window); + * var k = require('k')(window); * k('shift + tab', function(){}); * * @param {Element} el @@ -297,10 +391,10 @@ require.register("yields~k@0.6.1/lib/proto.js", function (exports, module) { * dependencies */ -var sequence = require("yields~k-sequence@0.1.0") - , keycode = require("yields~keycode@1.0.0") - , event = require("component~event@0.1.0") - , os = require("component~os@0.0.1"); +var sequence = require('yields~k-sequence@0.1.0') + , keycode = require('yields~keycode@1.1.0') + , event = require('component~event@0.1.0') + , os = require('component~os@0.0.1'); /** * modifiers. @@ -892,10 +986,10 @@ require.register("component~top@0.0.2", function (exports, module) { * Module dependencies. */ -var debounce = require("matthewmueller~debounce@0.0.1") - , html = require("component~top@0.0.2/template.js") - , domify = require("component~domify@1.0.0") - , event = require("component~event@0.1.0") +var debounce = require('matthewmueller~debounce@0.0.1') + , html = require('component~top@0.0.2/template.js') + , domify = require('component~domify@1.0.0') + , event = require('component~event@0.1.0') /** * Expose `top`. @@ -937,193 +1031,136 @@ require.register("component~top@0.0.2/template.js", function (exports, module) { module.exports = '\n'; }); -require.register("component~indexof@0.0.1", function (exports, module) { +require.register("component~trim@0.0.1", function (exports, module) { -var indexOf = [].indexOf; +exports = module.exports = trim; -module.exports = function(arr, obj){ - if (indexOf) return arr.indexOf(obj); - for (var i = 0; i < arr.length; ++i) { - if (arr[i] === obj) return i; - } - return -1; -}; -}); +function trim(str){ + if (str.trim) return str.trim(); + return str.replace(/^\s*|\s*$/g, ''); +} -require.register("component~indexof@0.0.3", function (exports, module) { -module.exports = function(arr, obj){ - if (arr.indexOf) return arr.indexOf(obj); - for (var i = 0; i < arr.length; ++i) { - if (arr[i] === obj) return i; - } - return -1; +exports.left = function(str){ + if (str.trimLeft) return str.trimLeft(); + return str.replace(/^\s*/, ''); }; -}); - -require.register("component~classes@1.1.1", function (exports, module) { - -/** - * Module dependencies. - */ -var index = require("component~indexof@0.0.3"); +exports.right = function(str){ + if (str.trimRight) return str.trimRight(); + return str.replace(/\s*$/, ''); +}; -/** - * Whitespace regexp. - */ +}); -var re = /\s+/; +require.register("component~type@1.0.0", function (exports, module) { /** - * toString reference. + * toString ref. */ var toString = Object.prototype.toString; /** - * Wrap `el` in a `ClassList`. + * Return the type of `val`. * - * @param {Element} el - * @return {ClassList} + * @param {Mixed} val + * @return {String} * @api public */ -module.exports = function(el){ - return new ClassList(el); +module.exports = function(val){ + switch (toString.call(val)) { + case '[object Function]': return 'function'; + case '[object Date]': return 'date'; + case '[object RegExp]': return 'regexp'; + case '[object Arguments]': return 'arguments'; + case '[object Array]': return 'array'; + case '[object String]': return 'string'; + } + + if (val === null) return 'null'; + if (val === undefined) return 'undefined'; + if (val && val.nodeType === 1) return 'element'; + if (val === Object(val)) return 'object'; + + return typeof val; }; -/** - * Initialize a new ClassList for `el`. - * - * @param {Element} el - * @api private - */ +}); -function ClassList(el) { - this.el = el; - this.list = el.classList; -} +require.register("component~querystring@1.3.1", function (exports, module) { /** - * Add class `name` if not already present. - * - * @param {String} name - * @return {ClassList} - * @api public + * Module dependencies. */ -ClassList.prototype.add = function(name){ - // classList - if (this.list) { - this.list.add(name); - return this; - } - - // fallback - var arr = this.array(); - var i = index(arr, name); - if (!~i) arr.push(name); - this.el.className = arr.join(' '); - return this; -}; +var encode = encodeURIComponent; +var decode = decodeURIComponent; +var trim = require('component~trim@0.0.1'); +var type = require('component~type@1.0.0'); /** - * Remove class `name` when present, or - * pass a regular expression to remove - * any which match. + * Parse the given query `str`. * - * @param {String|RegExp} name - * @return {ClassList} + * @param {String} str + * @return {Object} * @api public */ -ClassList.prototype.remove = function(name){ - if ('[object RegExp]' == toString.call(name)) { - return this.removeMatching(name); - } - - // classList - if (this.list) { - this.list.remove(name); - return this; - } - - // fallback - var arr = this.array(); - var i = index(arr, name); - if (~i) arr.splice(i, 1); - this.el.className = arr.join(' '); - return this; -}; +exports.parse = function(str){ + if ('string' != typeof str) return {}; -/** - * Remove all classes matching `re`. - * - * @param {RegExp} re - * @return {ClassList} - * @api private - */ + str = trim(str); + if ('' == str) return {}; + if ('?' == str.charAt(0)) str = str.slice(1); -ClassList.prototype.removeMatching = function(re){ - var arr = this.array(); - for (var i = 0; i < arr.length; i++) { - if (re.test(arr[i])) { - this.remove(arr[i]); + var obj = {}; + var pairs = str.split('&'); + for (var i = 0; i < pairs.length; i++) { + var parts = pairs[i].split('='); + var key = decode(parts[0]); + var m; + + if (m = /(\w+)\[(\d+)\]/.exec(key)) { + obj[m[1]] = obj[m[1]] || []; + obj[m[1]][m[2]] = decode(parts[1]); + continue; } - } - return this; -}; - -/** - * Toggle class `name`. - * - * @param {String} name - * @return {ClassList} - * @api public - */ -ClassList.prototype.toggle = function(name){ - // classList - if (this.list) { - this.list.toggle(name); - return this; + obj[parts[0]] = null == parts[1] + ? '' + : decode(parts[1]); } - // fallback - if (this.has(name)) { - this.remove(name); - } else { - this.add(name); - } - return this; + return obj; }; /** - * Return an array of classes. + * Stringify the given `obj`. * - * @return {Array} + * @param {Object} obj + * @return {String} * @api public */ -ClassList.prototype.array = function(){ - var arr = this.el.className.split(re); - if ('' === arr[0]) arr.pop(); - return arr; -}; +exports.stringify = function(obj){ + if (!obj) return ''; + var pairs = []; -/** - * Check if class `name` is present. - * - * @param {String} name - * @return {ClassList} - * @api public - */ + for (var key in obj) { + var value = obj[key]; -ClassList.prototype.has = -ClassList.prototype.contains = function(name){ - return this.list - ? this.list.contains(name) - : !! ~index(this.array(), name); + if ('array' == type(value)) { + for (var i = 0; i < value.length; ++i) { + pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i])); + } + continue; + } + + pairs.push(encode(key) + '=' + encode(obj[key])); + } + + return pairs.join('&'); }; }); @@ -1464,2709 +1501,2977 @@ Emitter.prototype.hasListeners = function(event){ }); -require.register("component~query@0.0.1", function (exports, module) { - -function one(selector, el) { - return el.querySelector(selector); -} +require.register("component~reduce@1.0.1", function (exports, module) { -exports = module.exports = function(selector, el){ - el = el || document; - return one(selector, el); -}; +/** + * Reduce `arr` with `fn`. + * + * @param {Array} arr + * @param {Function} fn + * @param {Mixed} initial + * + * TODO: combatible error handling? + */ -exports.all = function(selector, el){ - el = el || document; - return el.querySelectorAll(selector); -}; +module.exports = function(arr, fn, initial){ + var idx = 0; + var len = arr.length; + var curr = arguments.length == 3 + ? initial + : arr[idx++]; -exports.engine = function(obj){ - if (!obj.one) throw new Error('.one callback required'); - if (!obj.all) throw new Error('.all callback required'); - one = obj.one; - exports.all = obj.all; + while (idx < len) { + curr = fn.call(null, curr, arr[idx], ++idx, arr); + } + + return curr; }; - }); -require.register("yields~merge-attrs@0.0.1", function (exports, module) { +require.register("visionmedia~superagent@0.21.0", function (exports, module) { +/** + * Module dependencies. + */ + +var Emitter = require('component~emitter@1.1.2'); +var reduce = require('component~reduce@1.0.1'); /** - * Export `merge` + * Root reference for iframes. */ -module.exports = merge; +var root = 'undefined' == typeof window + ? this + : window; /** - * Merge `b`'s attrs into `a`. - * - * @param {Element} a - * @param {Element} b - * @api public + * Noop. */ -function merge(a, b){ - for (var i = 0; i < b.attributes.length; ++i) { - var attr = b.attributes[i]; - if (ignore(a, attr)) continue; - a.setAttribute(attr.name, attr.value); - } -} +function noop(){}; /** - * Check if `attr` should be ignored. + * Check if `obj` is a host object, + * we don't want to serialize these :) * - * @param {Element} a - * @param {Attr} attr + * TODO: future proof, move to compoent land + * + * @param {Object} obj * @return {Boolean} * @api private */ -function ignore(a, attr){ - return !attr.specified - || 'class' == attr.name - || 'id' == attr.name - || a.hasAttribute(attr.name); +function isHost(obj) { + var str = {}.toString.call(obj); + + switch (str) { + case '[object File]': + case '[object Blob]': + case '[object FormData]': + return true; + default: + return false; + } } -}); +/** + * Determine XHR. + */ -require.register("yields~uniq@master", function (exports, module) { +function getXHR() { + if (root.XMLHttpRequest + && ('file:' != root.location.protocol || !root.ActiveXObject)) { + return new XMLHttpRequest; + } else { + try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {} + try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {} + try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {} + try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {} + } + return false; +} /** - * dependencies + * Removes leading and trailing whitespace, added to support IE. + * + * @param {String} s + * @return {String} + * @api private */ -try { - var indexOf = require("component~indexof@0.0.1"); -} catch(e){ - var indexOf = require("indexof-component"); -} +var trim = ''.trim + ? function(s) { return s.trim(); } + : function(s) { return s.replace(/(^\s*|\s*$)/g, ''); }; /** - * Create duplicate free array - * from the provided `arr`. + * Check if `obj` is an object. * - * @param {Array} arr - * @param {Array} select - * @return {Array} + * @param {Object} obj + * @return {Boolean} + * @api private */ -module.exports = function (arr, select) { - var len = arr.length, ret = [], v; - select = select ? (select instanceof Array ? select : [select]) : false; +function isObject(obj) { + return obj === Object(obj); +} - for (var i = 0; i < len; i++) { - v = arr[i]; - if (select && !~indexOf(select, v)) { - ret.push(v); - } else if (!~indexOf(ret, v)) { - ret.push(v); +/** + * Serialize the given `obj`. + * + * @param {Object} obj + * @return {String} + * @api private + */ + +function serialize(obj) { + if (!isObject(obj)) return obj; + var pairs = []; + for (var key in obj) { + if (null != obj[key]) { + pairs.push(encodeURIComponent(key) + + '=' + encodeURIComponent(obj[key])); } } - return ret; -}; - -}); - -require.register("yields~carry@0.0.1", function (exports, module) { + return pairs.join('&'); +} /** - * dependencies + * Expose serialization method. */ -var merge = require("yields~merge-attrs@0.0.1") - , classes = require("component~classes@1.1.1") - , uniq = require("yields~uniq@master"); + request.serializeObject = serialize; + + /** + * Parse the given x-www-form-urlencoded `str`. + * + * @param {String} str + * @return {Object} + * @api private + */ + +function parseString(str) { + var obj = {}; + var pairs = str.split('&'); + var parts; + var pair; + + for (var i = 0, len = pairs.length; i < len; ++i) { + pair = pairs[i]; + parts = pair.split('='); + obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); + } + + return obj; +} /** - * Export `carry` + * Expose parser. */ -module.exports = carry; +request.parseString = parseString; /** - * Carry over attrs and classes - * from `b` to `a`. + * Default MIME type map. + * + * superagent.types.xml = 'application/xml'; * - * @param {Element} a - * @param {Element} b - * @return {Element} - * @api public */ -function carry(a, b){ - if (!a) return b.cloneNode(); - carry.attrs(a, b); - carry.classes(a, b); - return a; -} +request.types = { + html: 'text/html', + json: 'application/json', + xml: 'application/xml', + urlencoded: 'application/x-www-form-urlencoded', + 'form': 'application/x-www-form-urlencoded', + 'form-data': 'application/x-www-form-urlencoded' +}; /** - * Carry attributes. + * Default serialization map. + * + * superagent.serialize['application/xml'] = function(obj){ + * return 'generated xml here'; + * }; * - * @param {Element} a - * @param {Element} b - * @return {Element} a - * @api public */ -carry.attrs = function(a, b){ - merge(a, b); - return a; + request.serialize = { + 'application/x-www-form-urlencoded': serialize, + 'application/json': JSON.stringify + }; + + /** + * Default parsers. + * + * superagent.parse['application/xml'] = function(str){ + * return { object parsed from str }; + * }; + * + */ + +request.parse = { + 'application/x-www-form-urlencoded': parseString, + 'application/json': JSON.parse }; /** - * Carry over classes. + * Parse the given header `str` into + * an object containing the mapped fields. * - * @param {Element} a - * @param {Element} b - * @return {Element} a - * @api public + * @param {String} str + * @return {Object} + * @api private */ -carry.classes = function(a, b){ - if (a.className == b.className) return a; - var blist = classes(b).array(); - var alist = classes(a).array(); - var list = alist.concat(blist); - a.className = uniq(list).join(' '); - return a; -}; - -}); +function parseHeader(str) { + var lines = str.split(/\r?\n/); + var fields = {}; + var index; + var line; + var field; + var val; -require.register("visionmedia~debug@0.7.4", function (exports, module) { -if ('undefined' == typeof window) { - module.exports = require("./lib/debug"); -} else { - module.exports = require("visionmedia~debug@0.7.4/debug.js"); -} + lines.pop(); // trailing CRLF -}); + for (var i = 0, len = lines.length; i < len; ++i) { + line = lines[i]; + index = line.indexOf(':'); + field = line.slice(0, index).toLowerCase(); + val = trim(line.slice(index + 1)); + fields[field] = val; + } -require.register("visionmedia~debug@0.7.4/debug.js", function (exports, module) { + return fields; +} /** - * Expose `debug()` as the module. + * Return the mime type for the given `str`. + * + * @param {String} str + * @return {String} + * @api private */ -module.exports = debug; +function type(str){ + return str.split(/ *; */).shift(); +}; /** - * Create a debugger with the given `name`. + * Return header field parameters. * - * @param {String} name - * @return {Type} - * @api public + * @param {String} str + * @return {Object} + * @api private */ -function debug(name) { - if (!debug.enabled(name)) return function(){}; - - return function(fmt){ - fmt = coerce(fmt); +function params(str){ + return reduce(str.split(/ *; */), function(obj, str){ + var parts = str.split(/ *= */) + , key = parts.shift() + , val = parts.shift(); - var curr = new Date; - var ms = curr - (debug[name] || curr); - debug[name] = curr; + if (key && val) obj[key] = val; + return obj; + }, {}); +}; - fmt = name - + ' ' - + fmt - + ' +' + debug.humanize(ms); +/** + * Initialize a new `Response` with the given `xhr`. + * + * - set flags (.ok, .error, etc) + * - parse header + * + * Examples: + * + * Aliasing `superagent` as `request` is nice: + * + * request = superagent; + * + * We can use the promise-like API, or pass callbacks: + * + * request.get('/').end(function(res){}); + * request.get('/', function(res){}); + * + * Sending data can be chained: + * + * request + * .post('/user') + * .send({ name: 'tj' }) + * .end(function(res){}); + * + * Or passed to `.send()`: + * + * request + * .post('/user') + * .send({ name: 'tj' }, function(res){}); + * + * Or passed to `.post()`: + * + * request + * .post('/user', { name: 'tj' }) + * .end(function(res){}); + * + * Or further reduced to a single call for simple cases: + * + * request + * .post('/user', { name: 'tj' }, function(res){}); + * + * @param {XMLHTTPRequest} xhr + * @param {Object} options + * @api private + */ - // This hackery is required for IE8 - // where `console.log` doesn't have 'apply' - window.console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); - } +function Response(req, options) { + options = options || {}; + this.req = req; + this.xhr = this.req.xhr; + this.text = this.req.method !='HEAD' + ? this.xhr.responseText + : null; + this.setStatusProperties(this.xhr.status); + this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders()); + // getAllResponseHeaders sometimes falsely returns "" for CORS requests, but + // getResponseHeader still works. so we get content-type even if getting + // other headers fails. + this.header['content-type'] = this.xhr.getResponseHeader('content-type'); + this.setHeaderProperties(this.header); + this.body = this.req.method != 'HEAD' + ? this.parseBody(this.text) + : null; } /** - * The currently active debug mode names. + * Get case-insensitive `field` value. + * + * @param {String} field + * @return {String} + * @api public */ -debug.names = []; -debug.skips = []; +Response.prototype.get = function(field){ + return this.header[field.toLowerCase()]; +}; /** - * Enables a debug mode by name. This can include modes - * separated by a colon and wildcards. + * Set header related properties: * - * @param {String} name - * @api public + * - `.type` the content type without params + * + * A response of "Content-Type: text/plain; charset=utf-8" + * will provide you with a `.type` of "text/plain". + * + * @param {Object} header + * @api private */ -debug.enable = function(name) { - try { - localStorage.debug = name; - } catch(e){} - - var split = (name || '').split(/[\s,]+/) - , len = split.length; +Response.prototype.setHeaderProperties = function(header){ + // content-type + var ct = this.header['content-type'] || ''; + this.type = type(ct); - for (var i = 0; i < len; i++) { - name = split[i].replace('*', '.*?'); - if (name[0] === '-') { - debug.skips.push(new RegExp('^' + name.substr(1) + '$')); - } - else { - debug.names.push(new RegExp('^' + name + '$')); - } - } + // params + var obj = params(ct); + for (var key in obj) this[key] = obj[key]; }; /** - * Disable debug output. + * Parse the given body `str`. * - * @api public + * Used for auto-parsing of bodies. Parsers + * are defined on the `superagent.parse` object. + * + * @param {String} str + * @return {Mixed} + * @api private */ -debug.disable = function(){ - debug.enable(''); +Response.prototype.parseBody = function(str){ + var parse = request.parse[this.type]; + return parse && str && str.length + ? parse(str) + : null; }; /** - * Humanize the given `ms`. + * Set flags such as `.ok` based on `status`. * - * @param {Number} m - * @return {String} + * For example a 2xx response will give you a `.ok` of __true__ + * whereas 5xx will be __false__ and `.error` will be __true__. The + * `.clientError` and `.serverError` are also available to be more + * specific, and `.statusType` is the class of error ranging from 1..5 + * sometimes useful for mapping respond colors etc. + * + * "sugar" properties are also defined for common cases. Currently providing: + * + * - .noContent + * - .badRequest + * - .unauthorized + * - .notAcceptable + * - .notFound + * + * @param {Number} status * @api private */ -debug.humanize = function(ms) { - var sec = 1000 - , min = 60 * 1000 - , hour = 60 * min; +Response.prototype.setStatusProperties = function(status){ + var type = status / 100 | 0; - if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; - if (ms >= min) return (ms / min).toFixed(1) + 'm'; - if (ms >= sec) return (ms / sec | 0) + 's'; - return ms + 'ms'; + // status / class + this.status = status; + this.statusType = type; + + // basics + this.info = 1 == type; + this.ok = 2 == type; + this.clientError = 4 == type; + this.serverError = 5 == type; + this.error = (4 == type || 5 == type) + ? this.toError() + : false; + + // sugar + this.accepted = 202 == status; + this.noContent = 204 == status || 1223 == status; + this.badRequest = 400 == status; + this.unauthorized = 401 == status; + this.notAcceptable = 406 == status; + this.notFound = 404 == status; + this.forbidden = 403 == status; }; /** - * Returns true if the given mode name is enabled, false otherwise. + * Return an `Error` representative of this response. * - * @param {String} name - * @return {Boolean} + * @return {Error} * @api public */ -debug.enabled = function(name) { - for (var i = 0, len = debug.skips.length; i < len; i++) { - if (debug.skips[i].test(name)) { - return false; - } - } - for (var i = 0, len = debug.names.length; i < len; i++) { - if (debug.names[i].test(name)) { - return true; - } - } - return false; +Response.prototype.toError = function(){ + var req = this.req; + var method = req.method; + var url = req.url; + + var msg = 'cannot ' + method + ' ' + url + ' (' + this.status + ')'; + var err = new Error(msg); + err.status = this.status; + err.method = method; + err.url = url; + + return err; }; /** - * Coerce `val`. + * Expose `Response`. */ -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} +request.Response = Response; -// persist +/** + * Initialize a new `Request` with the given `method` and `url`. + * + * @param {String} method + * @param {String} url + * @api public + */ -try { - if (window.localStorage) debug.enable(localStorage.debug); -} catch(e){} +function Request(method, url) { + var self = this; + Emitter.call(this); + this._query = this._query || []; + this.method = method; + this.url = url; + this.header = {}; + this._header = {}; + this.on('end', function(){ + var err = null; + var res = null; + + try { + res = new Response(self); + } catch(e) { + err = new Error('Parser is unable to parse the response'); + err.parse = true; + err.original = e; + } -}); + self.callback(err, res); + }); +} -require.register("component~reactive@1.1.0", function (exports, module) { /** - * Module dependencies. + * Mixin `Emitter`. */ -var Emitter = require("component~emitter@1.1.1"); -var query = require("component~query@0.0.1"); -var domify = require("component~domify@1.2.1"); -var debug = require("visionmedia~debug@0.7.4")('reactive'); - -var Adapter = require("component~reactive@1.1.0/lib/adapter.js"); -var AttrBinding = require("component~reactive@1.1.0/lib/attr-binding.js"); -var TextBinding = require("component~reactive@1.1.0/lib/text-binding.js"); -var bindings = require("component~reactive@1.1.0/lib/bindings.js"); -var Binding = require("component~reactive@1.1.0/lib/binding.js"); -var utils = require("component~reactive@1.1.0/lib/utils.js"); -var walk = require("component~reactive@1.1.0/lib/walk.js"); +Emitter(Request.prototype); /** - * Expose `Reactive`. + * Allow for extension */ -exports = module.exports = Reactive; +Request.prototype.use = function(fn) { + fn(this); + return this; +} /** - * Initialize a reactive template for `el` and `obj`. + * Set timeout to `ms`. * - * @param {Element} el - * @param {Element} obj - * @param {Object} options + * @param {Number} ms + * @return {Request} for chaining * @api public */ -function Reactive(el, model, opt) { - if (!(this instanceof Reactive)) return new Reactive(el, model, opt); - opt = opt || {}; - - if (typeof el === 'string') { - el = domify(el); - } - - var self = this; - self.opt = opt || {}; - self.model = model || {}; - self.adapter = (opt.adapter || Adapter)(self.model); - self.el = el; - self.view = opt.delegate || Object.create(null); - - self.bindings = opt.bindings || Object.create(null); - - // TODO undo this crap and just export bindings regularly - // not that binding order matters!! - bindings({ - bind: function(name, fn) { - self.bindings[name] = fn; - } - }); +Request.prototype.timeout = function(ms){ + this._timeout = ms; + return this; +}; - self._bind(this.el, []); -} +/** + * Clear previous timeout. + * + * @return {Request} for chaining + * @api public + */ -Emitter(Reactive.prototype); +Request.prototype.clearTimeout = function(){ + this._timeout = 0; + clearTimeout(this._timer); + return this; +}; /** - * Subscribe to changes on `prop`. + * Abort the request, and clear potential timeout. * - * @param {String} prop - * @param {Function} fn - * @return {Reactive} - * @api private + * @return {Request} + * @api public */ -Reactive.prototype.sub = function(prop, fn){ - var self = this; +Request.prototype.abort = function(){ + if (this.aborted) return; + this.aborted = true; + this.xhr.abort(); + this.clearTimeout(); + this.emit('abort'); + return this; +}; - debug('subscribe %s', prop); +/** + * Set header `field` to `val`, or multiple fields with one object. + * + * Examples: + * + * req.get('/') + * .set('Accept', 'application/json') + * .set('X-API-Key', 'foobar') + * .end(callback); + * + * req.get('/') + * .set({ Accept: 'application/json', 'X-API-Key': 'foobar' }) + * .end(callback); + * + * @param {String|Object} field + * @param {String} val + * @return {Request} for chaining + * @api public + */ - // if we have parts, we need to subscribe to the parent as well - // TODO (defunctzombie) multiple levels of properties - var parts = prop.split('.'); - if (parts.length > 1) { - self.sub(parts[0], fn); +Request.prototype.set = function(field, val){ + if (isObject(field)) { + for (var key in field) { + this.set(key, field[key]); + } + return this; } - - // for when reactive changes the property - this.on('change ' + prop, fn); - - // for when the property changed within the adapter - this.adapter.subscribe(prop, function() { - // skip items set internally from calling function twice - if (self._internal_set) return; - - fn.apply(this, arguments); - }); - + this._header[field.toLowerCase()] = val; + this.header[field] = val; return this; }; /** - * Unsubscribe to changes from `prop`. + * Remove header `field`. * - * @param {String} prop - * @param {Function} fn - * @return {Reactive} - * @api private + * Example: + * + * req.get('/') + * .unset('User-Agent') + * .end(callback); + * + * @param {String} field + * @return {Request} for chaining + * @api public */ -Reactive.prototype.unsub = function(prop, fn){ - this.off('change ' + prop, fn); - this.adapter.unsubscribe(prop, fn); - +Request.prototype.unset = function(field){ + delete this._header[field.toLowerCase()]; + delete this.header[field]; return this; }; /** - * Get a `prop` + * Get case-insensitive header `field` value. * - * @param {String} prop - * @param {Mixed} val - * @return {Mixed} + * @param {String} field + * @return {String} * @api private */ -Reactive.prototype.get = function(prop) { - if (prop === 'this') { - return this.model; - } - - // model takes precedence - var modelVal = this.adapter.get(prop); - if (modelVal) { - return modelVal; - } - - var view = this.view; - var viewVal = view[prop]; - if ('function' == typeof viewVal) { - return viewVal.call(view); - } - else if (viewVal) { - return viewVal; - } - - return undefined; +Request.prototype.getHeader = function(field){ + return this._header[field.toLowerCase()]; }; /** - * Set a `prop` + * Set Content-Type to `type`, mapping values from `request.types`. * - * @param {String} prop - * @param {Mixed} val - * @return {Reactive} - * @api private + * Examples: + * + * superagent.types.xml = 'application/xml'; + * + * request.post('/') + * .type('xml') + * .send(xmlstring) + * .end(callback); + * + * request.post('/') + * .type('application/xml') + * .send(xmlstring) + * .end(callback); + * + * @param {String} type + * @return {Request} for chaining + * @api public */ -Reactive.prototype.set = function(prop, val) { - var self = this; - // internal set flag lets reactive updates know to avoid triggering - // updates for the Adapter#set call - // we will already trigger updates with the change event - self._internal_set = true; - if( "object" == typeof prop) { - Object.keys(prop).forEach(function(name){ - self.set(name, prop[name]); - }); - } - else { - self.adapter.set(prop, val); - self.emit('change ' + prop, val); - } - self._internal_set = false; - return self; +Request.prototype.type = function(type){ + this.set('Content-Type', request.types[type] || type); + return this; }; /** - * Traverse and bind all interpolation within attributes and text. + * Set Accept to `type`, mapping values from `request.types`. * - * @param {Element} el - * @api private + * Examples: + * + * superagent.types.json = 'application/json'; + * + * request.get('/agent') + * .accept('json') + * .end(callback); + * + * request.get('/agent') + * .accept('application/json') + * .end(callback); + * + * @param {String} accept + * @return {Request} for chaining + * @api public */ -Reactive.prototype.bindInterpolation = function(el, els){ - - // element - if (el.nodeType == 1) { - for (var i = 0; i < el.attributes.length; i++) { - var attr = el.attributes[i]; - if (utils.hasInterpolation(attr.value)) { - new AttrBinding(this, el, attr); - } - } - } +Request.prototype.accept = function(type){ + this.set('Accept', request.types[type] || type); + return this; +}; - // text node - if (el.nodeType == 3) { - if (utils.hasInterpolation(el.data)) { - debug('bind text "%s"', el.data); - new TextBinding(this, el); - } - } +/** + * Set Authorization field value with `user` and `pass`. + * + * @param {String} user + * @param {String} pass + * @return {Request} for chaining + * @api public + */ - // walk nodes - for (var i = 0; i < el.childNodes.length; i++) { - var node = el.childNodes[i]; - this.bindInterpolation(node, els); - } +Request.prototype.auth = function(user, pass){ + var str = btoa(user + ':' + pass); + this.set('Authorization', 'Basic ' + str); + return this; }; -Reactive.prototype._bind = function() { - var self = this; - - var bindings = self.bindings; - - walk(self.el, function(el, next) { - // element - if (el.nodeType == 1) { - var skip = false; - - var attrs = {}; - for (var i = 0; i < el.attributes.length; ++i) { - var attr = el.attributes[i]; - var name = attr.name; - attrs[name] = attr; - } - - // bindings must be iterated first - // to see if any request skipping - // only then can we see about attributes - Object.keys(bindings).forEach(function(name) { - if (!attrs[name] || skip) { - return; - } - - debug('bind [%s]', name); - - var prop = attrs[name].value; - var binding_fn = bindings[name]; - if (!binding_fn) { - return; - } - - var binding = new Binding(name, self, el, binding_fn); - binding.bind(); - if (binding.skip) { - skip = true; - } - }); - - if (skip) { - return next(skip); - } - - // if we are not skipping - // bind any interpolation attrs - for (var i = 0; i < el.attributes.length; ++i) { - var attr = el.attributes[i]; - var name = attr.name; - if (utils.hasInterpolation(attr.value)) { - new AttrBinding(self, el, attr); - } - } - - return next(skip); - } - // text - else if (el.nodeType == 3) { - if (utils.hasInterpolation(el.data)) { - debug('bind text "%s"', el.data); - new TextBinding(self, el); - } - } +/** +* Add query-string `val`. +* +* Examples: +* +* request.get('/shoes') +* .query('size=10') +* .query({ color: 'blue' }) +* +* @param {Object|String} val +* @return {Request} for chaining +* @api public +*/ - next(); - }); +Request.prototype.query = function(val){ + if ('string' != typeof val) val = serialize(val); + if (val) this._query.push(val); + return this; }; /** - * Bind `name` to `fn`. + * Write the field `name` and `val` for "multipart/form-data" + * request bodies. * - * @param {String|Object} name or object - * @param {Function} fn + * ``` js + * request.post('/upload') + * .field('foo', 'bar') + * .end(callback); + * ``` + * + * @param {String} name + * @param {String|Blob|File} val + * @return {Request} for chaining * @api public */ -Reactive.prototype.bind = function(name, fn) { - var self = this; - if ('object' == typeof name) { - for (var key in name) { - this.bind(key, name[key]); - } - return; - } - - var els = query.all('[' + name + ']', this.el); - if (this.el.hasAttribute && this.el.hasAttribute(name)) { - els = [].slice.call(els); - els.unshift(this.el); - } - if (!els.length) return; - - debug('bind [%s] (%d elements)', name, els.length); - for (var i = 0; i < els.length; i++) { - var binding = new Binding(name, this, els[i], fn); - binding.bind(); - } +Request.prototype.field = function(name, val){ + if (!this._formData) this._formData = new FormData(); + this._formData.append(name, val); + return this; }; /** - * Destroy the binding - * - Removes the element from the dom (if inserted) - * - unbinds any event listeners + * Queue the given `file` as an attachment to the specified `field`, + * with optional `filename`. * + * ``` js + * request.post('/upload') + * .attach(new Blob(['hey!'], { type: "text/html"})) + * .end(callback); + * ``` + * + * @param {String} field + * @param {Blob|File} file + * @param {String} filename + * @return {Request} for chaining * @api public */ -Reactive.prototype.destroy = function() { - var self = this; - - if (self.el.parentNode) { - self.el.parentNode.removeChild(self.el); - } - - self.adapter.unsubscribeAll(); - self.emit('destroyed'); - self.removeAllListeners(); +Request.prototype.attach = function(field, file, filename){ + if (!this._formData) this._formData = new FormData(); + this._formData.append(field, file, filename); + return this; }; /** - * Use middleware + * Send `data`, defaulting the `.type()` to "json" when + * an object is given. + * + * Examples: + * + * // querystring + * request.get('/search') + * .end(callback) + * + * // multiple data "writes" + * request.get('/search') + * .send({ search: 'query' }) + * .send({ range: '1..5' }) + * .send({ order: 'desc' }) + * .end(callback) + * + * // manual json + * request.post('/user') + * .type('json') + * .send('{"name":"tj"}) + * .end(callback) + * + * // auto json + * request.post('/user') + * .send({ name: 'tj' }) + * .end(callback) + * + * // manual x-www-form-urlencoded + * request.post('/user') + * .type('form') + * .send('name=tj') + * .end(callback) + * + * // auto x-www-form-urlencoded + * request.post('/user') + * .type('form') + * .send({ name: 'tj' }) + * .end(callback) + * + * // defaults to x-www-form-urlencoded + * request.post('/user') + * .send('name=tobi') + * .send('species=ferret') + * .end(callback) * + * @param {String|Object} data + * @return {Request} for chaining * @api public */ -Reactive.prototype.use = function(fn) { - fn(this); - return this; -}; +Request.prototype.send = function(data){ + var obj = isObject(data); + var type = this.getHeader('Content-Type'); -}); + // merge + if (obj && isObject(this._data)) { + for (var key in data) { + this._data[key] = data[key]; + } + } else if ('string' == typeof data) { + if (!type) this.type('form'); + type = this.getHeader('Content-Type'); + if ('application/x-www-form-urlencoded' == type) { + this._data = this._data + ? this._data + '&' + data + : data; + } else { + this._data = (this._data || '') + data; + } + } else { + this._data = data; + } -require.register("component~reactive@1.1.0/lib/utils.js", function (exports, module) { + if (!obj) return this; + if (!type) this.type('json'); + return this; +}; /** - * Module dependencies. + * Invoke the callback with `err` and `res` + * and handle arity check. + * + * @param {Error} err + * @param {Response} res + * @api private */ -var debug = require("visionmedia~debug@0.7.4")('reactive:utils'); -//var props = require("props"); +Request.prototype.callback = function(err, res){ + var fn = this._callback; + this.clearTimeout(); + if (2 == fn.length) return fn(err, res); + if (err) return this.emit('error', err); + fn(res); +}; /** - * Function cache. + * Invoke callback with x-domain error. + * + * @api private */ -var cache = {}; +Request.prototype.crossDomainError = function(){ + var err = new Error('Origin is not allowed by Access-Control-Allow-Origin'); + err.crossDomain = true; + this.callback(err); +}; /** - * Return possible properties of a string - * @param {String} str - * @return {Array} of properties found in the string + * Invoke callback with timeout error. + * * @api private */ -var props = function(str) { - return str - .replace(/\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '') - .match(/[a-zA-Z_]\w*([.][a-zA-Z_]\w*)*/g) - || []; + +Request.prototype.timeoutError = function(){ + var timeout = this._timeout; + var err = new Error('timeout of ' + timeout + 'ms exceeded'); + err.timeout = timeout; + this.callback(err); }; + /** - * Return interpolation property names in `str`, - * for example "{foo} and {bar}" would return - * ['foo', 'bar']. + * Enable transmission of cookies with x-domain requests. * - * @param {String} str - * @return {Array} - * @api private + * Note that for this to work the origin must not be + * using "Access-Control-Allow-Origin" with a wildcard, + * and also must set "Access-Control-Allow-Credentials" + * to "true". + * + * @api public */ -exports.interpolationProps = function(str) { - var m; - var arr = []; - var re = /\{([^}]+)\}/g; - - while (m = re.exec(str)) { - var expr = m[1]; - arr = arr.concat(props(expr)); - } - - return unique(arr); +Request.prototype.withCredentials = function(){ + this._withCredentials = true; + return this; }; /** - * Interpolate `str` with the given `fn`. + * Initiate request, invoking callback `fn(res)` + * with an instanceof `Response`. * - * @param {String} str * @param {Function} fn - * @return {String} - * @api private + * @return {Request} for chaining + * @api public */ -exports.interpolate = function(str, fn){ - return str.replace(/\{([^}]+)\}/g, function(_, expr){ - var cb = cache[expr]; - if (!cb) cb = cache[expr] = compile(expr); - var val = fn(expr.trim(), cb); - return val == null ? '' : val; - }); -}; +Request.prototype.end = function(fn){ + var self = this; + var xhr = this.xhr = getXHR(); + var query = this._query.join('&'); + var timeout = this._timeout; + var data = this._formData || this._data; -/** - * Check if `str` has interpolation. - * - * @param {String} str - * @return {Boolean} - * @api private - */ + // store callback + this._callback = fn || noop; -exports.hasInterpolation = function(str) { - return ~str.indexOf('{'); -}; + // state change + xhr.onreadystatechange = function(){ + if (4 != xhr.readyState) return; + if (0 == xhr.status) { + if (self.aborted) return self.timeoutError(); + return self.crossDomainError(); + } + self.emit('end'); + }; -/** - * Compile `expr` to a `Function`. - * - * @param {String} expr - * @return {Function} - * @api private - */ + // progress + if (xhr.upload) { + xhr.upload.onprogress = function(e){ + e.percent = e.loaded / e.total * 100; + self.emit('progress', e); + }; + } -function compile(expr) { - var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/g; - var p = props(expr); + // timeout + if (timeout && !this._timer) { + this._timer = setTimeout(function(){ + self.abort(); + }, timeout); + } - // replace function calls with [ ] syntax to avoid capture as property - var funCallRe = /.\w+ *\(/g; - var body = expr.replace(funCallRe, function(_) { - return '[\'' + _.slice(1, -1) + '\']('; - }); + // querystring + if (query) { + query = request.serializeObject(query); + this.url += ~this.url.indexOf('?') + ? '&' + query + : '?' + query; + } - var body = body.replace(re, function(_) { - if (p.indexOf(_) >= 0) { - return access(_); - }; + // initiate request + xhr.open(this.method, this.url, true); - return _; - }); + // CORS + if (this._withCredentials) xhr.withCredentials = true; - debug('compile `%s`', body); - return new Function('reactive', 'return ' + body); -} + // body + if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !isHost(data)) { + // serialize stuff + var serialize = request.serialize[this.getHeader('Content-Type')]; + if (serialize) data = serialize(data); + } + + // set header fields + for (var field in this.header) { + if (null == this.header[field]) continue; + xhr.setRequestHeader(field, this.header[field]); + } + + // send stuff + this.emit('request', this); + xhr.send(data); + return this; +}; /** - * Access a method `prop` with dot notation. - * - * @param {String} prop - * @return {String} - * @api private + * Expose `Request`. */ -function access(prop) { - return 'reactive.get(\'' + prop + '\')'; -} +request.Request = Request; /** - * Return unique array. + * Issue a request: * - * @param {Array} arr - * @return {Array} - * @api private + * Examples: + * + * request('GET', '/users').end(callback) + * request('/users').end(callback) + * request('/users', callback) + * + * @param {String} method + * @param {String|Function} url or callback + * @return {Request} + * @api public */ -function unique(arr) { - var ret = []; +function request(method, url) { + // callback + if ('function' == typeof url) { + return new Request('GET', method).end(url); + } - for (var i = 0; i < arr.length; i++) { - if (~ret.indexOf(arr[i])) continue; - ret.push(arr[i]); + // url first + if (1 == arguments.length) { + return new Request('GET', method); } - return ret; + return new Request(method, url); } -}); - -require.register("component~reactive@1.1.0/lib/text-binding.js", function (exports, module) { - /** - * Module dependencies. + * GET `url` with optional callback `fn(res)`. + * + * @param {String} url + * @param {Mixed|Function} data or fn + * @param {Function} fn + * @return {Request} + * @api public */ -var debug = require("visionmedia~debug@0.7.4")('reactive:text-binding'); -var utils = require("component~reactive@1.1.0/lib/utils.js"); +request.get = function(url, data, fn){ + var req = request('GET', url); + if ('function' == typeof data) fn = data, data = null; + if (data) req.query(data); + if (fn) req.end(fn); + return req; +}; /** - * Expose `TextBinding`. + * HEAD `url` with optional callback `fn(res)`. + * + * @param {String} url + * @param {Mixed|Function} data or fn + * @param {Function} fn + * @return {Request} + * @api public */ -module.exports = TextBinding; +request.head = function(url, data, fn){ + var req = request('HEAD', url); + if ('function' == typeof data) fn = data, data = null; + if (data) req.send(data); + if (fn) req.end(fn); + return req; +}; /** - * Initialize a new text binding. + * DELETE `url` with optional callback `fn(res)`. * - * @param {Reactive} view - * @param {Element} node - * @param {Attribute} attr - * @api private + * @param {String} url + * @param {Function} fn + * @return {Request} + * @api public */ -function TextBinding(reactive, node) { - this.reactive = reactive; - this.text = node.data; - this.node = node; - this.props = utils.interpolationProps(this.text); - this.subscribe(); - this.render(); -} +request.del = function(url, fn){ + var req = request('DELETE', url); + if (fn) req.end(fn); + return req; +}; /** - * Subscribe to changes. + * PATCH `url` with optional `data` and callback `fn(res)`. + * + * @param {String} url + * @param {Mixed} data + * @param {Function} fn + * @return {Request} + * @api public */ -TextBinding.prototype.subscribe = function(){ - var self = this; - var reactive = this.reactive; - this.props.forEach(function(prop){ - reactive.sub(prop, function(){ - self.render(); - }); - }); +request.patch = function(url, data, fn){ + var req = request('PATCH', url); + if ('function' == typeof data) fn = data, data = null; + if (data) req.send(data); + if (fn) req.end(fn); + return req; }; /** - * Render text. + * POST `url` with optional `data` and callback `fn(res)`. + * + * @param {String} url + * @param {Mixed} data + * @param {Function} fn + * @return {Request} + * @api public */ -TextBinding.prototype.render = function(){ - var node = this.node; - var text = this.text; - var reactive = this.reactive; - var model = reactive.model; - - // TODO: delegate most of this to `Reactive` - debug('render "%s"', text); - node.data = utils.interpolate(text, function(prop, fn){ - if (fn) { - return fn(reactive); - } else { - return reactive.get(prop); - } - }); +request.post = function(url, data, fn){ + var req = request('POST', url); + if ('function' == typeof data) fn = data, data = null; + if (data) req.send(data); + if (fn) req.end(fn); + return req; }; -}); - -require.register("component~reactive@1.1.0/lib/attr-binding.js", function (exports, module) { - -/** - * Module dependencies. - */ - -var debug = require("visionmedia~debug@0.7.4")('reactive:attr-binding'); -var utils = require("component~reactive@1.1.0/lib/utils.js"); - /** - * Expose `AttrBinding`. - */ - -module.exports = AttrBinding; - -/** - * Initialize a new attribute binding. + * PUT `url` with optional `data` and callback `fn(res)`. * - * @param {Reactive} view - * @param {Element} node - * @param {Attribute} attr - * @api private - */ - -function AttrBinding(reactive, node, attr) { - var self = this; - this.reactive = reactive; - this.node = node; - this.attr = attr; - this.text = attr.value; - this.props = utils.interpolationProps(this.text); - this.subscribe(); - this.render(); -} - -/** - * Subscribe to changes. + * @param {String} url + * @param {Mixed|Function} data or fn + * @param {Function} fn + * @return {Request} + * @api public */ -AttrBinding.prototype.subscribe = function(){ - var self = this; - var reactive = this.reactive; - this.props.forEach(function(prop){ - reactive.sub(prop, function(){ - self.render(); - }); - }); +request.put = function(url, data, fn){ + var req = request('PUT', url); + if ('function' == typeof data) fn = data, data = null; + if (data) req.send(data); + if (fn) req.end(fn); + return req; }; /** - * Render the value. + * Expose `request`. */ -AttrBinding.prototype.render = function(){ - var attr = this.attr; - var text = this.text; - var reactive = this.reactive; - var model = reactive.model; - - // TODO: delegate most of this to `Reactive` - debug('render %s "%s"', attr.name, text); - attr.value = utils.interpolate(text, function(prop, fn){ - if (fn) { - return fn(reactive); - } else { - return reactive.get(prop); - } - }); -}; +module.exports = request; }); -require.register("component~reactive@1.1.0/lib/binding.js", function (exports, module) { -var hasInterpolation = require("component~reactive@1.1.0/lib/utils.js").hasInterpolation; -var interpolationProps = require("component~reactive@1.1.0/lib/utils.js").interpolationProps; +require.register("component~search.js@1.1.0", function (exports, module) { -/** - * Expose `Binding`. - */ +var crawler = search.crawler = require('component~search.js@1.1.0/client/crawler.js'); +var fns = require('component~search.js@1.1.0/lib/functions.js'); -module.exports = Binding; +module.exports = search /** - * Initialize a binding. - * - * @api private + * Right now, `query` must be a string, + * and we'll search intelligently based on it. + * In the future, we should allow options + * like the node version. */ -function Binding(name, reactive, el, fn) { - this.name = name; - this.reactive = reactive; - this.model = reactive.model; - this.view = reactive.view; - this.el = el; - this.fn = fn; -} - -/** - * Apply the binding. - * - * @api private - */ +function search(query, limit) { + query = (query || '').toLowerCase().trim(); -Binding.prototype.bind = function() { - var val = this.el.getAttribute(this.name); - this.fn(this.el, val, this.model); -}; + // don't need to show every component + if (!query) { + return crawler.components + .sort(fns.sortBy.starsAndWatchers) + .slice(0, limit || 25); + } -/** - * Perform interpolation on `name`. - * - * @param {String} name - * @return {String} - * @api public - */ + // search by / + if (/^[\w-]+\//.test(query)) { + return crawler.components + .filter(function (json) { + return !json.github.full_name.indexOf(query); + }) + .sort(function (a, b) { + // sort by the length of the name, ascending + return a.github.full_name.length + - b.github.full_name.length; + }) + .slice(0, limit || 25) + } -Binding.prototype.interpolate = function(name) { - var self = this; + var filters = []; - if (~name.indexOf('{')) { - return name.replace(/{([^}]+)}/g, function(_, name){ - return self.value(name); - }); - } + filters.push(fns.filterBy.text(query)); - return self.value(name); -}; + query.split(/\s*/) + .filter(Boolean) + .filter(function (keyword) { + // check only alphanumerics + return /^\w+$/.test(keyword); + }) + .forEach(function (keyword) { + filters.push(fns.filterBy.keyword(keyword)); + }) -/** - * Return value for property `name`. - * - * - check if the "view" has a `name` method - * - check if the "model" has a `name` method - * - check if the "model" has a `name` property - * - * @param {String} name - * @return {Mixed} - * @api public - */ + return crawler.components + .filter(function (json) { + return filters.some(function (filter) { + return filter(json); + }); + }) + .sort(fns.sortBy.starsAndWatchers) + .slice(0, limit || 25); +} +}); -Binding.prototype.value = function(name) { - return this.reactive.get(name); -}; +require.register("component~search.js@1.1.0/client/crawler.js", function (exports, module) { /** - * Invoke `fn` on changes. - * - * @param {Function} fn - * @api public + * Load all the components from the crawler. */ -Binding.prototype.change = function(fn) { - fn.call(this); +var request = require('visionmedia~superagent@0.21.0'); - var self = this; - var reactive = this.reactive; - var val = this.el.getAttribute(this.name); +var loaded = false; +var loading = false; +var queue = []; - // interpolation - if (hasInterpolation(val)) { - var props = interpolationProps(val); - props.forEach(function(prop){ - reactive.sub(prop, fn.bind(self)); - }); - return; - } +module.exports = crawler; - // bind to prop - var prop = val; - reactive.sub(prop, fn.bind(this)); -}; +// automatically load all the crawled data on page load +crawler(); -}); +function crawler(done) { + done = done || noop; + if (loaded) return done(); + if (loading) return queue.push(done); + loading = true; -require.register("component~reactive@1.1.0/lib/bindings.js", function (exports, module) { -/** - * Module dependencies. - */ + request + .get('http://component-crawler.herokuapp.com/.json') + .end(function (err, res) { + crawler.users = res.body.users; + crawler.components = res.body.components; -var carry = require("yields~carry@0.0.1"); -var classes = require("component~classes@1.1.1"); -var event = require("component~event@0.1.0"); + loaded = true; + loading = false; + done(); + while (queue.length) queue.shift()(); + }) +} -var each_binding = require("component~reactive@1.1.0/lib/each.js"); +function noop(){} -/** - * Attributes supported. - */ +}); -var attrs = [ - 'id', - 'src', - 'rel', - 'cols', - 'rows', - 'name', - 'href', - 'title', - 'class', - 'style', - 'width', - 'value', - 'height', - 'tabindex', - 'placeholder' -]; - -/** - * Events supported. - */ - -var events = [ - 'change', - 'click', - 'dblclick', - 'mousedown', - 'mouseup', - 'mouseenter', - 'mouseleave', - 'scroll', - 'blur', - 'focus', - 'input', - 'submit', - 'keydown', - 'keypress', - 'keyup' -]; - -/** - * Apply bindings. - */ - -module.exports = function(reactive){ - - reactive.bind('each', each_binding); - - /** - * Generate attribute bindings. - */ - - attrs.forEach(function(attr){ - reactive.bind('data-' + attr, function(el, name, obj){ - this.change(function(){ - el.setAttribute(attr, this.interpolate(name)); - }); - }); - }); - - /** - * Show binding. - */ - - reactive.bind('data-visible', function(el, name){ - this.change(function(){ - var val = this.value(name); - if (val) { - classes(el).add('visible').remove('hidden'); - } else { - classes(el).remove('visible').add('hidden'); - } - }); - }); - - /** - * Hide binding. - */ - - reactive.bind('data-hidden', function(el, name){ - this.change(function(){ - var val = this.value(name); - if (val) { - classes(el).remove('visible').add('hidden'); - } else { - classes(el).add('visible').remove('hidden'); - } - }); - }); - - /** - * Checked binding. - */ - - reactive.bind('data-checked', function(el, name){ - this.change(function(){ - if (this.value(name)) { - el.setAttribute('checked', 'checked'); - } else { - el.removeAttribute('checked'); - } - }); - }); - - /** - * Text binding. - */ - - reactive.bind('data-text', function(el, name){ - this.change(function(){ - el.textContent = this.interpolate(name); - }); - }); - - /** - * HTML binding. - */ - - reactive.bind('data-html', function(el, name){ - this.change(function(){ - el.innerHTML = this.interpolate(name); - }); - }); +require.register("component~search.js@1.1.0/lib/functions.js", function (exports, module) { - /** - * Generate event bindings. - */ +exports.filterBy = { + owner: function (owner) { + return function (json) { + return json.github.owner.login === owner; + } + }, + keyword: function (keyword) { + return function (json) { + var keywords = json.keywords; + if (!keywords) return false; + return ~json.keywords.indexOf(keyword); + } + }, + text: function (string) { + string = escapeRegExp(string).trim(); + var terms = string.split(/\s+/); + var re = new RegExp(terms.join('.* .*'), 'i'); + return function (json) { + return re.test(json.name) + || re.test(json.description) + || re.test(json.github.full_name); + } + } +} - events.forEach(function(name){ - reactive.bind('on-' + name, function(el, method){ - var self = this; - var view = self.reactive.view; - event.bind(el, name, function(e){ - e.preventDefault(); +exports.sortBy = { + starsAndWatchers: function (a, b) { + return followScore(b) - followScore(a); + } +} - var fn = view[method]; - if (!fn) throw new Error('method .' + method + '() missing'); - fn.call(view, e, self.reactive); - }); - }); - }); +function followScore(json) { + var watchers = json.github.subscribers_count || 0; + var stars = json.github.stargazers_count || 0; + return watchers * 10 + stars; +} - /** - * Append child element. - */ +function escapeRegExp(str) { + return String(str).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); +} +}); - reactive.bind('data-append', function(el, name){ - var other = this.value(name); - el.appendChild(other); - }); +require.register("component~indexof@0.0.1", function (exports, module) { - /** - * Replace element, carrying over its attributes. - */ +var indexOf = [].indexOf; - reactive.bind('data-replace', function(el, name){ - var other = carry(this.value(name), el); - el.parentNode.replaceChild(other, el); - }); +module.exports = function(arr, obj){ + if (indexOf) return arr.indexOf(obj); + for (var i = 0; i < arr.length; ++i) { + if (arr[i] === obj) return i; + } + return -1; }; - }); -require.register("component~reactive@1.1.0/lib/adapter.js", function (exports, module) { - -function Adapter(obj) { - if (!(this instanceof Adapter)) { - return new Adapter(obj); +require.register("component~indexof@0.0.3", function (exports, module) { +module.exports = function(arr, obj){ + if (arr.indexOf) return arr.indexOf(obj); + for (var i = 0; i < arr.length; ++i) { + if (arr[i] === obj) return i; } - - var self = this; - self.obj = obj; + return -1; }; +}); + +require.register("component~classes@1.1.1", function (exports, module) { /** - * Default subscription method. - * Subscribe to changes on the model. - * - * @param {Object} obj - * @param {String} prop - * @param {Function} fn + * Module dependencies. */ -Adapter.prototype.subscribe = function(prop, fn) { -}; +var index = require('component~indexof@0.0.3'); /** - * Default unsubscription method. - * Unsubscribe from changes on the model. + * Whitespace regexp. */ -Adapter.prototype.unsubscribe = function(prop, fn) { -}; +var re = /\s+/; /** - * Remove all subscriptions on this adapter + * toString reference. */ -Adapter.prototype.unsubscribeAll = function() { -}; +var toString = Object.prototype.toString; /** - * Default setter method. - * Set a property on the model. + * Wrap `el` in a `ClassList`. * - * @param {Object} obj - * @param {String} prop - * @param {Mixed} val + * @param {Element} el + * @return {ClassList} + * @api public */ -Adapter.prototype.set = function(prop, val) { - var obj = this.obj; - if (!obj) return; - if ('function' == typeof obj[prop]) { - obj[prop](val); - } - else if ('function' == typeof obj.set) { - obj.set(prop, val); - } - else { - obj[prop] = val; - } +module.exports = function(el){ + return new ClassList(el); }; /** - * Default getter method. - * Get a property from the model. + * Initialize a new ClassList for `el`. * - * @param {Object} obj - * @param {String} prop - * @return {Mixed} + * @param {Element} el + * @api private */ -Adapter.prototype.get = function(prop) { - var obj = this.obj; - if (!obj) { - return undefined; +function ClassList(el) { + this.el = el; + this.list = el.classList; +} + +/** + * Add class `name` if not already present. + * + * @param {String} name + * @return {ClassList} + * @api public + */ + +ClassList.prototype.add = function(name){ + // classList + if (this.list) { + this.list.add(name); + return this; } - // split property on '.' access - // and dig into the object - var parts = prop.split('.'); - var part = parts.shift(); - do { - if (typeof obj[part] === 'function') { - obj = obj[part].call(obj); - } - else { - obj = obj[part]; - } + // fallback + var arr = this.array(); + var i = index(arr, name); + if (!~i) arr.push(name); + this.el.className = arr.join(' '); + return this; +}; - if (!obj) { - return undefined; +/** + * Remove class `name` when present, or + * pass a regular expression to remove + * any which match. + * + * @param {String|RegExp} name + * @return {ClassList} + * @api public + */ + +ClassList.prototype.remove = function(name){ + if ('[object RegExp]' == toString.call(name)) { + return this.removeMatching(name); + } + + // classList + if (this.list) { + this.list.remove(name); + return this; + } + + // fallback + var arr = this.array(); + var i = index(arr, name); + if (~i) arr.splice(i, 1); + this.el.className = arr.join(' '); + return this; +}; + +/** + * Remove all classes matching `re`. + * + * @param {RegExp} re + * @return {ClassList} + * @api private + */ + +ClassList.prototype.removeMatching = function(re){ + var arr = this.array(); + for (var i = 0; i < arr.length; i++) { + if (re.test(arr[i])) { + this.remove(arr[i]); } + } + return this; +}; - part = parts.shift(); - } while(part); +/** + * Toggle class `name`. + * + * @param {String} name + * @return {ClassList} + * @api public + */ - return obj; +ClassList.prototype.toggle = function(name){ + // classList + if (this.list) { + this.list.toggle(name); + return this; + } + + // fallback + if (this.has(name)) { + this.remove(name); + } else { + this.add(name); + } + return this; }; -module.exports = Adapter; +/** + * Return an array of classes. + * + * @return {Array} + * @api public + */ + +ClassList.prototype.array = function(){ + var arr = this.el.className.split(re); + if ('' === arr[0]) arr.pop(); + return arr; +}; + +/** + * Check if class `name` is present. + * + * @param {String} name + * @return {ClassList} + * @api public + */ + +ClassList.prototype.has = +ClassList.prototype.contains = function(name){ + return this.list + ? this.list.contains(name) + : !! ~index(this.array(), name); +}; }); -require.register("component~reactive@1.1.0/lib/each.js", function (exports, module) { -// 'each' binding -module.exports = function(el, val) { - var self = this; +require.register("component~query@0.0.1", function (exports, module) { - // get the reactive constructor from the current reactive instance - // TODO(shtylman) port over adapter and bindings from instance? - var Reactive = self.reactive.constructor; +function one(selector, el) { + return el.querySelector(selector); +} - var val = val.split(/ +/); - el.removeAttribute('each'); +exports = module.exports = function(selector, el){ + el = el || document; + return one(selector, el); +}; - var name = val[0]; - var prop = val[0]; +exports.all = function(selector, el){ + el = el || document; + return el.querySelectorAll(selector); +}; - if (val.length > 1) { - name = val[0]; - prop = val[2]; - } +exports.engine = function(obj){ + if (!obj.one) throw new Error('.one callback required'); + if (!obj.all) throw new Error('.all callback required'); + one = obj.one; + exports.all = obj.all; +}; - var parent = el.parentNode; +}); - // use text node to hold where end of list should be - var placeholder = document.createTextNode(''); - parent.insertBefore(placeholder, el); - parent.removeChild(el); +require.register("yields~merge-attrs@0.0.1", function (exports, module) { - // the reactive views we created for our array - // one per array item - // the length of this MUST always match the length of the 'arr' - // and mutates with 'arr' - var views = []; +/** + * Export `merge` + */ - function childView(el, model) { - return Reactive(el, model, { - delegate: self.view, - adapter: self.reactive.opt.adapter, - bindings: self.reactive.bindings - }); +module.exports = merge; + +/** + * Merge `b`'s attrs into `a`. + * + * @param {Element} a + * @param {Element} b + * @api public + */ + +function merge(a, b){ + for (var i = 0; i < b.attributes.length; ++i) { + var attr = b.attributes[i]; + if (ignore(a, attr)) continue; + a.setAttribute(attr.name, attr.value); + } +} + +/** + * Check if `attr` should be ignored. + * + * @param {Element} a + * @param {Attr} attr + * @return {Boolean} + * @api private + */ + +function ignore(a, attr){ + return !attr.specified + || 'class' == attr.name + || 'id' == attr.name + || a.hasAttribute(attr.name); +} + +}); + +require.register("yields~uniq@1.0.0", function (exports, module) { + +/** + * dependencies + */ + +try { + var indexOf = require('component~indexof@0.0.1'); +} catch(e){ + var indexOf = require('indexof-component'); +} + +/** + * Create duplicate free array + * from the provided `arr`. + * + * @param {Array} arr + * @param {Array} select + * @return {Array} + */ + +module.exports = function (arr, select) { + var len = arr.length, ret = [], v; + select = select ? (select instanceof Array ? select : [select]) : false; + + for (var i = 0; i < len; i++) { + v = arr[i]; + if (select && !~indexOf(select, v)) { + ret.push(v); + } else if (!~indexOf(ret, v)) { + ret.push(v); } + } + return ret; +}; - var array; +}); - // bind entire new array - function change(arr) { +require.register("yields~carry@0.0.1", function (exports, module) { + +/** + * dependencies + */ + +var merge = require('yields~merge-attrs@0.0.1') + , classes = require('component~classes@1.1.1') + , uniq = require('yields~uniq@1.0.0'); + +/** + * Export `carry` + */ + +module.exports = carry; + +/** + * Carry over attrs and classes + * from `b` to `a`. + * + * @param {Element} a + * @param {Element} b + * @return {Element} + * @api public + */ + +function carry(a, b){ + if (!a) return b.cloneNode(); + carry.attrs(a, b); + carry.classes(a, b); + return a; +} + +/** + * Carry attributes. + * + * @param {Element} a + * @param {Element} b + * @return {Element} a + * @api public + */ + +carry.attrs = function(a, b){ + merge(a, b); + return a; +}; + +/** + * Carry over classes. + * + * @param {Element} a + * @param {Element} b + * @return {Element} a + * @api public + */ + +carry.classes = function(a, b){ + if (a.className == b.className) return a; + var blist = classes(b).array(); + var alist = classes(a).array(); + var list = alist.concat(blist); + a.className = uniq(list).join(' '); + return a; +}; + +}); + +require.register("visionmedia~debug@0.7.4", function (exports, module) { +if ('undefined' == typeof window) { + module.exports = require('./lib/debug'); +} else { + module.exports = require('visionmedia~debug@0.7.4/debug.js'); +} + +}); - // remove any old bindings/views - views.forEach(function(view) { - view.destroy(); - }); - views = []; +require.register("visionmedia~debug@0.7.4/debug.js", function (exports, module) { - // remove any old array observers - if (array) { - unpatchArray(array); - } - patchArray(arr); - array = arr; +/** + * Expose `debug()` as the module. + */ - // handle initial array - var fragment = document.createDocumentFragment(); - arr.forEach(function(obj) { - var clone = el.cloneNode(true); - var view = childView(clone, obj); - views.push(view); - fragment.appendChild(clone); - }); - parent.insertBefore(fragment, placeholder); - } +module.exports = debug; - function unpatchArray(arr) { - delete arr.splice; - delete arr.push; - delete arr.unshift; - } +/** + * Create a debugger with the given `name`. + * + * @param {String} name + * @return {Type} + * @api public + */ - function patchArray(arr) { - // splice will replace the current arr.splice function - // so that we can intercept modifications - var old_splice = arr.splice; - // idx -> index to start operation - // how many -> elements to remove - // ... elements to insert - // return removed elements - var splice = function(idx, how_many) { - var args = Array.prototype.slice.apply(arguments); +function debug(name) { + if (!debug.enabled(name)) return function(){}; - // new items to insert if any - var new_items = args.slice(2); + return function(fmt){ + fmt = coerce(fmt); - var place = placeholder; - if (idx < views.length) { - place = views[idx].el; - } + var curr = new Date; + var ms = curr - (debug[name] || curr); + debug[name] = curr; - // make views for these items - var new_views = new_items.map(function(item) { - var clone = el.cloneNode(true); - return childView(clone, item); - }); + fmt = name + + ' ' + + fmt + + ' +' + debug.humanize(ms); - var splice_args = [idx, how_many].concat(new_views); + // This hackery is required for IE8 + // where `console.log` doesn't have 'apply' + window.console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); + } +} - var removed = views.splice.apply(views, splice_args); +/** + * The currently active debug mode names. + */ - var frag = document.createDocumentFragment(); - // insert into appropriate place - // first removed item is where to insert - new_views.forEach(function(view) { - frag.appendChild(view.el); - }); +debug.names = []; +debug.skips = []; - // insert before a specific location - // the location is defined by the element at idx - parent.insertBefore(frag, place); +/** + * Enables a debug mode by name. This can include modes + * separated by a colon and wildcards. + * + * @param {String} name + * @api public + */ - // remove after since we may need the element for 'placement' - // of the new document fragment - removed.forEach(function(view) { - view.destroy(); - }); +debug.enable = function(name) { + try { + localStorage.debug = name; + } catch(e){} - var ret = old_splice.apply(arr, args); + var split = (name || '').split(/[\s,]+/) + , len = split.length; - // set the length property of the array - // so that any listeners can pick up on it - self.reactive.set(prop + '.length', arr.length); - return ret; - }; + for (var i = 0; i < len; i++) { + name = split[i].replace('*', '.*?'); + if (name[0] === '-') { + debug.skips.push(new RegExp('^' + name.substr(1) + '$')); + } + else { + debug.names.push(new RegExp('^' + name + '$')); + } + } +}; - /// existing methods can be implemented via splice +/** + * Disable debug output. + * + * @api public + */ - var push = function(el1, el2) { - var args = Array.prototype.slice.apply(arguments); - var len = arr.length; +debug.disable = function(){ + debug.enable(''); +}; - var splice_args = [len, 0].concat(args) - splice.apply(arr, splice_args); - return arr.length; - }; +/** + * Humanize the given `ms`. + * + * @param {Number} m + * @return {String} + * @api private + */ - var unshift = function(el1, el2) { - var args = Array.prototype.slice.apply(arguments); - var len = arr.length; +debug.humanize = function(ms) { + var sec = 1000 + , min = 60 * 1000 + , hour = 60 * min; - var splice_args = [0, 0].concat(args) - splice.apply(arr, splice_args); - return arr.length; - }; + if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; + if (ms >= min) return (ms / min).toFixed(1) + 'm'; + if (ms >= sec) return (ms / sec | 0) + 's'; + return ms + 'ms'; +}; - // use defineProperty to avoid making ownProperty fields - function set_prop(prop, fn) { - Object.defineProperty(arr, prop, { - enumerable: false, - writable: true, - configurable: true, - value: fn - }); - } +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ - set_prop('splice', splice); - set_prop('push', push); - set_prop('unshift', unshift); +debug.enabled = function(name) { + for (var i = 0, len = debug.skips.length; i < len; i++) { + if (debug.skips[i].test(name)) { + return false; + } + } + for (var i = 0, len = debug.names.length; i < len; i++) { + if (debug.names[i].test(name)) { + return true; } + } + return false; +}; - change(self.reactive.get(prop) || []); - self.skip = true; +/** + * Coerce `val`. + */ - self.reactive.sub(prop, change); -}; +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} +// persist + +try { + if (window.localStorage) debug.enable(localStorage.debug); +} catch(e){} }); -require.register("component~reactive@1.1.0/lib/walk.js", function (exports, module) { +require.register("component~reactive@1.1.0", function (exports, module) { /** - * @api private + * Module dependencies. */ -module.exports = function walk(el, process, done) { - var end = done || function(){}; - var nodes = [].slice.call(el.childNodes); - function next(stop){ - if (stop || nodes.length === 0) { - return end(); +var Emitter = require('component~emitter@1.1.1'); +var query = require('component~query@0.0.1'); +var domify = require('component~domify@1.2.1'); +var debug = require('visionmedia~debug@0.7.4')('reactive'); + +var Adapter = require('component~reactive@1.1.0/lib/adapter.js'); +var AttrBinding = require('component~reactive@1.1.0/lib/attr-binding.js'); +var TextBinding = require('component~reactive@1.1.0/lib/text-binding.js'); +var bindings = require('component~reactive@1.1.0/lib/bindings.js'); +var Binding = require('component~reactive@1.1.0/lib/binding.js'); +var utils = require('component~reactive@1.1.0/lib/utils.js'); +var walk = require('component~reactive@1.1.0/lib/walk.js'); + +/** + * Expose `Reactive`. + */ + +exports = module.exports = Reactive; + +/** + * Initialize a reactive template for `el` and `obj`. + * + * @param {Element} el + * @param {Element} obj + * @param {Object} options + * @api public + */ + +function Reactive(el, model, opt) { + if (!(this instanceof Reactive)) return new Reactive(el, model, opt); + opt = opt || {}; + + if (typeof el === 'string') { + el = domify(el); + } + + var self = this; + self.opt = opt || {}; + self.model = model || {}; + self.adapter = (opt.adapter || Adapter)(self.model); + self.el = el; + self.view = opt.delegate || Object.create(null); + + self.bindings = opt.bindings || Object.create(null); + + // TODO undo this crap and just export bindings regularly + // not that binding order matters!! + bindings({ + bind: function(name, fn) { + self.bindings[name] = fn; } - walk(nodes.shift(), process, next); - } + }); - process(el, next); + self._bind(this.el, []); } -}); - -require.register("component~reduce@1.0.1", function (exports, module) { +Emitter(Reactive.prototype); /** - * Reduce `arr` with `fn`. + * Subscribe to changes on `prop`. * - * @param {Array} arr + * @param {String} prop * @param {Function} fn - * @param {Mixed} initial - * - * TODO: combatible error handling? + * @return {Reactive} + * @api private */ -module.exports = function(arr, fn, initial){ - var idx = 0; - var len = arr.length; - var curr = arguments.length == 3 - ? initial - : arr[idx++]; +Reactive.prototype.sub = function(prop, fn){ + var self = this; - while (idx < len) { - curr = fn.call(null, curr, arr[idx], ++idx, arr); + debug('subscribe %s', prop); + + // if we have parts, we need to subscribe to the parent as well + // TODO (defunctzombie) multiple levels of properties + var parts = prop.split('.'); + if (parts.length > 1) { + self.sub(parts[0], fn); } - - return curr; -}; -}); -require.register("visionmedia~superagent@0.17.0", function (exports, module) { -/** - * Module dependencies. - */ + // for when reactive changes the property + this.on('change ' + prop, fn); -var Emitter = require("component~emitter@1.1.2"); -var reduce = require("component~reduce@1.0.1"); + // for when the property changed within the adapter + this.adapter.subscribe(prop, function() { + // skip items set internally from calling function twice + if (self._internal_set) return; -/** - * Root reference for iframes. - */ + fn.apply(this, arguments); + }); -var root = 'undefined' == typeof window - ? this - : window; + return this; +}; /** - * Noop. + * Unsubscribe to changes from `prop`. + * + * @param {String} prop + * @param {Function} fn + * @return {Reactive} + * @api private */ -function noop(){}; +Reactive.prototype.unsub = function(prop, fn){ + this.off('change ' + prop, fn); + this.adapter.unsubscribe(prop, fn); + + return this; +}; /** - * Check if `obj` is a host object, - * we don't want to serialize these :) - * - * TODO: future proof, move to compoent land + * Get a `prop` * - * @param {Object} obj - * @return {Boolean} + * @param {String} prop + * @param {Mixed} val + * @return {Mixed} * @api private */ -function isHost(obj) { - var str = {}.toString.call(obj); - - switch (str) { - case '[object File]': - case '[object Blob]': - case '[object FormData]': - return true; - default: - return false; +Reactive.prototype.get = function(prop) { + if (prop === 'this') { + return this.model; } -} -/** - * Determine XHR. - */ + // model takes precedence + var modelVal = this.adapter.get(prop); + if (modelVal) { + return modelVal; + } -function getXHR() { - if (root.XMLHttpRequest - && ('file:' != root.location.protocol || !root.ActiveXObject)) { - return new XMLHttpRequest; - } else { - try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {} - try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {} - try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {} - try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {} + var view = this.view; + var viewVal = view[prop]; + if ('function' == typeof viewVal) { + return viewVal.call(view); + } + else if (viewVal) { + return viewVal; } - return false; -} + + return undefined; +}; /** - * Removes leading and trailing whitespace, added to support IE. + * Set a `prop` * - * @param {String} s - * @return {String} + * @param {String} prop + * @param {Mixed} val + * @return {Reactive} * @api private */ -var trim = ''.trim - ? function(s) { return s.trim(); } - : function(s) { return s.replace(/(^\s*|\s*$)/g, ''); }; +Reactive.prototype.set = function(prop, val) { + var self = this; + // internal set flag lets reactive updates know to avoid triggering + // updates for the Adapter#set call + // we will already trigger updates with the change event + self._internal_set = true; + if( "object" == typeof prop) { + Object.keys(prop).forEach(function(name){ + self.set(name, prop[name]); + }); + } + else { + self.adapter.set(prop, val); + self.emit('change ' + prop, val); + } + self._internal_set = false; + return self; +}; /** - * Check if `obj` is an object. + * Traverse and bind all interpolation within attributes and text. * - * @param {Object} obj - * @return {Boolean} + * @param {Element} el * @api private */ -function isObject(obj) { - return obj === Object(obj); -} +Reactive.prototype.bindInterpolation = function(el, els){ + + // element + if (el.nodeType == 1) { + for (var i = 0; i < el.attributes.length; i++) { + var attr = el.attributes[i]; + if (utils.hasInterpolation(attr.value)) { + new AttrBinding(this, el, attr); + } + } + } + + // text node + if (el.nodeType == 3) { + if (utils.hasInterpolation(el.data)) { + debug('bind text "%s"', el.data); + new TextBinding(this, el); + } + } + + // walk nodes + for (var i = 0; i < el.childNodes.length; i++) { + var node = el.childNodes[i]; + this.bindInterpolation(node, els); + } +}; + +Reactive.prototype._bind = function() { + var self = this; + + var bindings = self.bindings; + + walk(self.el, function(el, next) { + // element + if (el.nodeType == 1) { + var skip = false; + + var attrs = {}; + for (var i = 0; i < el.attributes.length; ++i) { + var attr = el.attributes[i]; + var name = attr.name; + attrs[name] = attr; + } + + // bindings must be iterated first + // to see if any request skipping + // only then can we see about attributes + Object.keys(bindings).forEach(function(name) { + if (!attrs[name] || skip) { + return; + } + + debug('bind [%s]', name); + + var prop = attrs[name].value; + var binding_fn = bindings[name]; + if (!binding_fn) { + return; + } + + var binding = new Binding(name, self, el, binding_fn); + binding.bind(); + if (binding.skip) { + skip = true; + } + }); + + if (skip) { + return next(skip); + } + + // if we are not skipping + // bind any interpolation attrs + for (var i = 0; i < el.attributes.length; ++i) { + var attr = el.attributes[i]; + var name = attr.name; + if (utils.hasInterpolation(attr.value)) { + new AttrBinding(self, el, attr); + } + } + + return next(skip); + } + // text + else if (el.nodeType == 3) { + if (utils.hasInterpolation(el.data)) { + debug('bind text "%s"', el.data); + new TextBinding(self, el); + } + } + + next(); + }); +}; /** - * Serialize the given `obj`. + * Bind `name` to `fn`. * - * @param {Object} obj - * @return {String} - * @api private + * @param {String|Object} name or object + * @param {Function} fn + * @api public */ -function serialize(obj) { - if (!isObject(obj)) return obj; - var pairs = []; - for (var key in obj) { - if (null != obj[key]) { - pairs.push(encodeURIComponent(key) - + '=' + encodeURIComponent(obj[key])); +Reactive.prototype.bind = function(name, fn) { + var self = this; + if ('object' == typeof name) { + for (var key in name) { + this.bind(key, name[key]); } + return; } - return pairs.join('&'); -} - -/** - * Expose serialization method. - */ - request.serializeObject = serialize; - - /** - * Parse the given x-www-form-urlencoded `str`. - * - * @param {String} str - * @return {Object} - * @api private - */ - -function parseString(str) { - var obj = {}; - var pairs = str.split('&'); - var parts; - var pair; - - for (var i = 0, len = pairs.length; i < len; ++i) { - pair = pairs[i]; - parts = pair.split('='); - obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); + var els = query.all('[' + name + ']', this.el); + if (this.el.hasAttribute && this.el.hasAttribute(name)) { + els = [].slice.call(els); + els.unshift(this.el); } + if (!els.length) return; - return obj; -} + debug('bind [%s] (%d elements)', name, els.length); + for (var i = 0; i < els.length; i++) { + var binding = new Binding(name, this, els[i], fn); + binding.bind(); + } +}; /** - * Expose parser. + * Destroy the binding + * - Removes the element from the dom (if inserted) + * - unbinds any event listeners + * + * @api public */ -request.parseString = parseString; +Reactive.prototype.destroy = function() { + var self = this; -/** - * Default MIME type map. - * - * superagent.types.xml = 'application/xml'; - * - */ + if (self.el.parentNode) { + self.el.parentNode.removeChild(self.el); + } -request.types = { - html: 'text/html', - json: 'application/json', - xml: 'application/xml', - urlencoded: 'application/x-www-form-urlencoded', - 'form': 'application/x-www-form-urlencoded', - 'form-data': 'application/x-www-form-urlencoded' + self.adapter.unsubscribeAll(); + self.emit('destroyed'); + self.removeAllListeners(); }; /** - * Default serialization map. - * - * superagent.serialize['application/xml'] = function(obj){ - * return 'generated xml here'; - * }; + * Use middleware * + * @api public */ - request.serialize = { - 'application/x-www-form-urlencoded': serialize, - 'application/json': JSON.stringify - }; +Reactive.prototype.use = function(fn) { + fn(this); + return this; +}; - /** - * Default parsers. - * - * superagent.parse['application/xml'] = function(str){ - * return { object parsed from str }; - * }; - * - */ +}); -request.parse = { - 'application/x-www-form-urlencoded': parseString, - 'application/json': JSON.parse -}; +require.register("component~reactive@1.1.0/lib/utils.js", function (exports, module) { /** - * Parse the given header `str` into - * an object containing the mapped fields. - * - * @param {String} str - * @return {Object} - * @api private + * Module dependencies. */ -function parseHeader(str) { - var lines = str.split(/\r?\n/); - var fields = {}; - var index; - var line; - var field; - var val; - - lines.pop(); // trailing CRLF - - for (var i = 0, len = lines.length; i < len; ++i) { - line = lines[i]; - index = line.indexOf(':'); - field = line.slice(0, index).toLowerCase(); - val = trim(line.slice(index + 1)); - fields[field] = val; - } - - return fields; -} +var debug = require('visionmedia~debug@0.7.4')('reactive:utils'); +//var props = require('props'); /** - * Return the mime type for the given `str`. - * - * @param {String} str - * @return {String} - * @api private + * Function cache. */ -function type(str){ - return str.split(/ *; */).shift(); -}; +var cache = {}; /** - * Return header field parameters. - * + * Return possible properties of a string * @param {String} str - * @return {Object} + * @return {Array} of properties found in the string * @api private */ - -function params(str){ - return reduce(str.split(/ *; */), function(obj, str){ - var parts = str.split(/ *= */) - , key = parts.shift() - , val = parts.shift(); - - if (key && val) obj[key] = val; - return obj; - }, {}); +var props = function(str) { + return str + .replace(/\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '') + .match(/[a-zA-Z_]\w*([.][a-zA-Z_]\w*)*/g) + || []; }; - /** - * Initialize a new `Response` with the given `xhr`. - * - * - set flags (.ok, .error, etc) - * - parse header - * - * Examples: - * - * Aliasing `superagent` as `request` is nice: - * - * request = superagent; - * - * We can use the promise-like API, or pass callbacks: - * - * request.get('/').end(function(res){}); - * request.get('/', function(res){}); - * - * Sending data can be chained: - * - * request - * .post('/user') - * .send({ name: 'tj' }) - * .end(function(res){}); - * - * Or passed to `.send()`: - * - * request - * .post('/user') - * .send({ name: 'tj' }, function(res){}); - * - * Or passed to `.post()`: - * - * request - * .post('/user', { name: 'tj' }) - * .end(function(res){}); - * - * Or further reduced to a single call for simple cases: - * - * request - * .post('/user', { name: 'tj' }, function(res){}); + * Return interpolation property names in `str`, + * for example "{foo} and {bar}" would return + * ['foo', 'bar']. * - * @param {XMLHTTPRequest} xhr - * @param {Object} options + * @param {String} str + * @return {Array} * @api private */ -function Response(req, options) { - options = options || {}; - this.req = req; - this.xhr = this.req.xhr; - this.text = this.xhr.responseText; - this.setStatusProperties(this.xhr.status); - this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders()); - // getAllResponseHeaders sometimes falsely returns "" for CORS requests, but - // getResponseHeader still works. so we get content-type even if getting - // other headers fails. - this.header['content-type'] = this.xhr.getResponseHeader('content-type'); - this.setHeaderProperties(this.header); - this.body = this.req.method != 'HEAD' - ? this.parseBody(this.text) - : null; -} +exports.interpolationProps = function(str) { + var m; + var arr = []; + var re = /\{([^}]+)\}/g; -/** - * Get case-insensitive `field` value. - * - * @param {String} field - * @return {String} - * @api public - */ + while (m = re.exec(str)) { + var expr = m[1]; + arr = arr.concat(props(expr)); + } -Response.prototype.get = function(field){ - return this.header[field.toLowerCase()]; + return unique(arr); }; /** - * Set header related properties: - * - * - `.type` the content type without params - * - * A response of "Content-Type: text/plain; charset=utf-8" - * will provide you with a `.type` of "text/plain". + * Interpolate `str` with the given `fn`. * - * @param {Object} header + * @param {String} str + * @param {Function} fn + * @return {String} * @api private */ -Response.prototype.setHeaderProperties = function(header){ - // content-type - var ct = this.header['content-type'] || ''; - this.type = type(ct); - - // params - var obj = params(ct); - for (var key in obj) this[key] = obj[key]; +exports.interpolate = function(str, fn){ + return str.replace(/\{([^}]+)\}/g, function(_, expr){ + var cb = cache[expr]; + if (!cb) cb = cache[expr] = compile(expr); + var val = fn(expr.trim(), cb); + return val == null ? '' : val; + }); }; /** - * Parse the given body `str`. - * - * Used for auto-parsing of bodies. Parsers - * are defined on the `superagent.parse` object. + * Check if `str` has interpolation. * * @param {String} str - * @return {Mixed} + * @return {Boolean} * @api private */ -Response.prototype.parseBody = function(str){ - var parse = request.parse[this.type]; - return parse - ? parse(str) - : null; +exports.hasInterpolation = function(str) { + return ~str.indexOf('{'); }; /** - * Set flags such as `.ok` based on `status`. - * - * For example a 2xx response will give you a `.ok` of __true__ - * whereas 5xx will be __false__ and `.error` will be __true__. The - * `.clientError` and `.serverError` are also available to be more - * specific, and `.statusType` is the class of error ranging from 1..5 - * sometimes useful for mapping respond colors etc. - * - * "sugar" properties are also defined for common cases. Currently providing: - * - * - .noContent - * - .badRequest - * - .unauthorized - * - .notAcceptable - * - .notFound + * Compile `expr` to a `Function`. * - * @param {Number} status + * @param {String} expr + * @return {Function} * @api private */ -Response.prototype.setStatusProperties = function(status){ - var type = status / 100 | 0; +function compile(expr) { + var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/g; + var p = props(expr); - // status / class - this.status = status; - this.statusType = type; + // replace function calls with [ ] syntax to avoid capture as property + var funCallRe = /.\w+ *\(/g; + var body = expr.replace(funCallRe, function(_) { + return '[\'' + _.slice(1, -1) + '\']('; + }); - // basics - this.info = 1 == type; - this.ok = 2 == type; - this.clientError = 4 == type; - this.serverError = 5 == type; - this.error = (4 == type || 5 == type) - ? this.toError() - : false; + var body = body.replace(re, function(_) { + if (p.indexOf(_) >= 0) { + return access(_); + }; - // sugar - this.accepted = 202 == status; - this.noContent = 204 == status || 1223 == status; - this.badRequest = 400 == status; - this.unauthorized = 401 == status; - this.notAcceptable = 406 == status; - this.notFound = 404 == status; - this.forbidden = 403 == status; -}; + return _; + }); + + debug('compile `%s`', body); + return new Function('reactive', 'return ' + body); +} /** - * Return an `Error` representative of this response. + * Access a method `prop` with dot notation. * - * @return {Error} - * @api public + * @param {String} prop + * @return {String} + * @api private */ -Response.prototype.toError = function(){ - var req = this.req; - var method = req.method; - var path = req.path; - - var msg = 'cannot ' + method + ' ' + path + ' (' + this.status + ')'; - var err = new Error(msg); - err.status = this.status; - err.method = method; - err.path = path; - - return err; -}; +function access(prop) { + return 'reactive.get(\'' + prop + '\')'; +} /** - * Expose `Response`. + * Return unique array. + * + * @param {Array} arr + * @return {Array} + * @api private */ -request.Response = Response; +function unique(arr) { + var ret = []; -/** - * Initialize a new `Request` with the given `method` and `url`. - * - * @param {String} method - * @param {String} url - * @api public - */ + for (var i = 0; i < arr.length; i++) { + if (~ret.indexOf(arr[i])) continue; + ret.push(arr[i]); + } -function Request(method, url) { - var self = this; - Emitter.call(this); - this._query = this._query || []; - this.method = method; - this.url = url; - this.header = {}; - this._header = {}; - this.on('end', function(){ - var res = new Response(self); - if ('HEAD' == method) res.text = null; - self.callback(null, res); - }); + return ret; } -/** - * Mixin `Emitter`. - */ +}); -Emitter(Request.prototype); +require.register("component~reactive@1.1.0/lib/text-binding.js", function (exports, module) { /** - * Allow for extension + * Module dependencies. */ -Request.prototype.use = function(fn) { - fn(this); - return this; -} +var debug = require('visionmedia~debug@0.7.4')('reactive:text-binding'); +var utils = require('component~reactive@1.1.0/lib/utils.js'); /** - * Set timeout to `ms`. - * - * @param {Number} ms - * @return {Request} for chaining - * @api public + * Expose `TextBinding`. */ -Request.prototype.timeout = function(ms){ - this._timeout = ms; - return this; -}; +module.exports = TextBinding; /** - * Clear previous timeout. + * Initialize a new text binding. * - * @return {Request} for chaining - * @api public + * @param {Reactive} view + * @param {Element} node + * @param {Attribute} attr + * @api private */ -Request.prototype.clearTimeout = function(){ - this._timeout = 0; - clearTimeout(this._timer); - return this; -}; +function TextBinding(reactive, node) { + this.reactive = reactive; + this.text = node.data; + this.node = node; + this.props = utils.interpolationProps(this.text); + this.subscribe(); + this.render(); +} /** - * Abort the request, and clear potential timeout. - * - * @return {Request} - * @api public + * Subscribe to changes. */ -Request.prototype.abort = function(){ - if (this.aborted) return; - this.aborted = true; - this.xhr.abort(); - this.clearTimeout(); - this.emit('abort'); - return this; +TextBinding.prototype.subscribe = function(){ + var self = this; + var reactive = this.reactive; + this.props.forEach(function(prop){ + reactive.sub(prop, function(){ + self.render(); + }); + }); }; /** - * Set header `field` to `val`, or multiple fields with one object. - * - * Examples: - * - * req.get('/') - * .set('Accept', 'application/json') - * .set('X-API-Key', 'foobar') - * .end(callback); - * - * req.get('/') - * .set({ Accept: 'application/json', 'X-API-Key': 'foobar' }) - * .end(callback); - * - * @param {String|Object} field - * @param {String} val - * @return {Request} for chaining - * @api public + * Render text. */ -Request.prototype.set = function(field, val){ - if (isObject(field)) { - for (var key in field) { - this.set(key, field[key]); - } - return this; - } - this._header[field.toLowerCase()] = val; - this.header[field] = val; - return this; +TextBinding.prototype.render = function(){ + var node = this.node; + var text = this.text; + var reactive = this.reactive; + var model = reactive.model; + + // TODO: delegate most of this to `Reactive` + debug('render "%s"', text); + node.data = utils.interpolate(text, function(prop, fn){ + if (fn) { + return fn(reactive); + } else { + return reactive.get(prop); + } + }); }; -/** - * Get case-insensitive header `field` value. - * - * @param {String} field - * @return {String} - * @api private - */ +}); -Request.prototype.getHeader = function(field){ - return this._header[field.toLowerCase()]; -}; +require.register("component~reactive@1.1.0/lib/attr-binding.js", function (exports, module) { /** - * Set Content-Type to `type`, mapping values from `request.types`. - * - * Examples: - * - * superagent.types.xml = 'application/xml'; - * - * request.post('/') - * .type('xml') - * .send(xmlstring) - * .end(callback); - * - * request.post('/') - * .type('application/xml') - * .send(xmlstring) - * .end(callback); - * - * @param {String} type - * @return {Request} for chaining - * @api public + * Module dependencies. */ -Request.prototype.type = function(type){ - this.set('Content-Type', request.types[type] || type); - return this; -}; +var debug = require('visionmedia~debug@0.7.4')('reactive:attr-binding'); +var utils = require('component~reactive@1.1.0/lib/utils.js'); /** - * Set Accept to `type`, mapping values from `request.types`. - * - * Examples: - * - * superagent.types.json = 'application/json'; - * - * request.get('/agent') - * .accept('json') - * .end(callback); - * - * request.get('/agent') - * .accept('application/json') - * .end(callback); - * - * @param {String} accept - * @return {Request} for chaining - * @api public + * Expose `AttrBinding`. */ -Request.prototype.accept = function(type){ - this.set('Accept', request.types[type] || type); - return this; -}; +module.exports = AttrBinding; /** - * Set Authorization field value with `user` and `pass`. + * Initialize a new attribute binding. * - * @param {String} user - * @param {String} pass - * @return {Request} for chaining - * @api public + * @param {Reactive} view + * @param {Element} node + * @param {Attribute} attr + * @api private */ -Request.prototype.auth = function(user, pass){ - var str = btoa(user + ':' + pass); - this.set('Authorization', 'Basic ' + str); - return this; -}; +function AttrBinding(reactive, node, attr) { + var self = this; + this.reactive = reactive; + this.node = node; + this.attr = attr; + this.text = attr.value; + this.props = utils.interpolationProps(this.text); + this.subscribe(); + this.render(); +} /** -* Add query-string `val`. -* -* Examples: -* -* request.get('/shoes') -* .query('size=10') -* .query({ color: 'blue' }) -* -* @param {Object|String} val -* @return {Request} for chaining -* @api public -*/ + * Subscribe to changes. + */ -Request.prototype.query = function(val){ - if ('string' != typeof val) val = serialize(val); - if (val) this._query.push(val); - return this; +AttrBinding.prototype.subscribe = function(){ + var self = this; + var reactive = this.reactive; + this.props.forEach(function(prop){ + reactive.sub(prop, function(){ + self.render(); + }); + }); }; /** - * Send `data`, defaulting the `.type()` to "json" when - * an object is given. - * - * Examples: - * - * // querystring - * request.get('/search') - * .end(callback) - * - * // multiple data "writes" - * request.get('/search') - * .send({ search: 'query' }) - * .send({ range: '1..5' }) - * .send({ order: 'desc' }) - * .end(callback) - * - * // manual json - * request.post('/user') - * .type('json') - * .send('{"name":"tj"}) - * .end(callback) - * - * // auto json - * request.post('/user') - * .send({ name: 'tj' }) - * .end(callback) - * - * // manual x-www-form-urlencoded - * request.post('/user') - * .type('form') - * .send('name=tj') - * .end(callback) - * - * // auto x-www-form-urlencoded - * request.post('/user') - * .type('form') - * .send({ name: 'tj' }) - * .end(callback) - * - * // defaults to x-www-form-urlencoded - * request.post('/user') - * .send('name=tobi') - * .send('species=ferret') - * .end(callback) - * - * @param {String|Object} data - * @return {Request} for chaining - * @api public + * Render the value. */ -Request.prototype.send = function(data){ - var obj = isObject(data); - var type = this.getHeader('Content-Type'); +AttrBinding.prototype.render = function(){ + var attr = this.attr; + var text = this.text; + var reactive = this.reactive; + var model = reactive.model; - // merge - if (obj && isObject(this._data)) { - for (var key in data) { - this._data[key] = data[key]; - } - } else if ('string' == typeof data) { - if (!type) this.type('form'); - type = this.getHeader('Content-Type'); - if ('application/x-www-form-urlencoded' == type) { - this._data = this._data - ? this._data + '&' + data - : data; + // TODO: delegate most of this to `Reactive` + debug('render %s "%s"', attr.name, text); + attr.value = utils.interpolate(text, function(prop, fn){ + if (fn) { + return fn(reactive); } else { - this._data = (this._data || '') + data; + return reactive.get(prop); } - } else { - this._data = data; - } - - if (!obj) return this; - if (!type) this.type('json'); - return this; + }); }; +}); + +require.register("component~reactive@1.1.0/lib/binding.js", function (exports, module) { +var hasInterpolation = require('component~reactive@1.1.0/lib/utils.js').hasInterpolation; +var interpolationProps = require('component~reactive@1.1.0/lib/utils.js').interpolationProps; + /** - * Invoke the callback with `err` and `res` - * and handle arity check. + * Expose `Binding`. + */ + +module.exports = Binding; + +/** + * Initialize a binding. * - * @param {Error} err - * @param {Response} res * @api private */ -Request.prototype.callback = function(err, res){ - var fn = this._callback; - if (2 == fn.length) return fn(err, res); - if (err) return this.emit('error', err); - fn(res); -}; +function Binding(name, reactive, el, fn) { + this.name = name; + this.reactive = reactive; + this.model = reactive.model; + this.view = reactive.view; + this.el = el; + this.fn = fn; +} /** - * Invoke callback with x-domain error. + * Apply the binding. * * @api private */ -Request.prototype.crossDomainError = function(){ - var err = new Error('Origin is not allowed by Access-Control-Allow-Origin'); - err.crossDomain = true; - this.callback(err); +Binding.prototype.bind = function() { + var val = this.el.getAttribute(this.name); + this.fn(this.el, val, this.model); }; /** - * Invoke callback with timeout error. + * Perform interpolation on `name`. * - * @api private + * @param {String} name + * @return {String} + * @api public */ -Request.prototype.timeoutError = function(){ - var timeout = this._timeout; - var err = new Error('timeout of ' + timeout + 'ms exceeded'); - err.timeout = timeout; - this.callback(err); +Binding.prototype.interpolate = function(name) { + var self = this; + + if (~name.indexOf('{')) { + return name.replace(/{([^}]+)}/g, function(_, name){ + return self.value(name); + }); + } + + return self.value(name); }; /** - * Enable transmission of cookies with x-domain requests. + * Return value for property `name`. * - * Note that for this to work the origin must not be - * using "Access-Control-Allow-Origin" with a wildcard, - * and also must set "Access-Control-Allow-Credentials" - * to "true". + * - check if the "view" has a `name` method + * - check if the "model" has a `name` method + * - check if the "model" has a `name` property * + * @param {String} name + * @return {Mixed} * @api public */ -Request.prototype.withCredentials = function(){ - this._withCredentials = true; - return this; +Binding.prototype.value = function(name) { + return this.reactive.get(name); }; /** - * Initiate request, invoking callback `fn(res)` - * with an instanceof `Response`. + * Invoke `fn` on changes. * * @param {Function} fn - * @return {Request} for chaining * @api public */ -Request.prototype.end = function(fn){ - var self = this; - var xhr = this.xhr = getXHR(); - var query = this._query.join('&'); - var timeout = this._timeout; - var data = this._data; - - // store callback - this._callback = fn || noop; +Binding.prototype.change = function(fn) { + fn.call(this); - // state change - xhr.onreadystatechange = function(){ - if (4 != xhr.readyState) return; - if (0 == xhr.status) { - if (self.aborted) return self.timeoutError(); - return self.crossDomainError(); - } - self.emit('end'); - }; + var self = this; + var reactive = this.reactive; + var val = this.el.getAttribute(this.name); - // progress - if (xhr.upload) { - xhr.upload.onprogress = function(e){ - e.percent = e.loaded / e.total * 100; - self.emit('progress', e); - }; + // interpolation + if (hasInterpolation(val)) { + var props = interpolationProps(val); + props.forEach(function(prop){ + reactive.sub(prop, fn.bind(self)); + }); + return; } - // timeout - if (timeout && !this._timer) { - this._timer = setTimeout(function(){ - self.abort(); - }, timeout); - } + // bind to prop + var prop = val; + reactive.sub(prop, fn.bind(this)); +}; - // querystring - if (query) { - query = request.serializeObject(query); - this.url += ~this.url.indexOf('?') - ? '&' + query - : '?' + query; - } +}); - // initiate request - xhr.open(this.method, this.url, true); +require.register("component~reactive@1.1.0/lib/bindings.js", function (exports, module) { +/** + * Module dependencies. + */ - // CORS - if (this._withCredentials) xhr.withCredentials = true; +var carry = require('yields~carry@0.0.1'); +var classes = require('component~classes@1.1.1'); +var event = require('component~event@0.1.0'); - // body - if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !isHost(data)) { - // serialize stuff - var serialize = request.serialize[this.getHeader('Content-Type')]; - if (serialize) data = serialize(data); - } +var each_binding = require('component~reactive@1.1.0/lib/each.js'); - // set header fields - for (var field in this.header) { - if (null == this.header[field]) continue; - xhr.setRequestHeader(field, this.header[field]); - } +/** + * Attributes supported. + */ - // send stuff - this.emit('request', this); - xhr.send(data); - return this; -}; +var attrs = [ + 'id', + 'src', + 'rel', + 'cols', + 'rows', + 'name', + 'href', + 'title', + 'class', + 'style', + 'width', + 'value', + 'height', + 'tabindex', + 'placeholder' +]; /** - * Expose `Request`. + * Events supported. */ -request.Request = Request; +var events = [ + 'change', + 'click', + 'dblclick', + 'mousedown', + 'mouseup', + 'mouseenter', + 'mouseleave', + 'scroll', + 'blur', + 'focus', + 'input', + 'submit', + 'keydown', + 'keypress', + 'keyup' +]; /** - * Issue a request: - * - * Examples: - * - * request('GET', '/users').end(callback) - * request('/users').end(callback) - * request('/users', callback) - * - * @param {String} method - * @param {String|Function} url or callback - * @return {Request} - * @api public + * Apply bindings. */ -function request(method, url) { - // callback - if ('function' == typeof url) { - return new Request('GET', method).end(url); - } +module.exports = function(reactive){ + + reactive.bind('each', each_binding); + + /** + * Generate attribute bindings. + */ + + attrs.forEach(function(attr){ + reactive.bind('data-' + attr, function(el, name, obj){ + this.change(function(){ + el.setAttribute(attr, this.interpolate(name)); + }); + }); + }); + + /** + * Show binding. + */ + + reactive.bind('data-visible', function(el, name){ + this.change(function(){ + var val = this.value(name); + if (val) { + classes(el).add('visible').remove('hidden'); + } else { + classes(el).remove('visible').add('hidden'); + } + }); + }); + + /** + * Hide binding. + */ + + reactive.bind('data-hidden', function(el, name){ + this.change(function(){ + var val = this.value(name); + if (val) { + classes(el).remove('visible').add('hidden'); + } else { + classes(el).add('visible').remove('hidden'); + } + }); + }); + + /** + * Checked binding. + */ + + reactive.bind('data-checked', function(el, name){ + this.change(function(){ + if (this.value(name)) { + el.setAttribute('checked', 'checked'); + } else { + el.removeAttribute('checked'); + } + }); + }); + + /** + * Text binding. + */ + + reactive.bind('data-text', function(el, name){ + this.change(function(){ + el.textContent = this.interpolate(name); + }); + }); + + /** + * HTML binding. + */ + + reactive.bind('data-html', function(el, name){ + this.change(function(){ + el.innerHTML = this.interpolate(name); + }); + }); + + /** + * Generate event bindings. + */ + + events.forEach(function(name){ + reactive.bind('on-' + name, function(el, method){ + var self = this; + var view = self.reactive.view; + event.bind(el, name, function(e){ + e.preventDefault(); + + var fn = view[method]; + if (!fn) throw new Error('method .' + method + '() missing'); + fn.call(view, e, self.reactive); + }); + }); + }); + + /** + * Append child element. + */ + + reactive.bind('data-append', function(el, name){ + var other = this.value(name); + el.appendChild(other); + }); + + /** + * Replace element, carrying over its attributes. + */ - // url first - if (1 == arguments.length) { - return new Request('GET', method); - } + reactive.bind('data-replace', function(el, name){ + var other = carry(this.value(name), el); + el.parentNode.replaceChild(other, el); + }); +}; - return new Request(method, url); -} +}); -/** - * GET `url` with optional callback `fn(res)`. - * - * @param {String} url - * @param {Mixed|Function} data or fn - * @param {Function} fn - * @return {Request} - * @api public - */ +require.register("component~reactive@1.1.0/lib/adapter.js", function (exports, module) { -request.get = function(url, data, fn){ - var req = request('GET', url); - if ('function' == typeof data) fn = data, data = null; - if (data) req.query(data); - if (fn) req.end(fn); - return req; +function Adapter(obj) { + if (!(this instanceof Adapter)) { + return new Adapter(obj); + } + + var self = this; + self.obj = obj; }; /** - * HEAD `url` with optional callback `fn(res)`. + * Default subscription method. + * Subscribe to changes on the model. * - * @param {String} url - * @param {Mixed|Function} data or fn + * @param {Object} obj + * @param {String} prop * @param {Function} fn - * @return {Request} - * @api public */ -request.head = function(url, data, fn){ - var req = request('HEAD', url); - if ('function' == typeof data) fn = data, data = null; - if (data) req.send(data); - if (fn) req.end(fn); - return req; +Adapter.prototype.subscribe = function(prop, fn) { }; /** - * DELETE `url` with optional callback `fn(res)`. - * - * @param {String} url - * @param {Function} fn - * @return {Request} - * @api public + * Default unsubscription method. + * Unsubscribe from changes on the model. */ -request.del = function(url, fn){ - var req = request('DELETE', url); - if (fn) req.end(fn); - return req; +Adapter.prototype.unsubscribe = function(prop, fn) { }; /** - * PATCH `url` with optional `data` and callback `fn(res)`. - * - * @param {String} url - * @param {Mixed} data - * @param {Function} fn - * @return {Request} - * @api public + * Remove all subscriptions on this adapter */ -request.patch = function(url, data, fn){ - var req = request('PATCH', url); - if ('function' == typeof data) fn = data, data = null; - if (data) req.send(data); - if (fn) req.end(fn); - return req; +Adapter.prototype.unsubscribeAll = function() { }; /** - * POST `url` with optional `data` and callback `fn(res)`. + * Default setter method. + * Set a property on the model. * - * @param {String} url - * @param {Mixed} data - * @param {Function} fn - * @return {Request} - * @api public + * @param {Object} obj + * @param {String} prop + * @param {Mixed} val */ -request.post = function(url, data, fn){ - var req = request('POST', url); - if ('function' == typeof data) fn = data, data = null; - if (data) req.send(data); - if (fn) req.end(fn); - return req; +Adapter.prototype.set = function(prop, val) { + var obj = this.obj; + if (!obj) return; + if ('function' == typeof obj[prop]) { + obj[prop](val); + } + else if ('function' == typeof obj.set) { + obj.set(prop, val); + } + else { + obj[prop] = val; + } }; /** - * PUT `url` with optional `data` and callback `fn(res)`. + * Default getter method. + * Get a property from the model. * - * @param {String} url - * @param {Mixed|Function} data or fn - * @param {Function} fn - * @return {Request} - * @api public + * @param {Object} obj + * @param {String} prop + * @return {Mixed} */ -request.put = function(url, data, fn){ - var req = request('PUT', url); - if ('function' == typeof data) fn = data, data = null; - if (data) req.send(data); - if (fn) req.end(fn); - return req; -}; +Adapter.prototype.get = function(prop) { + var obj = this.obj; + if (!obj) { + return undefined; + } -/** - * Expose `request`. - */ + // split property on '.' access + // and dig into the object + var parts = prop.split('.'); + var part = parts.shift(); + do { + if (typeof obj[part] === 'function') { + obj = obj[part].call(obj); + } + else { + obj = obj[part]; + } -module.exports = request; + if (!obj) { + return undefined; + } + + part = parts.shift(); + } while(part); + + return obj; +}; + +module.exports = Adapter; }); -require.register("component~search.js@1.1.0", function (exports, module) { +require.register("component~reactive@1.1.0/lib/each.js", function (exports, module) { +// 'each' binding +module.exports = function(el, val) { + var self = this; -var crawler = search.crawler = require("component~search.js@1.1.0/client/crawler.js"); -var fns = require("component~search.js@1.1.0/lib/functions.js"); + // get the reactive constructor from the current reactive instance + // TODO(shtylman) port over adapter and bindings from instance? + var Reactive = self.reactive.constructor; -module.exports = search + var val = val.split(/ +/); + el.removeAttribute('each'); + + var name = val[0]; + var prop = val[0]; + + if (val.length > 1) { + name = val[0]; + prop = val[2]; + } + + var parent = el.parentNode; + + // use text node to hold where end of list should be + var placeholder = document.createTextNode(''); + parent.insertBefore(placeholder, el); + parent.removeChild(el); + + // the reactive views we created for our array + // one per array item + // the length of this MUST always match the length of the 'arr' + // and mutates with 'arr' + var views = []; + + function childView(el, model) { + return Reactive(el, model, { + delegate: self.view, + adapter: self.reactive.opt.adapter, + bindings: self.reactive.bindings + }); + } + + var array; + + // bind entire new array + function change(arr) { + + // remove any old bindings/views + views.forEach(function(view) { + view.destroy(); + }); + views = []; + + // remove any old array observers + if (array) { + unpatchArray(array); + } + patchArray(arr); + array = arr; + + // handle initial array + var fragment = document.createDocumentFragment(); + arr.forEach(function(obj) { + var clone = el.cloneNode(true); + var view = childView(clone, obj); + views.push(view); + fragment.appendChild(clone); + }); + parent.insertBefore(fragment, placeholder); + } + + function unpatchArray(arr) { + delete arr.splice; + delete arr.push; + delete arr.unshift; + } + + function patchArray(arr) { + // splice will replace the current arr.splice function + // so that we can intercept modifications + var old_splice = arr.splice; + // idx -> index to start operation + // how many -> elements to remove + // ... elements to insert + // return removed elements + var splice = function(idx, how_many) { + var args = Array.prototype.slice.apply(arguments); + + // new items to insert if any + var new_items = args.slice(2); + + var place = placeholder; + if (idx < views.length) { + place = views[idx].el; + } -/** - * Right now, `query` must be a string, - * and we'll search intelligently based on it. - * In the future, we should allow options - * like the node version. - */ + // make views for these items + var new_views = new_items.map(function(item) { + var clone = el.cloneNode(true); + return childView(clone, item); + }); -function search(query, limit) { - query = (query || '').toLowerCase().trim(); + var splice_args = [idx, how_many].concat(new_views); - // don't need to show every component - if (!query) { - return crawler.components - .sort(fns.sortBy.starsAndWatchers) - .slice(0, limit || 25); - } + var removed = views.splice.apply(views, splice_args); - // search by / - if (/^[\w-]+\//.test(query)) { - return crawler.components - .filter(function (json) { - return !json.github.full_name.indexOf(query); - }) - .sort(function (a, b) { - // sort by the length of the name, ascending - return a.github.full_name.length - - b.github.full_name.length; - }) - .slice(0, limit || 25) - } + var frag = document.createDocumentFragment(); + // insert into appropriate place + // first removed item is where to insert + new_views.forEach(function(view) { + frag.appendChild(view.el); + }); - var filters = []; + // insert before a specific location + // the location is defined by the element at idx + parent.insertBefore(frag, place); - filters.push(fns.filterBy.text(query)); + // remove after since we may need the element for 'placement' + // of the new document fragment + removed.forEach(function(view) { + view.destroy(); + }); - query.split(/\s*/) - .filter(Boolean) - .filter(function (keyword) { - // check only alphanumerics - return /^\w+$/.test(keyword); - }) - .forEach(function (keyword) { - filters.push(fns.filterBy.keyword(keyword)); - }) + var ret = old_splice.apply(arr, args); - return crawler.components - .filter(function (json) { - return filters.some(function (filter) { - return filter(json); - }); - }) - .sort(fns.sortBy.starsAndWatchers) - .slice(0, limit || 25); -} -}); + // set the length property of the array + // so that any listeners can pick up on it + self.reactive.set(prop + '.length', arr.length); + return ret; + }; -require.register("component~search.js@1.1.0/client/crawler.js", function (exports, module) { + /// existing methods can be implemented via splice -/** - * Load all the components from the crawler. - */ + var push = function(el1, el2) { + var args = Array.prototype.slice.apply(arguments); + var len = arr.length; -var request = require("visionmedia~superagent@0.17.0"); + var splice_args = [len, 0].concat(args) + splice.apply(arr, splice_args); + return arr.length; + }; -var loaded = false; -var loading = false; -var queue = []; + var unshift = function(el1, el2) { + var args = Array.prototype.slice.apply(arguments); + var len = arr.length; -module.exports = crawler; + var splice_args = [0, 0].concat(args) + splice.apply(arr, splice_args); + return arr.length; + }; -// automatically load all the crawled data on page load -crawler(); + // use defineProperty to avoid making ownProperty fields + function set_prop(prop, fn) { + Object.defineProperty(arr, prop, { + enumerable: false, + writable: true, + configurable: true, + value: fn + }); + } -function crawler(done) { - done = done || noop; - if (loaded) return done(); - if (loading) return queue.push(done); - loading = true; + set_prop('splice', splice); + set_prop('push', push); + set_prop('unshift', unshift); + } - request - .get('http://component-crawler.herokuapp.com/.json') - .end(function (err, res) { - crawler.users = res.body.users; - crawler.components = res.body.components; + change(self.reactive.get(prop) || []); + self.skip = true; - loaded = true; - loading = false; - done(); - while (queue.length) queue.shift()(); - }) -} + self.reactive.sub(prop, change); +}; -function noop(){} }); -require.register("component~search.js@1.1.0/lib/functions.js", function (exports, module) { +require.register("component~reactive@1.1.0/lib/walk.js", function (exports, module) { +/** + * @api private + */ +module.exports = function walk(el, process, done) { + var end = done || function(){}; + var nodes = [].slice.call(el.childNodes); -exports.filterBy = { - owner: function (owner) { - return function (json) { - return json.github.owner.login === owner; - } - }, - keyword: function (keyword) { - return function (json) { - var keywords = json.keywords; - if (!keywords) return false; - return ~json.keywords.indexOf(keyword); - } - }, - text: function (string) { - string = escapeRegExp(string).trim(); - var terms = string.split(/\s+/); - var re = new RegExp(terms.join('.* .*'), 'i'); - return function (json) { - return re.test(json.name) - || re.test(json.description) - || re.test(json.github.full_name); + function next(stop){ + if (stop || nodes.length === 0) { + return end(); } + walk(nodes.shift(), process, next); } -} - -exports.sortBy = { - starsAndWatchers: function (a, b) { - return followScore(b) - followScore(a); - } -} -function followScore(json) { - var watchers = json.github.subscribers_count || 0; - var stars = json.github.stargazers_count || 0; - return watchers * 10 + stars; + process(el, next); } -function escapeRegExp(str) { - return String(str).replace(/([.*+?=^!:${}()|[\]\/\\])/g, '\\$1'); -} }); require.register("component~autoscale-canvas@0.0.3", function (exports, module) { @@ -4193,7 +4498,7 @@ module.exports = function(canvas){ }; }); -require.register("component~raf@1.1.3", function (exports, module) { +require.register("component~raf@1.2.0", function (exports, module) { /** * Expose `requestAnimationFrame()`. */ @@ -4201,8 +4506,6 @@ require.register("component~raf@1.1.3", function (exports, module) { exports = module.exports = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame - || window.oRequestAnimationFrame - || window.msRequestAnimationFrame || fallback; /** @@ -4225,8 +4528,6 @@ function fallback(fn) { var cancel = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame - || window.oCancelAnimationFrame - || window.msCancelAnimationFrame || window.clearTimeout; exports.cancel = function(id){ @@ -4241,8 +4542,8 @@ require.register("component~spinner@1.0.0", function (exports, module) { * Module dependencies. */ -var autoscale = require("component~autoscale-canvas@0.0.3"); -var raf = require("component~raf@1.1.3"); +var autoscale = require('component~autoscale-canvas@0.0.3'); +var raf = require('component~raf@1.2.0'); /** * Expose `Spinner`. @@ -4431,46 +4732,13 @@ Spinner.prototype.draw = function(ctx){ }); -require.register("./lib/error", function (exports, module) { - -/** - * Module dependencies. - */ - -var tmpl = require("./lib/error/template.html"); -var reactive = require("component~reactive@1.1.0"); -var domify = require("component~domify@1.2.2"); - -/** - * Expose `ErrorView`. - */ - -module.exports = ErrorView; - -/** - * Initialize a new error view. - * - * @param {Object} error - * @api public - */ - -function ErrorView(error) { - this.error = error; - this.el = domify(tmpl); - this.view = reactive(this.el, error, this); -} - -}); - -require.define("./lib/error/template.html", "
\n

\n

\n
\n"); - require.register("./lib/list", function (exports, module) { /** * Module dependencies. */ -var ComponentView = require("./lib/list/view.js"); +var ComponentView = require('./lib/list/view.js'); /** * Expose `List`. @@ -4521,9 +4789,9 @@ require.register("./lib/list/view.js", function (exports, module) { * Module dependencies. */ -var reactive = require("component~reactive@1.1.0"); +var reactive = require('component~reactive@1.1.0'); -var tmpl = require("component~domify@1.2.2")(require("./lib/list/template.html")); +var tmpl = require('component~domify@1.2.2')(require('./lib/list/template.html')); /** * Expose `ComponentView`. @@ -4573,20 +4841,53 @@ ComponentView.prototype.stars = function () { require.define("./lib/list/template.html", "
\n
\n

\n \n
\n \n \n \n \n \n \n \n \n \n
Stars
License
\n

\n
\n"); +require.register("./lib/error", function (exports, module) { + +/** + * Module dependencies. + */ + +var tmpl = require('./lib/error/template.html'); +var reactive = require('component~reactive@1.1.0'); +var domify = require('component~domify@1.2.2'); + +/** + * Expose `ErrorView`. + */ + +module.exports = ErrorView; + +/** + * Initialize a new error view. + * + * @param {Object} error + * @api public + */ + +function ErrorView(error) { + this.error = error; + this.el = domify(tmpl); + this.view = reactive(this.el, error, this); +} + +}); + +require.define("./lib/error/template.html", "
\n

\n

\n
\n"); + require.register("./lib/search", function (exports, module) { /** * Module dependencies. */ -var List = require("./lib/list"); -var ErrorView = require("./lib/error"); -var reactive = require("component~reactive@1.1.0"); -var Spinner = require("component~spinner@1.0.0"); -var Emitter = require("component~emitter@1.1.2"); -var domify = require("component~domify@1.2.2"); -var tmpl = require("./lib/search/template.html"); -var search = require("component~search.js@1.1.0"); +var List = require('./lib/list'); +var ErrorView = require('./lib/error'); +var reactive = require('component~reactive@1.1.0'); +var Spinner = require('component~spinner@1.0.0'); +var Emitter = require('component~emitter@1.1.2'); +var domify = require('component~domify@1.2.2'); +var tmpl = require('./lib/search/template.html'); +var search = require('component~search.js@1.1.0'); /** * Expose `SearchView`. @@ -4618,6 +4919,16 @@ function SearchView() { Emitter(SearchView.prototype); +/** + * Set the value of the input. + * + * @api public + */ + +SearchView.prototype.input = function(query){ + this.el.querySelector('input').value = query; +}; + /** * Focus on the input. * @@ -4634,7 +4945,8 @@ SearchView.prototype.focus = function(){ SearchView.prototype.search = function(e){ var self = this; - var str = e.target.value; + var targetEl = (e && e.target) || this.el.querySelector('input'); + var str = targetEl.value; if (!str) return self.emit('query', ''); if (str.length < 2) return; @@ -4642,9 +4954,9 @@ SearchView.prototype.search = function(e){ this.timer = setTimeout(function(){ self.timer = null; - self.emit('query', e.target.value); + self.emit('query', str); analytics.track('query', { - query: e.target.value + query: str }); }, 100); }; @@ -4739,9 +5051,11 @@ require.register("./lib/boot", function (exports, module) { * Module dependencies. */ -var Search = require("./lib/search"); -var top = require("component~top@0.0.2"); -var k = require("yields~k@0.6.1")(window); +var Search = require('./lib/search'); +var top = require('component~top@0.0.2'); +var k = require('yields~k@0.6.1')(window); +var qs = require('component~querystring@1.3.1'); + // back to top @@ -4777,7 +5091,14 @@ search.on('query', function(str){ search.show(str); }); -search.show(''); +var initialQuery = qs.parse(location.search).q; +if (!initialQuery || initialQuery.length < 2) { + initialQuery = ''; +} +search.input(initialQuery); +search.focus(); +search.search(); + }); -require("./lib/boot") +require("./lib/boot"); diff --git a/lib/boot/component.json b/lib/boot/component.json index 8bb59c3..8636060 100644 --- a/lib/boot/component.json +++ b/lib/boot/component.json @@ -2,7 +2,8 @@ "dependencies": { "logo/component": "0.0.1", "yields/k": "0.6.1", - "component/top": "0.0.2" + "component/top": "0.0.2", + "component/querystring": "1.3.1" }, "locals": [ "search" diff --git a/lib/boot/index.js b/lib/boot/index.js index 8918459..1cae23e 100644 --- a/lib/boot/index.js +++ b/lib/boot/index.js @@ -6,6 +6,8 @@ var Search = require('search'); var top = require('top'); var k = require('k')(window); +var qs = require('querystring'); + // back to top @@ -41,4 +43,10 @@ search.on('query', function(str){ search.show(str); }); -search.show(''); \ No newline at end of file +var initialQuery = qs.parse(location.search).q; +if (!initialQuery || initialQuery.length < 2) { + initialQuery = ''; +} +search.input(initialQuery); +search.focus(); +search.search(); diff --git a/lib/search/index.js b/lib/search/index.js index 5defccb..131ad53 100644 --- a/lib/search/index.js +++ b/lib/search/index.js @@ -42,6 +42,16 @@ function SearchView() { Emitter(SearchView.prototype); +/** + * Set the value of the input. + * + * @api public + */ + +SearchView.prototype.input = function(query){ + this.el.querySelector('input').value = query; +}; + /** * Focus on the input. * @@ -58,7 +68,8 @@ SearchView.prototype.focus = function(){ SearchView.prototype.search = function(e){ var self = this; - var str = e.target.value; + var targetEl = (e && e.target) || this.el.querySelector('input'); + var str = targetEl.value; if (!str) return self.emit('query', ''); if (str.length < 2) return; @@ -66,9 +77,9 @@ SearchView.prototype.search = function(e){ this.timer = setTimeout(function(){ self.timer = null; - self.emit('query', e.target.value); + self.emit('query', str); analytics.track('query', { - query: e.target.value + query: str }); }, 100); };