diff options
Diffstat (limited to 'tree/public/assets/js/vendor/view')
| -rw-r--r-- | tree/public/assets/js/vendor/view/OKTmplView.js | 97 | ||||
| -rw-r--r-- | tree/public/assets/js/vendor/view/Router.js | 82 | ||||
| -rw-r--r-- | tree/public/assets/js/vendor/view/ScrollableView.js | 31 | ||||
| -rw-r--r-- | tree/public/assets/js/vendor/view/View.js | 150 |
4 files changed, 360 insertions, 0 deletions
diff --git a/tree/public/assets/js/vendor/view/OKTmplView.js b/tree/public/assets/js/vendor/view/OKTmplView.js new file mode 100644 index 0000000..86f1ee3 --- /dev/null +++ b/tree/public/assets/js/vendor/view/OKTmplView.js @@ -0,0 +1,97 @@ +/** + * An OK templating language I just made up + * + * Templates: + * <!-- Add data-ok-* attrs --> + * <span data-ok-msg></span + * + * Usage: + * // "Compile" + * var view = new OKTmplView('tmpl-id') + * // Attach to DOM before render + * document.body.appendChild(view.el) + * // Render to heart's content + * view.render(data) + */ +function OKTmplView (id) { + var $el = this.$el = $('#ok-' + id).children().clone() + var el = this.el = $el[0] + var pathMetas = reduceToPathMetas(el) + + this.render = function (data) { + data = data || {} + pathMetas.forEach(function (pathMeta) { + $el.find('[' + pathMeta.attr + ']').html( + getPath(data, pathMeta.path) + ) + }) + } + + this.hide = function () { + $el.hide() + } + + this.show = function () { + $el.show() + } + + function reduceToPathMetas (el) { + return flatten(next([el])) + + function next (children) { + return Array.prototype.slice.call(children) + .reduce(function (result, child) { + result.push(getPathMetas(child).concat(next(child.childNodes))) + return result + }, []) + } + + function getPathMetas (el) { + var attrs = Array.prototype.slice.call(el.attributes || []) + return attrs + .filter(specified) + .reduce(function (result, attrNode) { + if (/^data-ok-/.test(attrNode.nodeName)) { + result.push({ + attr: attrNode.nodeName, + path: attrNode.nodeName.split('-').slice(2), + }) + } + return result + }, []) + + function specified (attrNode) { + return typeof attrNode.specified === 'undefined' || + attrNode.specified + } + } + } +} + +/** + * Get value for key at path where path is an array of keys + */ +function getPath (obj, path) { + obj = obj || {} + path = path || [] + return path.reduce(function (obj, key, i) { + if (i < path.length - 1) + return obj[key] || {} + else + return obj[key] + }, obj) +} + +function flatten (list) { + var result = [] + next(list || []) + return result + + function next (list) { + list.forEach(function (item) { + return typeof item.length !== 'undefined' + ? next(item) + : result.push(item) + }) + } +} diff --git a/tree/public/assets/js/vendor/view/Router.js b/tree/public/assets/js/vendor/view/Router.js new file mode 100644 index 0000000..3b0d939 --- /dev/null +++ b/tree/public/assets/js/vendor/view/Router.js @@ -0,0 +1,82 @@ +var Router = View.extend({ + + routeByHash: false, + + go: function(url){ + this.parseRoute(url) + }, + + pushState: function(url){ + if (this.routeByHash) { + window.location.hash = url + } + else if (window.history) { + window.history.pushState(null, null, url) + } + }, + + route: function(){ + var path = this.routeByHash ? window.location.hash.substr(0) : window.location.pathname + path = path || "/" + this.originalPath = path + this.parseRoute(path) + }, + + parseRoute: function(pathname){ + + pathname = pathname.replace(/^#/, "") + + if (pathname[0] !== "/") { pathname = "/" + pathname } + + var routes = this.routes, + path = pathname.split("/"); + + for (var i = 0; i < path.length; i++) { + if (! path[i].length) { + path[i] = null + } + } + + if (pathname in routes) { + this[this.routes[pathname]]() + return + } + + if (path[path.length-1] == null) { + path.pop() + } + + for (var route in routes) { + var routePath = route.split("/") + if (routePath[1] == path[1]) { + if (routePath[2] && routePath[2].indexOf(":") !== -1 && path[2] && (path[3] === routePath[3]) ) { + this[this.routes[route]](path[2]) + return + } + else if (routePath[2] == path[2]) { + if (routePath[3] && path[3]) { + if (routePath[3].indexOf(":") !== -1) { + this[this.routes[route]](path[3]) + return + } + else if (routePath[3] == path[3]) { + this[this.routes[route]]() + return + } + } + else if (! routePath[3] && ! path[3]) { + this[this.routes[route]]() + return + } + } + else if (! routePath[2] && (! path[2].length || ! path[2])) { + this[this.routes[route]]() + return + } + } + } + // Redirect to root on 404 + window.location = '/' + } + +}) diff --git a/tree/public/assets/js/vendor/view/ScrollableView.js b/tree/public/assets/js/vendor/view/ScrollableView.js new file mode 100644 index 0000000..a01809c --- /dev/null +++ b/tree/public/assets/js/vendor/view/ScrollableView.js @@ -0,0 +1,31 @@ +var ScrollableView = View.extend({ + + events: { + "load img": "deferRefresh", + }, + + deferScrollToTop: function(){ + if (! this.scroller) return + setTimeout(this.scrollToTop.bind(this), 0) + }, + + refreshScroller: function(){ + if (! this.scroller) return + this.scroller.refresh() + clearTimeout( this.scrollerRefreshTimeout ) + }, + + scrollerRefreshTimeout: null, + deferRefresh: function(){ + if (! this.scroller) return + clearTimeout( this.scrollerRefreshTimeout ) + this.scrollerRefreshTimeout = setTimeout(this.refreshScroller.bind(this)) + }, + + scrollToTop: function(){ + if (! this.scroller) return + this.scroller.refresh() + app.collection.scroller.scrollTo(0, 0) + }, + +})
\ No newline at end of file diff --git a/tree/public/assets/js/vendor/view/View.js b/tree/public/assets/js/vendor/view/View.js new file mode 100644 index 0000000..09b722e --- /dev/null +++ b/tree/public/assets/js/vendor/view/View.js @@ -0,0 +1,150 @@ +var View = (function($, _){ + + var View = function(options) { + this._id = _.uniqueId('view') + this.type = "view" + options || (options = {}); + _.extend(this, _.pick(options, viewOptions)) + this._ensureElement() + this.initialize.apply(this, arguments) + this.delegateEvents() + } + + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + _.extend(View.prototype, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + $: function(selector) { + return this.$el.find(selector); + }, + + initialize: function(){}, + + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof $ ? element : $(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + if (is_mobile) { + if (eventName === 'mouseenter' || eventName === 'mouseleave') { + continue + } +/* + // cordova apps have some issue with click on android + if (is_android && eventName === 'click') { + eventName = 'touchstart' + } +*/ + } + eventName += '.delegateEvents' + this._id; + if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this._id); + return this; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + this.setElement(_.result(this, 'el'), false); + }, + + preventDefault: function(e){ + e && e.preventDefault() + }, + + stopPropagation: function(e){ + e && e.stopPropagation() + }, + + }); + + + var extend = function(protoProps, staticProps) { + var staticProps = staticProps || {} + var parent = this; + var child; + var childEvents = {}; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent's constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Extend events so we can subclass views + _.extend(childEvents, parent.prototype.events, protoProps.events) + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + var Surrogate = function(){ this.constructor = child; }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate; + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Set a convenience property in case the parent's prototype is needed + // later. + child.prototype.__super__ = parent.prototype; + child.prototype.events = childEvents + + return child; + }; + + View.extend = extend; + + return View; +})(jQuery, _) |
