diff --git a/README.md b/README.md index 91569cd..ca58376 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ An open-source [Workflowy](http://workflowy.com) clone. [Static demo available h * Edit `config/development.json` and `config/database.json` to your needs * `npm install` +* `npm install bower` +* `bower install` * `node server.js` ## Heroku deploy diff --git a/bower.json b/bower.json index 21be4e3..eb8d4fa 100644 --- a/bower.json +++ b/bower.json @@ -2,6 +2,7 @@ "name": "hackflowy", "description": "A simple WorkFlowy clone", "main": "public/javascripts/app.js", + "version": "0.0.3", "license": "MIT", "ignore": [ "**/.*", @@ -15,13 +16,15 @@ "text": "requirejs-text#~2.0.14", "jquery": "~2.2.0", "backbone": "~1.2.3", - "lodash": "~4.0.1", "requirejs": "~2.1.22", - "modernizr": "~3.3.1", + "modernizr": "http://modernizr.com/download/?-inlinesvg-svg-svgclippaths-touch-shiv-mq-cssclasses-teststyles-prefixes-ie8compat-load.zip", "localforage": "~1.3.3", "localforage-backbone": "~0.6.2", "normalize-css": "normalize.css#~3.0.3", "backbone.marionette": "~2.4.4", "underscore": "~1.8.3" + }, + "resolutions": { + "jquery": "^1.8.0 || ^2.0.0" } } diff --git a/index.html b/index.html index 9b0a856..adb8f6f 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,9 @@ HackFlowy - + + + @@ -17,7 +19,11 @@
- +
@@ -28,42 +34,43 @@
+
- - diff --git a/javascripts/app.js b/javascripts/app.js index 42eb960..6a448b3 100644 --- a/javascripts/app.js +++ b/javascripts/app.js @@ -7,7 +7,7 @@ require.config({ backbone: '../bower_components/backbone/backbone-min', localforage: '../bower_components/localforage/dist/localforage', localforagebackbone: '../bower_components/localforage-backbone/dist/localforage.backbone', - modernizr: "vendor/custom.modernizr", + modernizr: "../bower_components/modernizr/custom.modernizr", socket: "../bower_components/socket.io-client/socket.io", text: '../bower_components/text/text', marionette: '../bower_components/backbone.marionette/lib/backbone.marionette' @@ -28,5 +28,5 @@ require([ 'views/page' ], function (App) { - new App(); + pageView = new App(); }); diff --git a/javascripts/collections/list.js b/javascripts/collections/list.js index 45b0330..c874f9b 100644 --- a/javascripts/collections/list.js +++ b/javascripts/collections/list.js @@ -21,23 +21,33 @@ localforageBackbone initialize: function(){ // update order on add, remove. // Ref: http://stackoverflow.com/a/11665085/221742 - this.on('add remove', this.updateModelPriority); + this.on('add remove sort', this.updateModelPriority); }, + /** + * Move a model in the list up. Only a sort event is emitted + * @param {Backbone.Model} model - Model to be moved + */ moveUp: function(model) { // I see move up as the -1 var index = this.indexOf(model); if (index > 0) { - this.remove(model, {silent: true}); // silence this to stop excess event triggers - this.add(model, {at: index-1}); + this.remove(model, {silent: true}); + this.add(model, {at: index-1, silent: true}); } + this.trigger('sort', this, {}); }, + /** + * Move a model in the list up. Only a sort event is emitted + * @param {Backbone.Model} model - Model to be moved + */ moveDown: function(model) { // I see move up as the -1 var index = this.indexOf(model); if (index < this.models.length) { - this.remove(model, {silent: true}); // silence this to stop excess event triggers - this.add(model, {at: index+1}); + this.remove(model, {silent: true}); + this.add(model, {at: index+1, silent: true}); } + this.trigger('sort', this, {}); }, /** Updated priority of each member of list **/ diff --git a/javascripts/data/demo.js b/javascripts/data/demo.js index bb8aac6..5c0fc67 100644 --- a/javascripts/data/demo.js +++ b/javascripts/data/demo.js @@ -25,7 +25,7 @@ "updatedAt": "2016-01-29T05:44:30.858Z" }, { "id": 83, - "content": "Desyncr pulled this together in a few hours to learn Backbone", + "content": "Pulled this together in a few hours to learn Backbone", "parentId": 0, "isCompleted": false, "priority": 0, diff --git a/javascripts/vendor/custom.modernizr.js b/javascripts/vendor/custom.modernizr.js deleted file mode 100644 index 4eb3d06..0000000 --- a/javascripts/vendor/custom.modernizr.js +++ /dev/null @@ -1,4 +0,0 @@ -/* Modernizr 2.6.2 (Custom Build) | MIT & BSD - * Build: http://modernizr.com/download/#-inlinesvg-svg-svgclippaths-touch-shiv-mq-cssclasses-teststyles-prefixes-ie8compat-load - */ -;window.Modernizr=function(a,b,c){function y(a){j.cssText=a}function z(a,b){return y(m.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={svg:"http://www.w3.org/2000/svg"},o={},p={},q={},r=[],s=r.slice,t,u=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return u("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect},o.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==n.svg},o.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(l.call(b.createElementNS(n.svg,"clipPath")))};for(var D in o)x(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.mq=v,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+r.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f>> 0, k = 0, accumulator - if(typeof fun != 'function') throw new TypeError() - if(len == 0 && arguments.length == 1) throw new TypeError() - - if(arguments.length >= 2) - accumulator = arguments[1] - else - do{ - if(k in t){ - accumulator = t[k++] - break - } - if(++k >= len) throw new TypeError() - } while (true) - - while (k < len){ - if(k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t) - k++ - } - return accumulator - } - -})() - -var Zepto = (function() { - var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter, - document = window.document, - elementDisplay = {}, classCache = {}, - getComputedStyle = document.defaultView.getComputedStyle, - cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 }, - fragmentRE = /^\s*<(\w+|!)[^>]*>/, - tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rootNodeRE = /^(?:body|html)$/i, - - // special attributes that should be get/set via method calls - methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'], - - adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ], - table = document.createElement('table'), - tableRow = document.createElement('tr'), - containers = { - 'tr': document.createElement('tbody'), - 'tbody': table, 'thead': table, 'tfoot': table, - 'td': tableRow, 'th': tableRow, - '*': document.createElement('div') - }, - readyRE = /complete|loaded|interactive/, - classSelectorRE = /^\.([\w-]+)$/, - idSelectorRE = /^#([\w-]*)$/, - tagSelectorRE = /^[\w-]+$/, - class2type = {}, - toString = class2type.toString, - zepto = {}, - camelize, uniq, - tempParent = document.createElement('div') - - zepto.matches = function(element, selector) { - if (!element || element.nodeType !== 1) return false - var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector || - element.oMatchesSelector || element.matchesSelector - if (matchesSelector) return matchesSelector.call(element, selector) - // fall back to performing a selector: - var match, parent = element.parentNode, temp = !parent - if (temp) (parent = tempParent).appendChild(element) - match = ~zepto.qsa(parent, selector).indexOf(element) - temp && tempParent.removeChild(element) - return match - } - - function type(obj) { - return obj == null ? String(obj) : - class2type[toString.call(obj)] || "object" - } - - function isFunction(value) { return type(value) == "function" } - function isWindow(obj) { return obj != null && obj == obj.window } - function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE } - function isObject(obj) { return type(obj) == "object" } - function isPlainObject(obj) { - return isObject(obj) && !isWindow(obj) && obj.__proto__ == Object.prototype - } - function isArray(value) { return value instanceof Array } - function likeArray(obj) { return typeof obj.length == 'number' } - - function compact(array) { return filter.call(array, function(item){ return item != null }) } - function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array } - camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) } - function dasherize(str) { - return str.replace(/::/g, '/') - .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') - .replace(/([a-z\d])([A-Z])/g, '$1_$2') - .replace(/_/g, '-') - .toLowerCase() - } - uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) } - - function classRE(name) { - return name in classCache ? - classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) - } - - function maybeAddPx(name, value) { - return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value - } - - function defaultDisplay(nodeName) { - var element, display - if (!elementDisplay[nodeName]) { - element = document.createElement(nodeName) - document.body.appendChild(element) - display = getComputedStyle(element, '').getPropertyValue("display") - element.parentNode.removeChild(element) - display == "none" && (display = "block") - elementDisplay[nodeName] = display - } - return elementDisplay[nodeName] - } - - function children(element) { - return 'children' in element ? - slice.call(element.children) : - $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node }) - } - - // `$.zepto.fragment` takes a html string and an optional tag name - // to generate DOM nodes nodes from the given html string. - // The generated DOM nodes are returned as an array. - // This function can be overriden in plugins for example to make - // it compatible with browsers that don't support the DOM fully. - zepto.fragment = function(html, name, properties) { - if (html.replace) html = html.replace(tagExpanderRE, "<$1>") - if (name === undefined) name = fragmentRE.test(html) && RegExp.$1 - if (!(name in containers)) name = '*' - - var nodes, dom, container = containers[name] - container.innerHTML = '' + html - dom = $.each(slice.call(container.childNodes), function(){ - container.removeChild(this) - }) - if (isPlainObject(properties)) { - nodes = $(dom) - $.each(properties, function(key, value) { - if (methodAttributes.indexOf(key) > -1) nodes[key](value) - else nodes.attr(key, value) - }) - } - return dom - } - - // `$.zepto.Z` swaps out the prototype of the given `dom` array - // of nodes with `$.fn` and thus supplying all the Zepto functions - // to the array. Note that `__proto__` is not supported on Internet - // Explorer. This method can be overriden in plugins. - zepto.Z = function(dom, selector) { - dom = dom || [] - dom.__proto__ = $.fn - dom.selector = selector || '' - return dom - } - - // `$.zepto.isZ` should return `true` if the given object is a Zepto - // collection. This method can be overriden in plugins. - zepto.isZ = function(object) { - return object instanceof zepto.Z - } - - // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and - // takes a CSS selector and an optional context (and handles various - // special cases). - // This method can be overriden in plugins. - zepto.init = function(selector, context) { - // If nothing given, return an empty Zepto collection - if (!selector) return zepto.Z() - // If a function is given, call it when the DOM is ready - else if (isFunction(selector)) return $(document).ready(selector) - // If a Zepto collection is given, juts return it - else if (zepto.isZ(selector)) return selector - else { - var dom - // normalize array if an array of nodes is given - if (isArray(selector)) dom = compact(selector) - // Wrap DOM nodes. If a plain object is given, duplicate it. - else if (isObject(selector)) - dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null - // If it's a html fragment, create nodes from it - else if (fragmentRE.test(selector)) - dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null - // If there's a context, create a collection on that context first, and select - // nodes from there - else if (context !== undefined) return $(context).find(selector) - // And last but no least, if it's a CSS selector, use it to select nodes. - else dom = zepto.qsa(document, selector) - // create a new Zepto collection from the nodes found - return zepto.Z(dom, selector) - } - } - - // `$` will be the base `Zepto` object. When calling this - // function just call `$.zepto.init, which makes the implementation - // details of selecting nodes and creating Zepto collections - // patchable in plugins. - $ = function(selector, context){ - return zepto.init(selector, context) - } - - function extend(target, source, deep) { - for (key in source) - if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { - if (isPlainObject(source[key]) && !isPlainObject(target[key])) - target[key] = {} - if (isArray(source[key]) && !isArray(target[key])) - target[key] = [] - extend(target[key], source[key], deep) - } - else if (source[key] !== undefined) target[key] = source[key] - } - - // Copy all but undefined properties from one or more - // objects to the `target` object. - $.extend = function(target){ - var deep, args = slice.call(arguments, 1) - if (typeof target == 'boolean') { - deep = target - target = args.shift() - } - args.forEach(function(arg){ extend(target, arg, deep) }) - return target - } - - // `$.zepto.qsa` is Zepto's CSS selector implementation which - // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`. - // This method can be overriden in plugins. - zepto.qsa = function(element, selector){ - var found - return (isDocument(element) && idSelectorRE.test(selector)) ? - ( (found = element.getElementById(RegExp.$1)) ? [found] : [] ) : - (element.nodeType !== 1 && element.nodeType !== 9) ? [] : - slice.call( - classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) : - tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) : - element.querySelectorAll(selector) - ) - } - - function filtered(nodes, selector) { - return selector === undefined ? $(nodes) : $(nodes).filter(selector) - } - - $.contains = function(parent, node) { - return parent !== node && parent.contains(node) - } - - function funcArg(context, arg, idx, payload) { - return isFunction(arg) ? arg.call(context, idx, payload) : arg - } - - function setAttribute(node, name, value) { - value == null ? node.removeAttribute(name) : node.setAttribute(name, value) - } - - // access className property while respecting SVGAnimatedString - function className(node, value){ - var klass = node.className, - svg = klass && klass.baseVal !== undefined - - if (value === undefined) return svg ? klass.baseVal : klass - svg ? (klass.baseVal = value) : (node.className = value) - } - - // "true" => true - // "false" => false - // "null" => null - // "42" => 42 - // "42.5" => 42.5 - // JSON => parse if valid - // String => self - function deserializeValue(value) { - var num - try { - return value ? - value == "true" || - ( value == "false" ? false : - value == "null" ? null : - !isNaN(num = Number(value)) ? num : - /^[\[\{]/.test(value) ? $.parseJSON(value) : - value ) - : value - } catch(e) { - return value - } - } - - $.type = type - $.isFunction = isFunction - $.isWindow = isWindow - $.isArray = isArray - $.isPlainObject = isPlainObject - - $.isEmptyObject = function(obj) { - var name - for (name in obj) return false - return true - } - - $.inArray = function(elem, array, i){ - return emptyArray.indexOf.call(array, elem, i) - } - - $.camelCase = camelize - $.trim = function(str) { return str.trim() } - - // plugin compatibility - $.uuid = 0 - $.support = { } - $.expr = { } - - $.map = function(elements, callback){ - var value, values = [], i, key - if (likeArray(elements)) - for (i = 0; i < elements.length; i++) { - value = callback(elements[i], i) - if (value != null) values.push(value) - } - else - for (key in elements) { - value = callback(elements[key], key) - if (value != null) values.push(value) - } - return flatten(values) - } - - $.each = function(elements, callback){ - var i, key - if (likeArray(elements)) { - for (i = 0; i < elements.length; i++) - if (callback.call(elements[i], i, elements[i]) === false) return elements - } else { - for (key in elements) - if (callback.call(elements[key], key, elements[key]) === false) return elements - } - - return elements - } - - $.grep = function(elements, callback){ - return filter.call(elements, callback) - } - - if (window.JSON) $.parseJSON = JSON.parse - - // Populate the class2type map - $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase() - }) - - // Define methods that will be available on all - // Zepto collections - $.fn = { - // Because a collection acts like an array - // copy over these useful array functions. - forEach: emptyArray.forEach, - reduce: emptyArray.reduce, - push: emptyArray.push, - sort: emptyArray.sort, - indexOf: emptyArray.indexOf, - concat: emptyArray.concat, - - // `map` and `slice` in the jQuery API work differently - // from their array counterparts - map: function(fn){ - return $($.map(this, function(el, i){ return fn.call(el, i, el) })) - }, - slice: function(){ - return $(slice.apply(this, arguments)) - }, - - ready: function(callback){ - if (readyRE.test(document.readyState)) callback($) - else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false) - return this - }, - get: function(idx){ - return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length] - }, - toArray: function(){ return this.get() }, - size: function(){ - return this.length - }, - remove: function(){ - return this.each(function(){ - if (this.parentNode != null) - this.parentNode.removeChild(this) - }) - }, - each: function(callback){ - emptyArray.every.call(this, function(el, idx){ - return callback.call(el, idx, el) !== false - }) - return this - }, - filter: function(selector){ - if (isFunction(selector)) return this.not(this.not(selector)) - return $(filter.call(this, function(element){ - return zepto.matches(element, selector) - })) - }, - add: function(selector,context){ - return $(uniq(this.concat($(selector,context)))) - }, - is: function(selector){ - return this.length > 0 && zepto.matches(this[0], selector) - }, - not: function(selector){ - var nodes=[] - if (isFunction(selector) && selector.call !== undefined) - this.each(function(idx){ - if (!selector.call(this,idx)) nodes.push(this) - }) - else { - var excludes = typeof selector == 'string' ? this.filter(selector) : - (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector) - this.forEach(function(el){ - if (excludes.indexOf(el) < 0) nodes.push(el) - }) - } - return $(nodes) - }, - has: function(selector){ - return this.filter(function(){ - return isObject(selector) ? - $.contains(this, selector) : - $(this).find(selector).size() - }) - }, - eq: function(idx){ - return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1) - }, - first: function(){ - var el = this[0] - return el && !isObject(el) ? el : $(el) - }, - last: function(){ - var el = this[this.length - 1] - return el && !isObject(el) ? el : $(el) - }, - find: function(selector){ - var result, $this = this - if (typeof selector == 'object') - result = $(selector).filter(function(){ - var node = this - return emptyArray.some.call($this, function(parent){ - return $.contains(parent, node) - }) - }) - else if (this.length == 1) result = $(zepto.qsa(this[0], selector)) - else result = this.map(function(){ return zepto.qsa(this, selector) }) - return result - }, - closest: function(selector, context){ - var node = this[0], collection = false - if (typeof selector == 'object') collection = $(selector) - while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))) - node = node !== context && !isDocument(node) && node.parentNode - return $(node) - }, - parents: function(selector){ - var ancestors = [], nodes = this - while (nodes.length > 0) - nodes = $.map(nodes, function(node){ - if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) { - ancestors.push(node) - return node - } - }) - return filtered(ancestors, selector) - }, - parent: function(selector){ - return filtered(uniq(this.pluck('parentNode')), selector) - }, - children: function(selector){ - return filtered(this.map(function(){ return children(this) }), selector) - }, - contents: function() { - return this.map(function() { return slice.call(this.childNodes) }) - }, - siblings: function(selector){ - return filtered(this.map(function(i, el){ - return filter.call(children(el.parentNode), function(child){ return child!==el }) - }), selector) - }, - empty: function(){ - return this.each(function(){ this.innerHTML = '' }) - }, - // `pluck` is borrowed from Prototype.js - pluck: function(property){ - return $.map(this, function(el){ return el[property] }) - }, - show: function(){ - return this.each(function(){ - this.style.display == "none" && (this.style.display = null) - if (getComputedStyle(this, '').getPropertyValue("display") == "none") - this.style.display = defaultDisplay(this.nodeName) - }) - }, - replaceWith: function(newContent){ - return this.before(newContent).remove() - }, - wrap: function(structure){ - var func = isFunction(structure) - if (this[0] && !func) - var dom = $(structure).get(0), - clone = dom.parentNode || this.length > 1 - - return this.each(function(index){ - $(this).wrapAll( - func ? structure.call(this, index) : - clone ? dom.cloneNode(true) : dom - ) - }) - }, - wrapAll: function(structure){ - if (this[0]) { - $(this[0]).before(structure = $(structure)) - var children - // drill down to the inmost element - while ((children = structure.children()).length) structure = children.first() - $(structure).append(this) - } - return this - }, - wrapInner: function(structure){ - var func = isFunction(structure) - return this.each(function(index){ - var self = $(this), contents = self.contents(), - dom = func ? structure.call(this, index) : structure - contents.length ? contents.wrapAll(dom) : self.append(dom) - }) - }, - unwrap: function(){ - this.parent().each(function(){ - $(this).replaceWith($(this).children()) - }) - return this - }, - clone: function(){ - return this.map(function(){ return this.cloneNode(true) }) - }, - hide: function(){ - return this.css("display", "none") - }, - toggle: function(setting){ - return this.each(function(){ - var el = $(this) - ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide() - }) - }, - prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') }, - next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') }, - html: function(html){ - return html === undefined ? - (this.length > 0 ? this[0].innerHTML : null) : - this.each(function(idx){ - var originHtml = this.innerHTML - $(this).empty().append( funcArg(this, html, idx, originHtml) ) - }) - }, - text: function(text){ - return text === undefined ? - (this.length > 0 ? this[0].textContent : null) : - this.each(function(){ this.textContent = text }) - }, - attr: function(name, value){ - var result - return (typeof name == 'string' && value === undefined) ? - (this.length == 0 || this[0].nodeType !== 1 ? undefined : - (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() : - (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result - ) : - this.each(function(idx){ - if (this.nodeType !== 1) return - if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) - else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) - }) - }, - removeAttr: function(name){ - return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) }) - }, - prop: function(name, value){ - return (value === undefined) ? - (this[0] && this[0][name]) : - this.each(function(idx){ - this[name] = funcArg(this, value, idx, this[name]) - }) - }, - data: function(name, value){ - var data = this.attr('data-' + dasherize(name), value) - return data !== null ? deserializeValue(data) : undefined - }, - val: function(value){ - return (value === undefined) ? - (this[0] && (this[0].multiple ? - $(this[0]).find('option').filter(function(o){ return this.selected }).pluck('value') : - this[0].value) - ) : - this.each(function(idx){ - this.value = funcArg(this, value, idx, this.value) - }) - }, - offset: function(coordinates){ - if (coordinates) return this.each(function(index){ - var $this = $(this), - coords = funcArg(this, coordinates, index, $this.offset()), - parentOffset = $this.offsetParent().offset(), - props = { - top: coords.top - parentOffset.top, - left: coords.left - parentOffset.left - } - - if ($this.css('position') == 'static') props['position'] = 'relative' - $this.css(props) - }) - if (this.length==0) return null - var obj = this[0].getBoundingClientRect() - return { - left: obj.left + window.pageXOffset, - top: obj.top + window.pageYOffset, - width: Math.round(obj.width), - height: Math.round(obj.height) - } - }, - css: function(property, value){ - if (arguments.length < 2 && typeof property == 'string') - return this[0] && (this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property)) - - var css = '' - if (type(property) == 'string') { - if (!value && value !== 0) - this.each(function(){ this.style.removeProperty(dasherize(property)) }) - else - css = dasherize(property) + ":" + maybeAddPx(property, value) - } else { - for (key in property) - if (!property[key] && property[key] !== 0) - this.each(function(){ this.style.removeProperty(dasherize(key)) }) - else - css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' - } - - return this.each(function(){ this.style.cssText += ';' + css }) - }, - index: function(element){ - return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]) - }, - hasClass: function(name){ - return emptyArray.some.call(this, function(el){ - return this.test(className(el)) - }, classRE(name)) - }, - addClass: function(name){ - return this.each(function(idx){ - classList = [] - var cls = className(this), newName = funcArg(this, name, idx, cls) - newName.split(/\s+/g).forEach(function(klass){ - if (!$(this).hasClass(klass)) classList.push(klass) - }, this) - classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) - }) - }, - removeClass: function(name){ - return this.each(function(idx){ - if (name === undefined) return className(this, '') - classList = className(this) - funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){ - classList = classList.replace(classRE(klass), " ") - }) - className(this, classList.trim()) - }) - }, - toggleClass: function(name, when){ - return this.each(function(idx){ - var $this = $(this), names = funcArg(this, name, idx, className(this)) - names.split(/\s+/g).forEach(function(klass){ - (when === undefined ? !$this.hasClass(klass) : when) ? - $this.addClass(klass) : $this.removeClass(klass) - }) - }) - }, - scrollTop: function(){ - if (!this.length) return - return ('scrollTop' in this[0]) ? this[0].scrollTop : this[0].scrollY - }, - position: function() { - if (!this.length) return - - var elem = this[0], - // Get *real* offsetParent - offsetParent = this.offsetParent(), - // Get correct offsets - offset = this.offset(), - parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset() - - // Subtract element margins - // note: when an element has margin: auto the offsetLeft and marginLeft - // are the same in Safari causing offset.left to incorrectly be 0 - offset.top -= parseFloat( $(elem).css('margin-top') ) || 0 - offset.left -= parseFloat( $(elem).css('margin-left') ) || 0 - - // Add offsetParent borders - parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0 - parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0 - - // Subtract the two offsets - return { - top: offset.top - parentOffset.top, - left: offset.left - parentOffset.left - } - }, - offsetParent: function() { - return this.map(function(){ - var parent = this.offsetParent || document.body - while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static") - parent = parent.offsetParent - return parent - }) - } - } - - // for now - $.fn.detach = $.fn.remove - - // Generate the `width` and `height` functions - ;['width', 'height'].forEach(function(dimension){ - $.fn[dimension] = function(value){ - var offset, el = this[0], - Dimension = dimension.replace(/./, function(m){ return m[0].toUpperCase() }) - if (value === undefined) return isWindow(el) ? el['inner' + Dimension] : - isDocument(el) ? el.documentElement['offset' + Dimension] : - (offset = this.offset()) && offset[dimension] - else return this.each(function(idx){ - el = $(this) - el.css(dimension, funcArg(this, value, idx, el[dimension]())) - }) - } - }) - - function traverseNode(node, fun) { - fun(node) - for (var key in node.childNodes) traverseNode(node.childNodes[key], fun) - } - - // Generate the `after`, `prepend`, `before`, `append`, - // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods. - adjacencyOperators.forEach(function(operator, operatorIndex) { - var inside = operatorIndex % 2 //=> prepend, append - - $.fn[operator] = function(){ - // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings - var argType, nodes = $.map(arguments, function(arg) { - argType = type(arg) - return argType == "object" || argType == "array" || arg == null ? - arg : zepto.fragment(arg) - }), - parent, copyByClone = this.length > 1 - if (nodes.length < 1) return this - - return this.each(function(_, target){ - parent = inside ? target : target.parentNode - - // convert all methods to a "before" operation - target = operatorIndex == 0 ? target.nextSibling : - operatorIndex == 1 ? target.firstChild : - operatorIndex == 2 ? target : - null - - nodes.forEach(function(node){ - if (copyByClone) node = node.cloneNode(true) - else if (!parent) return $(node).remove() - - traverseNode(parent.insertBefore(node, target), function(el){ - if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && - (!el.type || el.type === 'text/javascript') && !el.src) - window['eval'].call(window, el.innerHTML) - }) - }) - }) - } - - // after => insertAfter - // prepend => prependTo - // before => insertBefore - // append => appendTo - $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){ - $(html)[operator](this) - return this - } - }) - - zepto.Z.prototype = $.fn - - // Export internal API functions in the `$.zepto` namespace - zepto.uniq = uniq - zepto.deserializeValue = deserializeValue - $.zepto = zepto - - return $ -})() - -window.Zepto = Zepto -'$' in window || (window.$ = Zepto) - -;(function($){ - function detect(ua){ - var os = this.os = {}, browser = this.browser = {}, - webkit = ua.match(/WebKit\/([\d.]+)/), - android = ua.match(/(Android)\s+([\d.]+)/), - ipad = ua.match(/(iPad).*OS\s([\d_]+)/), - iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/), - webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/), - touchpad = webos && ua.match(/TouchPad/), - kindle = ua.match(/Kindle\/([\d.]+)/), - silk = ua.match(/Silk\/([\d._]+)/), - blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/), - bb10 = ua.match(/(BB10).*Version\/([\d.]+)/), - rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/), - playbook = ua.match(/PlayBook/), - chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/), - firefox = ua.match(/Firefox\/([\d.]+)/) - - // Todo: clean this up with a better OS/browser seperation: - // - discern (more) between multiple browsers on android - // - decide if kindle fire in silk mode is android or not - // - Firefox on Android doesn't specify the Android version - // - possibly devide in os, device and browser hashes - - if (browser.webkit = !!webkit) browser.version = webkit[1] - - if (android) os.android = true, os.version = android[2] - if (iphone) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.') - if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.') - if (webos) os.webos = true, os.version = webos[2] - if (touchpad) os.touchpad = true - if (blackberry) os.blackberry = true, os.version = blackberry[2] - if (bb10) os.bb10 = true, os.version = bb10[2] - if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2] - if (playbook) browser.playbook = true - if (kindle) os.kindle = true, os.version = kindle[1] - if (silk) browser.silk = true, browser.version = silk[1] - if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true - if (chrome) browser.chrome = true, browser.version = chrome[1] - if (firefox) browser.firefox = true, browser.version = firefox[1] - - os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || (firefox && ua.match(/Tablet/))) - os.phone = !!(!os.tablet && (android || iphone || webos || blackberry || bb10 || - (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || (firefox && ua.match(/Mobile/)))) - } - - detect.call($, navigator.userAgent) - // make available to unit tests - $.__detect = detect - -})(Zepto) - -;(function($){ - var $$ = $.zepto.qsa, handlers = {}, _zid = 1, specialEvents={}, - hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' } - - specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' - - function zid(element) { - return element._zid || (element._zid = _zid++) - } - function findHandlers(element, event, fn, selector) { - event = parse(event) - if (event.ns) var matcher = matcherFor(event.ns) - return (handlers[zid(element)] || []).filter(function(handler) { - return handler - && (!event.e || handler.e == event.e) - && (!event.ns || matcher.test(handler.ns)) - && (!fn || zid(handler.fn) === zid(fn)) - && (!selector || handler.sel == selector) - }) - } - function parse(event) { - var parts = ('' + event).split('.') - return {e: parts[0], ns: parts.slice(1).sort().join(' ')} - } - function matcherFor(ns) { - return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') - } - - function eachEvent(events, fn, iterator){ - if ($.type(events) != "string") $.each(events, iterator) - else events.split(/\s/).forEach(function(type){ iterator(type, fn) }) - } - - function eventCapture(handler, captureSetting) { - return handler.del && - (handler.e == 'focus' || handler.e == 'blur') || - !!captureSetting - } - - function realEvent(type) { - return hover[type] || type - } - - function add(element, events, fn, selector, getDelegate, capture){ - var id = zid(element), set = (handlers[id] || (handlers[id] = [])) - eachEvent(events, fn, function(event, fn){ - var handler = parse(event) - handler.fn = fn - handler.sel = selector - // emulate mouseenter, mouseleave - if (handler.e in hover) fn = function(e){ - var related = e.relatedTarget - if (!related || (related !== this && !$.contains(this, related))) - return handler.fn.apply(this, arguments) - } - handler.del = getDelegate && getDelegate(fn, event) - var callback = handler.del || fn - handler.proxy = function (e) { - var result = callback.apply(element, [e].concat(e.data)) - if (result === false) e.preventDefault(), e.stopPropagation() - return result - } - handler.i = set.length - set.push(handler) - element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) - }) - } - function remove(element, events, fn, selector, capture){ - var id = zid(element) - eachEvent(events || '', fn, function(event, fn){ - findHandlers(element, event, fn, selector).forEach(function(handler){ - delete handlers[id][handler.i] - element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) - }) - }) - } - - $.event = { add: add, remove: remove } - - $.proxy = function(fn, context) { - if ($.isFunction(fn)) { - var proxyFn = function(){ return fn.apply(context, arguments) } - proxyFn._zid = zid(fn) - return proxyFn - } else if (typeof context == 'string') { - return $.proxy(fn[context], fn) - } else { - throw new TypeError("expected function") - } - } - - $.fn.bind = function(event, callback){ - return this.each(function(){ - add(this, event, callback) - }) - } - $.fn.unbind = function(event, callback){ - return this.each(function(){ - remove(this, event, callback) - }) - } - $.fn.one = function(event, callback){ - return this.each(function(i, element){ - add(this, event, callback, null, function(fn, type){ - return function(){ - var result = fn.apply(element, arguments) - remove(element, type, fn) - return result - } - }) - }) - } - - var returnTrue = function(){return true}, - returnFalse = function(){return false}, - ignoreProperties = /^([A-Z]|layer[XY]$)/, - eventMethods = { - preventDefault: 'isDefaultPrevented', - stopImmediatePropagation: 'isImmediatePropagationStopped', - stopPropagation: 'isPropagationStopped' - } - function createProxy(event) { - var key, proxy = { originalEvent: event } - for (key in event) - if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] - - $.each(eventMethods, function(name, predicate) { - proxy[name] = function(){ - this[predicate] = returnTrue - return event[name].apply(event, arguments) - } - proxy[predicate] = returnFalse - }) - return proxy - } - - // emulates the 'defaultPrevented' property for browsers that have none - function fix(event) { - if (!('defaultPrevented' in event)) { - event.defaultPrevented = false - var prevent = event.preventDefault - event.preventDefault = function() { - this.defaultPrevented = true - prevent.call(this) - } - } - } - - $.fn.delegate = function(selector, event, callback){ - return this.each(function(i, element){ - add(element, event, callback, selector, function(fn){ - return function(e){ - var evt, match = $(e.target).closest(selector, element).get(0) - if (match) { - evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) - return fn.apply(match, [evt].concat([].slice.call(arguments, 1))) - } - } - }) - }) - } - $.fn.undelegate = function(selector, event, callback){ - return this.each(function(){ - remove(this, event, callback, selector) - }) - } - - $.fn.live = function(event, callback){ - $(document.body).delegate(this.selector, event, callback) - return this - } - $.fn.die = function(event, callback){ - $(document.body).undelegate(this.selector, event, callback) - return this - } - - $.fn.on = function(event, selector, callback){ - return !selector || $.isFunction(selector) ? - this.bind(event, selector || callback) : this.delegate(selector, event, callback) - } - $.fn.off = function(event, selector, callback){ - return !selector || $.isFunction(selector) ? - this.unbind(event, selector || callback) : this.undelegate(selector, event, callback) - } - - $.fn.trigger = function(event, data){ - if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event) - fix(event) - event.data = data - return this.each(function(){ - // items in the collection might not be DOM elements - // (todo: possibly support events on plain old objects) - if('dispatchEvent' in this) this.dispatchEvent(event) - }) - } - - // triggers event handlers on current element just as if an event occurred, - // doesn't trigger an actual event, doesn't bubble - $.fn.triggerHandler = function(event, data){ - var e, result - this.each(function(i, element){ - e = createProxy(typeof event == 'string' ? $.Event(event) : event) - e.data = data - e.target = element - $.each(findHandlers(element, event.type || event), function(i, handler){ - result = handler.proxy(e) - if (e.isImmediatePropagationStopped()) return false - }) - }) - return result - } - - // shortcut methods for `.bind(event, fn)` for each event type - ;('focusin focusout load resize scroll unload click dblclick '+ - 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+ - 'change select keydown keypress keyup error').split(' ').forEach(function(event) { - $.fn[event] = function(callback) { - return callback ? - this.bind(event, callback) : - this.trigger(event) - } - }) - - ;['focus', 'blur'].forEach(function(name) { - $.fn[name] = function(callback) { - if (callback) this.bind(name, callback) - else this.each(function(){ - try { this[name]() } - catch(e) {} - }) - return this - } - }) - - $.Event = function(type, props) { - if (typeof type != 'string') props = type, type = props.type - var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true - if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) - event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null) - event.isDefaultPrevented = function(){ return this.defaultPrevented } - return event - } - -})(Zepto) - -;(function($){ - var jsonpID = 0, - document = window.document, - key, - name, - rscript = /)<[^<]*)*<\/script>/gi, - scriptTypeRE = /^(?:text|application)\/javascript/i, - xmlTypeRE = /^(?:text|application)\/xml/i, - jsonType = 'application/json', - htmlType = 'text/html', - blankRE = /^\s*$/ - - // trigger a custom event and return false if it was cancelled - function triggerAndReturn(context, eventName, data) { - var event = $.Event(eventName) - $(context).trigger(event, data) - return !event.defaultPrevented - } - - // trigger an Ajax "global" event - function triggerGlobal(settings, context, eventName, data) { - if (settings.global) return triggerAndReturn(context || document, eventName, data) - } - - // Number of active Ajax requests - $.active = 0 - - function ajaxStart(settings) { - if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart') - } - function ajaxStop(settings) { - if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop') - } - - // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable - function ajaxBeforeSend(xhr, settings) { - var context = settings.context - if (settings.beforeSend.call(context, xhr, settings) === false || - triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false) - return false - - triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]) - } - function ajaxSuccess(data, xhr, settings) { - var context = settings.context, status = 'success' - settings.success.call(context, data, status, xhr) - triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data]) - ajaxComplete(status, xhr, settings) - } - // type: "timeout", "error", "abort", "parsererror" - function ajaxError(error, type, xhr, settings) { - var context = settings.context - settings.error.call(context, xhr, type, error) - triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error]) - ajaxComplete(type, xhr, settings) - } - // status: "success", "notmodified", "error", "timeout", "abort", "parsererror" - function ajaxComplete(status, xhr, settings) { - var context = settings.context - settings.complete.call(context, xhr, status) - triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings]) - ajaxStop(settings) - } - - // Empty function, used as default callback - function empty() {} - - $.ajaxJSONP = function(options){ - if (!('type' in options)) return $.ajax(options) - - var callbackName = 'jsonp' + (++jsonpID), - script = document.createElement('script'), - cleanup = function() { - clearTimeout(abortTimeout) - $(script).remove() - delete window[callbackName] - }, - abort = function(type){ - cleanup() - // In case of manual abort or timeout, keep an empty function as callback - // so that the SCRIPT tag that eventually loads won't result in an error. - if (!type || type == 'timeout') window[callbackName] = empty - ajaxError(null, type || 'abort', xhr, options) - }, - xhr = { abort: abort }, abortTimeout - - if (ajaxBeforeSend(xhr, options) === false) { - abort('abort') - return false - } - - window[callbackName] = function(data){ - cleanup() - ajaxSuccess(data, xhr, options) - } - - script.onerror = function() { abort('error') } - - script.src = options.url.replace(/=\?/, '=' + callbackName) - $('head').append(script) - - if (options.timeout > 0) abortTimeout = setTimeout(function(){ - abort('timeout') - }, options.timeout) - - return xhr - } - - $.ajaxSettings = { - // Default type of request - type: 'GET', - // Callback that is executed before request - beforeSend: empty, - // Callback that is executed if the request succeeds - success: empty, - // Callback that is executed the the server drops error - error: empty, - // Callback that is executed on request complete (both: error and success) - complete: empty, - // The context for the callbacks - context: null, - // Whether to trigger "global" Ajax events - global: true, - // Transport - xhr: function () { - return new window.XMLHttpRequest() - }, - // MIME types mapping - accepts: { - script: 'text/javascript, application/javascript', - json: jsonType, - xml: 'application/xml, text/xml', - html: htmlType, - text: 'text/plain' - }, - // Whether the request is to another domain - crossDomain: false, - // Default timeout - timeout: 0, - // Whether data should be serialized to string - processData: true, - // Whether the browser should be allowed to cache GET responses - cache: true, - } - - function mimeToDataType(mime) { - if (mime) mime = mime.split(';', 2)[0] - return mime && ( mime == htmlType ? 'html' : - mime == jsonType ? 'json' : - scriptTypeRE.test(mime) ? 'script' : - xmlTypeRE.test(mime) && 'xml' ) || 'text' - } - - function appendQuery(url, query) { - return (url + '&' + query).replace(/[&?]{1,2}/, '?') - } - - // serialize payload and append it to the URL for GET requests - function serializeData(options) { - if (options.processData && options.data && $.type(options.data) != "string") - options.data = $.param(options.data, options.traditional) - if (options.data && (!options.type || options.type.toUpperCase() == 'GET')) - options.url = appendQuery(options.url, options.data) - } - - $.ajax = function(options){ - var settings = $.extend({}, options || {}) - for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key] - - ajaxStart(settings) - - if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && - RegExp.$2 != window.location.host - - if (!settings.url) settings.url = window.location.toString() - serializeData(settings) - if (settings.cache === false) settings.url = appendQuery(settings.url, '_=' + Date.now()) - - var dataType = settings.dataType, hasPlaceholder = /=\?/.test(settings.url) - if (dataType == 'jsonp' || hasPlaceholder) { - if (!hasPlaceholder) settings.url = appendQuery(settings.url, 'callback=?') - return $.ajaxJSONP(settings) - } - - var mime = settings.accepts[dataType], - baseHeaders = { }, - protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol, - xhr = settings.xhr(), abortTimeout - - if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest' - if (mime) { - baseHeaders['Accept'] = mime - if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0] - xhr.overrideMimeType && xhr.overrideMimeType(mime) - } - if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET')) - baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded') - settings.headers = $.extend(baseHeaders, settings.headers || {}) - - xhr.onreadystatechange = function(){ - if (xhr.readyState == 4) { - xhr.onreadystatechange = empty; - clearTimeout(abortTimeout) - var result, error = false - if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) { - dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type')) - result = xhr.responseText - - try { - // http://perfectionkills.com/global-eval-what-are-the-options/ - if (dataType == 'script') (1,eval)(result) - else if (dataType == 'xml') result = xhr.responseXML - else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result) - } catch (e) { error = e } - - if (error) ajaxError(error, 'parsererror', xhr, settings) - else ajaxSuccess(result, xhr, settings) - } else { - ajaxError(null, xhr.status ? 'error' : 'abort', xhr, settings) - } - } - } - - var async = 'async' in settings ? settings.async : true - xhr.open(settings.type, settings.url, async) - - for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name]) - - if (ajaxBeforeSend(xhr, settings) === false) { - xhr.abort() - return false - } - - if (settings.timeout > 0) abortTimeout = setTimeout(function(){ - xhr.onreadystatechange = empty - xhr.abort() - ajaxError(null, 'timeout', xhr, settings) - }, settings.timeout) - - // avoid sending empty string (#319) - xhr.send(settings.data ? settings.data : null) - return xhr - } - - // handle optional data/success arguments - function parseArguments(url, data, success, dataType) { - var hasData = !$.isFunction(data) - return { - url: url, - data: hasData ? data : undefined, - success: !hasData ? data : $.isFunction(success) ? success : undefined, - dataType: hasData ? dataType || success : success - } - } - - $.get = function(url, data, success, dataType){ - return $.ajax(parseArguments.apply(null, arguments)) - } - - $.post = function(url, data, success, dataType){ - var options = parseArguments.apply(null, arguments) - options.type = 'POST' - return $.ajax(options) - } - - $.getJSON = function(url, data, success){ - var options = parseArguments.apply(null, arguments) - options.dataType = 'json' - return $.ajax(options) - } - - $.fn.load = function(url, data, success){ - if (!this.length) return this - var self = this, parts = url.split(/\s/), selector, - options = parseArguments(url, data, success), - callback = options.success - if (parts.length > 1) options.url = parts[0], selector = parts[1] - options.success = function(response){ - self.html(selector ? - $('
').html(response.replace(rscript, "")).find(selector) - : response) - callback && callback.apply(self, arguments) - } - $.ajax(options) - return this - } - - var escape = encodeURIComponent - - function serialize(params, obj, traditional, scope){ - var type, array = $.isArray(obj) - $.each(obj, function(key, value) { - type = $.type(value) - if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']' - // handle data in serializeArray() format - if (!scope && array) params.add(value.name, value.value) - // recurse into nested objects - else if (type == "array" || (!traditional && type == "object")) - serialize(params, value, traditional, key) - else params.add(key, value) - }) - } - - $.param = function(obj, traditional){ - var params = [] - params.add = function(k, v){ this.push(escape(k) + '=' + escape(v)) } - serialize(params, obj, traditional) - return params.join('&').replace(/%20/g, '+') - } -})(Zepto) - -;(function ($) { - $.fn.serializeArray = function () { - var result = [], el - $( Array.prototype.slice.call(this.get(0).elements) ).each(function () { - el = $(this) - var type = el.attr('type') - if (this.nodeName.toLowerCase() != 'fieldset' && - !this.disabled && type != 'submit' && type != 'reset' && type != 'button' && - ((type != 'radio' && type != 'checkbox') || this.checked)) - result.push({ - name: el.attr('name'), - value: el.val() - }) - }) - return result - } - - $.fn.serialize = function () { - var result = [] - this.serializeArray().forEach(function (elm) { - result.push( encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value) ) - }) - return result.join('&') - } - - $.fn.submit = function (callback) { - if (callback) this.bind('submit', callback) - else if (this.length) { - var event = $.Event('submit') - this.eq(0).trigger(event) - if (!event.defaultPrevented) this.get(0).submit() - } - return this - } - -})(Zepto) - -;(function($, undefined){ - var prefix = '', eventPrefix, endEventName, endAnimationName, - vendors = { Webkit: 'webkit', Moz: '', O: 'o', ms: 'MS' }, - document = window.document, testEl = document.createElement('div'), - supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i, - transform, - transitionProperty, transitionDuration, transitionTiming, - animationName, animationDuration, animationTiming, - cssReset = {} - - function dasherize(str) { return downcase(str.replace(/([a-z])([A-Z])/, '$1-$2')) } - function downcase(str) { return str.toLowerCase() } - function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : downcase(name) } - - $.each(vendors, function(vendor, event){ - if (testEl.style[vendor + 'TransitionProperty'] !== undefined) { - prefix = '-' + downcase(vendor) + '-' - eventPrefix = event - return false - } - }) - - transform = prefix + 'transform' - cssReset[transitionProperty = prefix + 'transition-property'] = - cssReset[transitionDuration = prefix + 'transition-duration'] = - cssReset[transitionTiming = prefix + 'transition-timing-function'] = - cssReset[animationName = prefix + 'animation-name'] = - cssReset[animationDuration = prefix + 'animation-duration'] = - cssReset[animationTiming = prefix + 'animation-timing-function'] = '' - - $.fx = { - off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined), - speeds: { _default: 400, fast: 200, slow: 600 }, - cssPrefix: prefix, - transitionEnd: normalizeEvent('TransitionEnd'), - animationEnd: normalizeEvent('AnimationEnd') - } - - $.fn.animate = function(properties, duration, ease, callback){ - if ($.isPlainObject(duration)) - ease = duration.easing, callback = duration.complete, duration = duration.duration - if (duration) duration = (typeof duration == 'number' ? duration : - ($.fx.speeds[duration] || $.fx.speeds._default)) / 1000 - return this.anim(properties, duration, ease, callback) - } - - $.fn.anim = function(properties, duration, ease, callback){ - var key, cssValues = {}, cssProperties, transforms = '', - that = this, wrappedCallback, endEvent = $.fx.transitionEnd - - if (duration === undefined) duration = 0.4 - if ($.fx.off) duration = 0 - - if (typeof properties == 'string') { - // keyframe animation - cssValues[animationName] = properties - cssValues[animationDuration] = duration + 's' - cssValues[animationTiming] = (ease || 'linear') - endEvent = $.fx.animationEnd - } else { - cssProperties = [] - // CSS transitions - for (key in properties) - if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') ' - else cssValues[key] = properties[key], cssProperties.push(dasherize(key)) - - if (transforms) cssValues[transform] = transforms, cssProperties.push(transform) - if (duration > 0 && typeof properties === 'object') { - cssValues[transitionProperty] = cssProperties.join(', ') - cssValues[transitionDuration] = duration + 's' - cssValues[transitionTiming] = (ease || 'linear') - } - } - - wrappedCallback = function(event){ - if (typeof event !== 'undefined') { - if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below" - $(event.target).unbind(endEvent, wrappedCallback) - } - $(this).css(cssReset) - callback && callback.call(this) - } - if (duration > 0) this.bind(endEvent, wrappedCallback) - - // trigger page reflow so new elements can animate - this.size() && this.get(0).clientLeft - - this.css(cssValues) - - if (duration <= 0) setTimeout(function() { - that.each(function(){ wrappedCallback.call(this) }) - }, 0) - - return this - } - - testEl = null -})(Zepto) - -// Zepto.js -// (c) 2010-2012 Thomas Fuchs -// Zepto.js may be freely distributed under the MIT license. - -;(function($, undefined){ - var document = window.document, docElem = document.documentElement, - origShow = $.fn.show, origHide = $.fn.hide, origToggle = $.fn.toggle - - function anim(el, speed, opacity, scale, callback) { - if (typeof speed == 'function' && !callback) callback = speed, speed = undefined - var props = { opacity: opacity } - if (scale) { - props.scale = scale - el.css($.fx.cssPrefix + 'transform-origin', '0 0') - } - return el.animate(props, speed, null, callback) - } - - function hide(el, speed, scale, callback) { - return anim(el, speed, 0, scale, function(){ - origHide.call($(this)) - callback && callback.call(this) - }) - } - - $.fn.show = function(speed, callback) { - origShow.call(this) - if (speed === undefined) speed = 0 - else this.css('opacity', 0) - return anim(this, speed, 1, '1,1', callback) - } - - $.fn.hide = function(speed, callback) { - if (speed === undefined) return origHide.call(this) - else return hide(this, speed, '0,0', callback) - } - - $.fn.toggle = function(speed, callback) { - if (speed === undefined || typeof speed == 'boolean') - return origToggle.call(this, speed) - else return this.each(function(){ - var el = $(this) - el[el.css('display') == 'none' ? 'show' : 'hide'](speed, callback) - }) - } - - $.fn.fadeTo = function(speed, opacity, callback) { - return anim(this, speed, opacity, null, callback) - } - - $.fn.fadeIn = function(speed, callback) { - var target = this.css('opacity') - if (target > 0) this.css('opacity', 0) - else target = 1 - return origShow.call(this).fadeTo(speed, target, callback) - } - - $.fn.fadeOut = function(speed, callback) { - return hide(this, speed, null, callback) - } - - $.fn.fadeToggle = function(speed, callback) { - return this.each(function(){ - var el = $(this) - el[ - (el.css('opacity') == 0 || el.css('display') == 'none') ? 'fadeIn' : 'fadeOut' - ](speed, callback) - }) - } - -})(Zepto) - -// Zepto.js -// (c) 2010-2012 Thomas Fuchs -// Zepto.js may be freely distributed under the MIT license. - -;(function($){ - var cache = [], timeout - - $.fn.remove = function(){ - return this.each(function(){ - if(this.parentNode){ - if(this.tagName === 'IMG'){ - cache.push(this) - this.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' - if (timeout) clearTimeout(timeout) - timeout = setTimeout(function(){ cache = [] }, 60000) - } - this.parentNode.removeChild(this) - } - }) - } -})(Zepto) - -// Zepto.js -// (c) 2010-2012 Thomas Fuchs -// Zepto.js may be freely distributed under the MIT license. - -// The following code is heavily inspired by jQuery's $.fn.data() - -;(function($) { - var data = {}, dataAttr = $.fn.data, camelize = $.camelCase, - exp = $.expando = 'Zepto' + (+new Date()) - - // Get value from node: - // 1. first try key as given, - // 2. then try camelized key, - // 3. fall back to reading "data-*" attribute. - function getData(node, name) { - var id = node[exp], store = id && data[id] - if (name === undefined) return store || setData(node) - else { - if (store) { - if (name in store) return store[name] - var camelName = camelize(name) - if (camelName in store) return store[camelName] - } - return dataAttr.call($(node), name) - } - } - - // Store value under camelized key on node - function setData(node, name, value) { - var id = node[exp] || (node[exp] = ++$.uuid), - store = data[id] || (data[id] = attributeData(node)) - if (name !== undefined) store[camelize(name)] = value - return store - } - - // Read all "data-*" attributes from a node - function attributeData(node) { - var store = {} - $.each(node.attributes, function(i, attr){ - if (attr.name.indexOf('data-') == 0) - store[camelize(attr.name.replace('data-', ''))] = - $.zepto.deserializeValue(attr.value) - }) - return store - } - - $.fn.data = function(name, value) { - return value === undefined ? - // set multiple values via object - $.isPlainObject(name) ? - this.each(function(i, node){ - $.each(name, function(key, value){ setData(node, key, value) }) - }) : - // get value from first element - this.length == 0 ? undefined : getData(this[0], name) : - // set value on all elements - this.each(function(){ setData(this, name, value) }) - } - - $.fn.removeData = function(names) { - if (typeof names == 'string') names = names.split(/\s+/) - return this.each(function(){ - var id = this[exp], store = id && data[id] - if (store) $.each(names, function(){ delete store[camelize(this)] }) - }) - } -})(Zepto) - -;(function($){ - var zepto = $.zepto, oldQsa = zepto.qsa, oldMatches = zepto.matches - - function visible(elem){ - elem = $(elem) - return !!(elem.width() || elem.height()) && elem.css("display") !== "none" - } - - // Implements a subset from: - // http://api.jquery.com/category/selectors/jquery-selector-extensions/ - // - // Each filter function receives the current index, all nodes in the - // considered set, and a value if there were parentheses. The value - // of `this` is the node currently being considered. The function returns the - // resulting node(s), null, or undefined. - // - // Complex selectors are not supported: - // li:has(label:contains("foo")) + li:has(label:contains("bar")) - // ul.inner:first > li - var filters = $.expr[':'] = { - visible: function(){ if (visible(this)) return this }, - hidden: function(){ if (!visible(this)) return this }, - selected: function(){ if (this.selected) return this }, - checked: function(){ if (this.checked) return this }, - parent: function(){ return this.parentNode }, - first: function(idx){ if (idx === 0) return this }, - last: function(idx, nodes){ if (idx === nodes.length - 1) return this }, - eq: function(idx, _, value){ if (idx === value) return this }, - contains: function(idx, _, text){ if ($(this).text().indexOf(text) > -1) return this }, - has: function(idx, _, sel){ if (zepto.qsa(this, sel).length) return this } - } - - var filterRe = new RegExp('(.*):(\\w+)(?:\\(([^)]+)\\))?$\\s*'), - childRe = /^\s*>/, - classTag = 'Zepto' + (+new Date()) - - function process(sel, fn) { - // quote the hash in `a[href^=#]` expression - sel = sel.replace(/=#\]/g, '="#"]') - var filter, arg, match = filterRe.exec(sel) - if (match && match[2] in filters) { - filter = filters[match[2]], arg = match[3] - sel = match[1] - if (arg) { - var num = Number(arg) - if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '') - else arg = num - } - } - return fn(sel, filter, arg) - } - - zepto.qsa = function(node, selector) { - return process(selector, function(sel, filter, arg){ - try { - var taggedParent - if (!sel && filter) sel = '*' - else if (childRe.test(sel)) - // support "> *" child queries by tagging the parent node with a - // unique class and prepending that classname onto the selector - taggedParent = $(node).addClass(classTag), sel = '.'+classTag+' '+sel - - var nodes = oldQsa(node, sel) - } catch(e) { - console.error('error performing selector: %o', selector) - throw e - } finally { - if (taggedParent) taggedParent.removeClass(classTag) - } - return !filter ? nodes : - zepto.uniq($.map(nodes, function(n, i){ return filter.call(n, i, nodes, arg) })) - }) - } - - zepto.matches = function(node, selector){ - return process(selector, function(sel, filter, arg){ - return (!sel || oldMatches(node, sel)) && - (!filter || filter.call(node, null, arg) === node) - }) - } -})(Zepto) - -// Zepto.js -// (c) 2010-2012 Thomas Fuchs -// Zepto.js may be freely distributed under the MIT license. - -;(function($){ - $.fn.end = function(){ - return this.prevObject || $() - } - - $.fn.andSelf = function(){ - return this.add(this.prevObject || $()) - } - - 'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property){ - var fn = $.fn[property] - $.fn[property] = function(){ - var ret = fn.apply(this, arguments) - ret.prevObject = this - return ret - } - }) -})(Zepto) - - -// outer and inner height/width support -if (this.Zepto) { - (function($) { - var ioDim, _base; - ioDim = function(elem, Dimension, dimension, includeBorder, includeMargin) { - var sides, size; - if (elem) { - size = elem[dimension](); - sides = { - width: ["left", "right"], - height: ["top", "bottom"] - }; - sides[dimension].forEach(function(side) { - size += parseInt(elem.css("padding-" + side), 10); - if (includeBorder) { - size += parseInt(elem.css("border-" + side + "-width"), 10); - } - if (includeMargin) { - return size += parseInt(elem.css("margin-" + side), 10); - } - }); - return size; - } else { - return null; - } - }; - ["width", "height"].forEach(function(dimension) { - var Dimension, _base, _base1, _name, _name1; - Dimension = dimension.replace(/./, function(m) { - return m[0].toUpperCase(); - }); - (_base = $.fn)[_name = "inner" + Dimension] || (_base[_name] = function(includeMargin) { - return ioDim(this, Dimension, dimension, false, includeMargin); - }); - return (_base1 = $.fn)[_name1 = "outer" + Dimension] || (_base1[_name1] = function(includeMargin) { - return ioDim(this, Dimension, dimension, true, includeMargin); - }); - }); - return (_base = $.fn).detach || (_base.detach = function(selector) { - var cloned, set; - set = this; - if (selector != null) { - set = set.filter(selector); - } - cloned = set.clone(true); - set.remove(); - return cloned; - }); - })(Zepto); -} \ No newline at end of file diff --git a/javascripts/views/list.js b/javascripts/views/list.js index a6f3499..82f4125 100644 --- a/javascripts/views/list.js +++ b/javascripts/views/list.js @@ -3,7 +3,6 @@ define( 'backbone', 'collections/list', 'views/task', - 'data/demo', 'text!../../templates/task.html', 'marionette' ], @@ -13,7 +12,6 @@ define( Backbone, List, TaskView, - demoData, listTemplate, Marionette ) { @@ -27,59 +25,53 @@ define( template: _.template(listTemplate), events: { - 'click #add': 'addTask' + 'click #add': 'addTask', }, initialize: function () { - var self = this; - - // this wholeCollection holds all items - this.wholeCollection = Tasks = new List(); - //this.collection = new List(); - - // custom events - this.listenTo(this, 'childview:rerender', this.render); - // this.listenTo(this.collection, 'add remove', this.render); - - /** Load demo data **/ - function loadDemoData() { - for (var i = 0; i < demoData.length; i++) { - var task = Tasks.add(demoData[i]); - task.save(); - } - } - - function success(children, data, promise) { - // load demo data if the server returns nothing - var directChildren = children.filter(this.filterDirectChildren); - if (directChildren.length === 0) - loadDemoData(); - this.collection = new List(Tasks.filter(this.filterDirectChildren)); - this.render(); - } - - Tasks.fetch({ - success: success, - error: function () { - - // switch to localforage database if server isn't present and fetch again - // from there - window.hackflowyOffline = true; - $('#header').append('
Running in offline mode, data may be lost
'); - Tasks.fetch({ - success: success, - context: this - }); - }, - context: this - }); - }, + + /** Get root id for the displayed list from the url hash or 0 **/ + // getRootId: function(){ + // var hash = window.location.hash.slice(1); + // return hash ? hash: 0; + // + // }, + + // /** Called when window location hash changes **/ + // changeRootId: function(){ + // this.collection.remove(this.collection.models); + // this.collection.add(pageView.collection.filter(this.filterDirectChildren,this)); + // this.updateBreadCrumbs(); + // }, + + // updateBreadCrumbs: function(){ + // var rootId = this.getRootId(); + // if (rootId){ + // var current = Tasks.get(rootId); + // var breadCrumbs = _.template('<%= content %>')(current.attributes); + // var depth=0; + // while (current.get('parentId') && depth<100){ + // depth++; + // current = Tasks.get(current.get('parentId')); + // breadCrumbs=_.template('<%= content %> > ')(current.attributes)+breadCrumbs; + // } + // if (depth>=100) console.error('Max depth exceeded while making breadCrumbs'); + // breadCrumbs='Home > '+breadCrumbs; + // $('#task-breadcrumbs').append(breadCrumbs); + // + // } + // }, + // Only show direct children - filterDirectChildren: function (child, index, collection) { - return child.get('parentId') === 0; - }, + // filterDirectChildren: function (child, index, collection) { + // var rootId = this.getRootId(); + // if (rootId) + // return child.get('id') == rootId; + // else + // return child.get('parentId') == rootId; + // }, /** This is the root view in the tree **/ getParentView: function () { @@ -88,8 +80,9 @@ define( /** Update parentId when added to collection **/ onAddChild: function(childView){ - if (childView.model.get('parentId')!==0) - childView.model.save({parentId: 0}); + var rootId = pageView.getRootId(); + if (childView.model.get('parentId')!=rootId && childView.model==rootId) + childView.model.save({parentId: rootId}); }, }); diff --git a/javascripts/views/page.js b/javascripts/views/page.js index b3b7c8e..2815289 100644 --- a/javascripts/views/page.js +++ b/javascripts/views/page.js @@ -1,41 +1,130 @@ define( ['jquery', 'backbone', +'marionette', 'views/list', -'models/task' +'data/demo', +'models/task', +'collections/list' ], function( $, Backbone, +Marionette, ListView, -Task +demoData, +Task, +List ) { - var PageView = Backbone.View.extend({ + var PageView = Marionette.View.extend({ - el: $("#main"), + el: $("#hackflowy"), events: { - 'keypress #newTask': 'createNewTask', - 'blur #newTask': 'createNewTask' }, initialize: function() { - listView = this.listView = new ListView(); - this.input = $('#newTask'); + + // this wholeCollection holds all items + this.collection = new List(); + + + function success(children, data, promise) { + // load demo data if the server returns nothing + var directChildren = children.filter(this.filterDirectChildren,this); + if (directChildren.length === 0){ + for (var i = 0; i < demoData.length; i++) { + var task = this.collection.add(demoData[i]); + task.save(); + } + } + this.listView.collection.add(children.filter(this.filterDirectChildren,this)); + this.listView.render(); + this.updateBreadCrumbs(); + } + + this.collection.fetch({ + success: success, + error: function () { + // switch to localforage database if server isn't present and fetch again + // from there + window.hackflowyOffline = true; + this.$('#header-alerts').append('
Running in offline mode, data may be lost
'); + this.collection.fetch({ + success: success, + context: this + }); + }, + context: this + }); + + var rootItems = new List(this.collection.filter(this.filterDirectChildren,this)); + this.listView = new ListView({collection: rootItems}); + + + // change root item when the url hash changes + // HACK should ideally use http://stackoverflow.com/a/19114496/221742 + $(window).on("hashchange", this.changeRootId.bind(this)); // stop browser going back a page when jamming backspace - window.addEventListener('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}},true); + $(window).on('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}}); + }, - createNewTask: function(e) { - if (e.keyCode != 13) return; - if (!this.input.val().trim()) return; - this.listView.collection.add(new Task({content: this.input.val().trim() })); - this.input.val(''); - } + /** remove non backbone listener on delete **/ + onDestroy: function() { + $(window).off("haschange",this.changeRootId); + $(window).off('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}}); + }, + + /** Get root id for the displayed list from the url hash or 0 **/ + getRootId: function(){ + var hash = window.location.hash.slice(1); + if (hash && pageView.collection.get(hash)) + return hash; + else if (hash) + window.location.hash=''; + return 0; + }, + + /** Called when window location hash changes **/ + changeRootId: function(){ + // change the listview children + this.listView.collection.remove(this.collection.models); + this.listView.collection.add(this.collection.filter(this.filterDirectChildren,this)); + this.updateBreadCrumbs(); + }, + + // Only show direct children + filterDirectChildren: function (child, index, collection) { + var rootId = this.getRootId(); + if (rootId) + return child.get('id') == rootId; + else + return child.get('parentId') == rootId; + }, + + updateBreadCrumbs: function(){ + var rootId = this.getRootId(); + this.$('#task-breadcrumbs').empty(); + if (rootId){ + var current = this.collection.get(rootId); + var breadCrumbs = '';//_.template('<%= content %>')(current.attributes); + var depth=0; + while (current.get('parentId') && depth<100){ + depth++; + current = this.collection.get(current.get('parentId')); + breadCrumbs=_.template('<%= content %> > ')(current.attributes)+breadCrumbs; + } + if (depth>=100) console.error('Max depth exceeded while making breadCrumbs'); + breadCrumbs='Home > '+breadCrumbs; + this.$('#task-breadcrumbs').append(breadCrumbs); + + } + }, }); diff --git a/javascripts/views/task.js b/javascripts/views/task.js index 68161bf..1e5a4ef 100644 --- a/javascripts/views/task.js +++ b/javascripts/views/task.js @@ -20,9 +20,10 @@ define( // The recursive tree view. Ref:http://jsfiddle.net/wassname/zf61mLvh/2/ var TaskView = Backbone.Marionette.CompositeView.extend({ - template: _.template(taskTemplate), //'#task-view-template', + template: _.template(taskTemplate), tagName: 'ul', - className: "shift", + className: "task-view", + viewComparator: List.prototype.comporator, childView: TaskView, childViewContainer: '.children', childViewOptions: { @@ -50,38 +51,42 @@ define( 'click .complete:first': 'markComplete', 'click .uncomplete:first': 'unmarkComlete', 'click .note:first': 'addNote', - 'click .mouse-tip:first': 'foldChildren', + 'click .fold-button:first': 'foldChildren', + 'click .fold:first': 'foldChildren', + 'click .destroy:first': 'destroy', + // custom events + 'focus': 'this.model.focusOnView', }, - collectionEvents: {}, + collectionEvents: { + }, childEvents: {}, initialize: function (options) { var task = this; - // options - if (!('reorderOnSort' in options)) { - options.reorderOnSort = true; - } - // backlink this.model.view = this; - var children = Tasks.filter( + var children = pageView.collection.filter( function (child, index, collection) { return child.get('parentId') === this.model.id; }, this); this.collection = new List(children); + // there is probobly a better way to do this + if (this.isEmpty()){ + this.$el.addClass('empty'); + } + // events this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); // refresh ui hashes after children are rendered this.listenTo(this, 'render:collection', this.bindUIElements); // custom event - this.listenTo(this, 'childview:rerender', this.render); this.listenTo(this, 'focus', this.model.focusOnView); // updates from server @@ -113,9 +118,9 @@ define( /** Get the parent view or root view **/ getParentView: function () { - var parent = Tasks.get(this.model.get('parentId')); + var parent = pageView.collection.get(this.model.get('parentId')); if (parent) return parent.view; - else return listView; + else return pageView.listView; }, /** Update parentId when added to collection **/ @@ -126,7 +131,7 @@ define( /** Focus on the next visible element down despite list level **/ focusOnPrev: function () { - var all = listView.$el.find('ul:visible'); + var all = pageView.listView.$el.find('ul:visible'); var prev = $(all[all.index(this.$el) - 1]); if (prev.length){ prev.find('input:first').focus(); @@ -135,7 +140,7 @@ define( }, focusOnNext: function () { - var all = listView.$el.find('ul:visible'); + var all = pageView.listView.$el.find('ul:visible'); var next = $(all[all.index(this.$el) + 1]); if (next) next.find('input:first').focus(); @@ -169,13 +174,11 @@ define( // move down list by swapping priority with next sibling e.preventDefault(); this.getParentView().collection.moveDown(this.model); - this.trigger('rerender'); this.model.focusOnView(); } else if (e.ctrlKey && e.keyCode == constants.UP_ARROW) { // move up the list e.preventDefault(); this.getParentView().collection.moveUp(this.model); - this.trigger('rerender'); this.model.focusOnView(); } else if (e.keyCode == constants.DOWN_ARROW) { this.focusOnNext(); @@ -188,7 +191,7 @@ define( if (parentId===0){ } else if (parentId){ - parentModel = Tasks.get(parentId); + parentModel = pageView.collection.get(parentId); this.getParentView().collection.remove(this.model); var index = parentModel.view.getParentView().collection.indexOf(parentModel); if (index<0) index=this.getParentView().collection.length-1; @@ -203,7 +206,7 @@ define( var prevSibling = this.$el.prev('ul'); if (prevSibling.length) { var prevSibId = prevSibling.find('input:first').data('id'); - var prevSibView =Tasks.get(prevSibId).view; + var prevSibView =pageView.collection.get(prevSibId).view; this.getParentView().collection.remove(this.model); prevSibView.collection.add(this.model); this.model.focusOnView(); @@ -269,7 +272,7 @@ define( var index= this.getParentView().collection.indexOf(this.model); - var task = Tasks.add({parentId: this.model.get('parentId')}); + var task = pageView.collection.add({parentId: this.model.get('parentId')}); task.save(); if (index>=0) this.getParentView().collection.add(task, {at:index+1}); diff --git a/mockup.html b/mockup.html deleted file mode 100644 index 95e2e1e..0000000 --- a/mockup.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - HackFlowy - - - - - - - - - - - -
-
-
- -
-
-

test

-
-
-
- -
Task 1
-
-
- -
Task 2
-
-
- -
Child 1 Task 2
-
-
- -
Subchild 1 Child 1 Task 2
-
-
- -
Subchild 2 Child 1 Task 2
-
-
- -
Subsubchild 1 Subchild 2 Child 1 Task 2 asbdkjas dkjas bdajsbd kajsdb jasbd kajsb dajskbd akjsb dkjab
-
-
- -
Subsubchild 2 Subchild 2 Child 1 Task 2
-
-
-
-
-
-
- -
Child 1 Task 3
-
-
-
-
- -
Task 3
-
-
-
-
- Make Lists. Catch Fish. <3 Open Source. -
-
-
-
- - - - diff --git a/package.json b/package.json index ef8341a..6a5b6f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "hackflowy", - "version": "0.0.2", + "version": "0.0.3", + "license": "MIT", "description": "A simple WorkFlowy clone", "dependencies": { "body-parser": "^1.14.2", diff --git a/sass/app.scss b/sass/app.scss index d2f4bec..df57299 100644 --- a/sass/app.scss +++ b/sass/app.scss @@ -162,6 +162,9 @@ body { li.shift9 { margin-left: 270px; } + ul.shift { + margin-bottom: 0px; + } } } #main > .children { diff --git a/stylesheets/app.css b/stylesheets/app.css index 5dc4700..396dd9b 100644 --- a/stylesheets/app.css +++ b/stylesheets/app.css @@ -5681,9 +5681,6 @@ body #hackflowy #main .children li.shift8 { body #hackflowy #main .children li.shift9 { margin-left: 270px; } -body #hackflowy #main .children ul.shift { - margin-bottom: 0px; -} /* line 167, ../sass/app.scss */ body #hackflowy #main > .children { margin-left: 0; @@ -5695,54 +5692,58 @@ body #hackflowy #footer { font-size: 12px; } +body #hackflowy #main .children ul.task-view { + margin-bottom: 0px; + margin-left: 0px; +} .options-div{ display: none; } .options{ - position: absolute; -z-index: 1000; - color: rgb(51, 51, 51); -cursor: auto; -display: none; -font-family: 'Helvetica Neue', Arial, sans-serif; -font-size: 16px; -font-weight: normal; -height: 0px; -line-height: 16px; -width: 675px; + position: absolute; + z-index: 1000; + color: rgb(51, 51, 51); + cursor: auto; + display: none; + font-family: 'Helvetica Neue', Arial, sans-serif; + font-size: 16px; + font-weight: normal; + height: 0px; + line-height: 16px; + width: 675px; } .options a { -display: inline-block; -width: 20px; -height: 20px; -cursor: pointer; + display: inline-block; + width: 20px; + height: 20px; + cursor: pointer; } .options-left { -background: #ddd; -border: 1px solid #bbb; -width: 80px; -min-height: 58px; -padding: 10px 0 5px 10px; -margin-bottom: 10px; -border-radius: 4px; -font-family: 'Helvetica Neue', Arial, Sans-serif; -z-index: 8; -margin-top: 8px; -margin-left: -29px; + background: #ddd; + border: 1px solid #bbb; + width: 80px; + min-height: 58px; + padding: 10px 0 5px 10px; + margin-bottom: 10px; + border-radius: 4px; + font-family: 'Helvetica Neue', Arial, Sans-serif; + z-index: 8; + margin-top: 16px; + margin-left: -29px; } .options-left a { -color: #555; -margin-bottom: 5px; -font-size: 12px; -display: block; -margin-right: 10px; -width: auto; + color: #555; + margin-bottom: 5px; + font-size: 12px; + display: block; + margin-right: 10px; + width: auto; } .options-left a:hover { @@ -5750,42 +5751,85 @@ width: auto; } .options-left > .mouse-tip { -content: ''; -position: absolute; -left: 0; -height: 25px; -width: 90px; -margin-top: -36px; -margin-left: -1%; + content: ''; + position: absolute; + left: 0; + height: 25px; + width: 90px; + margin-top: -36px; + margin-left: -1%; } .options-left > .mouse-tip:before { -content: ''; -position: absolute; -top: 10px; -left: 19%; -margin-left: -15px; -height: 0; -width: 0; -border-bottom: 15px solid #ddd; -border-left: 15px solid transparent; -border-right: 15px solid transparent; + content: ''; + position: absolute; + top: 10px; + left: 19%; + margin-left: -15px; + height: 0; + width: 0; + border-bottom: 15px solid #ddd; + border-left: 15px solid transparent; + border-right: 15px solid transparent; } .mouse-tip:before { -content: ''; -position: absolute; -top: 10px; -left: 50%; -margin-left: -15px; -height: 0; -width: 0; -border-bottom: 15px solid #ddd; -border-left: 15px solid transparent; -border-right: 15px solid transparent; + content: ''; + position: absolute; + top: 10px; + left: 50%; + margin-left: -15px; + height: 0; + width: 0; + border-bottom: 15px solid #ddd; + border-left: 15px solid transparent; + border-right: 15px solid transparent; } +.options-left > .fold-button { + position: absolute; + left: 0; + /*height: 25px;*/ + /*width: 90px;*/ + margin-top: -45px; + margin-left: -3%; + font-size: 1.5em; +} +.options-left > .fold-button:hover { + text-decoration: none; +} + +/* By default show fold button */ +.options-left>.fold-button:before { + content: "-"; +} +/* no fold button on empty nodes */ +.empty .options-left:first-child > .fold-button:before { + content: ''; +} +/* expand button on folded nodes */ +.folded .options-left:first-child>.fold-button:before { + content: "+"; +} .task-completed{ text-decoration: line-through; } + +.subtle-link { + color: rgb(51, 51, 51); +} +.subtle-link:hover { + color: rgb(51, 51, 51); + text-decoration: underline; +} + +#task-breadcrumbs { + font-size: smaller; +} +#task-breadcrumbs a { + color: rgb(51, 51, 51); +} +#task-breadcrumbs a:hover { + color: rgb(51, 51, 51); +} diff --git a/stylesheets/normalize.css b/stylesheets/normalize.css deleted file mode 100644 index a6de71b..0000000 --- a/stylesheets/normalize.css +++ /dev/null @@ -1,405 +0,0 @@ -/* normalize.css v2.1.0 | MIT License | git.io/normalize */ -/* ========================================================================== - HTML5 display definitions - ========================================================================== */ -/** - * Correct `block` display not defined in IE 8/9. - */ -/* line 22, ../sass/normalize.scss */ -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -nav, -section, -summary { - display: block; -} - -/** - * Correct `inline-block` display not defined in IE 8/9. - */ -/* line 32, ../sass/normalize.scss */ -audio, -canvas, -video { - display: inline-block; -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ -/* line 41, ../sass/normalize.scss */ -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address styling not present in IE 8/9. - */ -/* line 50, ../sass/normalize.scss */ -[hidden] { - display: none; -} - -/* ========================================================================== - Base - ========================================================================== */ -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ -/* line 64, ../sass/normalize.scss */ -html { - font-family: sans-serif; - /* 1 */ - -webkit-text-size-adjust: 100%; - /* 2 */ - -ms-text-size-adjust: 100%; - /* 2 */ -} - -/** - * Remove default margin. - */ -/* line 74, ../sass/normalize.scss */ -body { - margin: 0; -} - -/* ========================================================================== - Links - ========================================================================== */ -/** - * Address `outline` inconsistency between Chrome and other browsers. - */ -/* line 86, ../sass/normalize.scss */ -a:focus { - outline: thin dotted; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ -/* line 95, ../sass/normalize.scss */ -a:active, -a:hover { - outline: 0; -} - -/* ========================================================================== - Typography - ========================================================================== */ -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari 5, and Chrome. - */ -/* line 108, ../sass/normalize.scss */ -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9, Safari 5, and Chrome. - */ -/* line 117, ../sass/normalize.scss */ -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. - */ -/* line 126, ../sass/normalize.scss */ -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari 5 and Chrome. - */ -/* line 134, ../sass/normalize.scss */ -dfn { - font-style: italic; -} - -/** - * Address differences between Firefox and other browsers. - */ -/* line 142, ../sass/normalize.scss */ -hr { - -moz-box-sizing: content-box; - box-sizing: content-box; - height: 0; -} - -/** - * Address styling not present in IE 8/9. - */ -/* line 152, ../sass/normalize.scss */ -mark { - background: #ff0; - color: #000; -} - -/** - * Correct font family set oddly in Safari 5 and Chrome. - */ -/* line 164, ../sass/normalize.scss */ -code, -kbd, -pre, -samp { - font-family: monospace, serif; - font-size: 1em; -} - -/** - * Improve readability of pre-formatted text in all browsers. - */ -/* line 173, ../sass/normalize.scss */ -pre { - white-space: pre-wrap; -} - -/** - * Set consistent quote types. - */ -/* line 181, ../sass/normalize.scss */ -q { - quotes: "\201C" "\201D" "\2018" "\2019"; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ -/* line 189, ../sass/normalize.scss */ -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ -/* line 198, ../sass/normalize.scss */ -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -/* line 205, ../sass/normalize.scss */ -sup { - top: -0.5em; -} - -/* line 209, ../sass/normalize.scss */ -sub { - bottom: -0.25em; -} - -/* ========================================================================== - Embedded content - ========================================================================== */ -/** - * Remove border when inside `a` element in IE 8/9. - */ -/* line 221, ../sass/normalize.scss */ -img { - border: 0; -} - -/** - * Correct overflow displayed oddly in IE 9. - */ -/* line 229, ../sass/normalize.scss */ -svg:not(:root) { - overflow: hidden; -} - -/* ========================================================================== - Figures - ========================================================================== */ -/** - * Address margin not present in IE 8/9 and Safari 5. - */ -/* line 241, ../sass/normalize.scss */ -figure { - margin: 0; -} - -/* ========================================================================== - Forms - ========================================================================== */ -/** - * Define consistent border, margin, and padding. - */ -/* line 253, ../sass/normalize.scss */ -fieldset { - border: 1px solid silver; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ -/* line 264, ../sass/normalize.scss */ -legend { - border: 0; - /* 1 */ - padding: 0; - /* 2 */ -} - -/** - * 1. Correct font family not being inherited in all browsers. - * 2. Correct font size not being inherited in all browsers. - * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. - */ -/* line 278, ../sass/normalize.scss */ -button, -input, -select, -textarea { - font-family: inherit; - /* 1 */ - font-size: 100%; - /* 2 */ - margin: 0; - /* 3 */ -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ -/* line 290, ../sass/normalize.scss */ -button, -input { - line-height: normal; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. - * Correct `select` style inheritance in Firefox 4+ and Opera. - */ -/* line 302, ../sass/normalize.scss */ -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ -/* line 317, ../sass/normalize.scss */ -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; - /* 2 */ - cursor: pointer; - /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ -/* line 327, ../sass/normalize.scss */ -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * 1. Address box sizing set to `content-box` in IE 8/9. - * 2. Remove excess padding in IE 8/9. - */ -/* line 337, ../sass/normalize.scss */ -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; - /* 1 */ - padding: 0; - /* 2 */ -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome - * (include `-moz` to future-proof). - */ -/* line 348, ../sass/normalize.scss */ -input[type="search"] { - -webkit-appearance: textfield; - /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; - /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari 5 and Chrome - * on OS X. - */ -/* line 361, ../sass/normalize.scss */ -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ -/* line 370, ../sass/normalize.scss */ -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * 1. Remove default vertical scrollbar in IE 8/9. - * 2. Improve readability and alignment in all browsers. - */ -/* line 380, ../sass/normalize.scss */ -textarea { - overflow: auto; - /* 1 */ - vertical-align: top; - /* 2 */ -} - -/* ========================================================================== - Tables - ========================================================================== */ -/** - * Remove most spacing between table cells. - */ -/* line 393, ../sass/normalize.scss */ -table { - border-collapse: collapse; - border-spacing: 0; -} diff --git a/templates/task.html b/templates/task.html index 7ddece2..30fb4f1 100644 --- a/templates/task.html +++ b/templates/task.html @@ -4,12 +4,15 @@