From 1cee7fe2696bf94a5efebed80d7284d9e6fa6a82 Mon Sep 17 00:00:00 2001 From: Radek Stepan Date: Tue, 7 Oct 2014 21:24:49 -0700 Subject: [PATCH] use ractive-ractive --- Gruntfile.coffee | 2 +- README.md | 11 +- TODO.md | 1 - bower.json | 1 + public/js/app.bundle.js | 307 +++++++++++++++++++++++----------------- 5 files changed, 191 insertions(+), 131 deletions(-) diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 39453d3..0d14aea 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -33,7 +33,7 @@ module.exports = (grunt) -> 'vendor/lodash/dist/lodash.js' 'vendor/ractive/ractive.js' 'vendor/ractive-transitions-fade/ractive-transitions-fade.js' - 'vendor/ractive-adaptor/adaptor.js' + 'vendor/ractive-ractive/index.js' 'vendor/firebase/firebase.js' 'vendor/firebase-simple-login/firebase-simple-login.js' 'vendor/superagent/superagent.js' diff --git a/README.md b/README.md index a0423a2..e45858e 100644 --- a/README.md +++ b/README.md @@ -16,21 +16,26 @@ GitHub Burndown Chart as a service. Public repos are free, for private access au - $2.5 Node.js PaaS via Gandi with promo code `PAASLAUNCH-C50E-B077-A317`. - let people vote on features they want to see fast: [tally.tl](http://tally.tl/). - use [readme.io](https://readme.io/) for documentation -- have people pay outright or call me instead telling me what they'll use it for and they will get 6 months free; the idea is to get feedback from them; also, if they want to leave/close account/have not used app in a while, give them more free months for their feedback on how to make the app better; feedback is more important than money in early stages - send handwritten thank you cards to the first customers - use [DigitalOcean](https://www.digitalocean.com/) as a GitHub Student (@bath.edu email) to get $100 in platform credits which translates to 20 months on the slowest (fast enough) dyno ##Plans -###Community +###Community Plan - your repos are saved locally - no auto-updates to milestones, everything fetched on page load - no private repos -###Business +###Business Plan - you need to pay for a license to use the app for business purposes - repos, milestones saved remotely - auto-update with new information - private repos + +###Free Forever Business Plan + +Let me call you every 3 months to ask how you are doing, how you are using the software, what can I improve, and you will get 3 months usage for free. The idea is to keep in touch with the most loyal customers, to hear them say how great/shabby the app is. If they don't want to talk they can always pay for the Business Plan. + +If someone stops using the app, send them an email asking them for a good time to call so I can make things right. They would get 3 months usage as well. \ No newline at end of file diff --git a/TODO.md b/TODO.md index 72576f3..b37a9f7 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,6 @@ ###Main -1. Switch to [ractive-ractive](https://github.com/rstacruz/ractive-ractive) and use [proxied events](http://docs.ractivejs.org/latest/components#events). 1. Load projects from `localStorage` (use sync). If we are on *Project* or *Chart* page, add it behind the scenes. 1. Now we fetch all milestones for our repo if we don't have any cached already. This calculates the points for each milestone. *The first two steps can happen on a Router level* 1. Continue with page-specific actions. diff --git a/bower.json b/bower.json index 88c1ef1..76435fc 100644 --- a/bower.json +++ b/bower.json @@ -6,6 +6,7 @@ "normalize-css": "2.1.3", "ractive": "~0.6.0", "ractive-adaptor": "radekstepan/ractive-adaptor-ractive", + "ractive-ractive": "rstacruz/ractive-ractive", "firebase": "~1.0.21", "firebase-simple-login": "~1.6.3", "localforage": "~0.9.2", diff --git a/public/js/app.bundle.js b/public/js/app.bundle.js index 7e232d0..731e166 100644 --- a/public/js/app.bundle.js +++ b/public/js/app.bundle.js @@ -21249,150 +21249,205 @@ Ractive.transitions.fade = fade; })); -;// Ractive adaptor plugin -// ======================= -// -// This plugin allows you to have several Ractive instances sharing -// a single model, without using any third party libraries. -// -// Usage: -// -// var ractiveOne = new Ractive({ -// el: 'one', -// template: templateOne -// }); -// -// var ractiveTwo = new Ractive({ -// el: 'two', -// template: templateTwo, -// data: ractiveOne, -// adaptors: [ 'Ractive' ] -// }); -// -// Changes to either Ractive will be reflected in both. +;;(function (root, factory) { -(function ( global, factory ) { + if (typeof define === 'function' && define.amd) { + define(['ractive'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('ractive')); + } else { + factory(root.Ractive); + } - 'use strict'; +}(this, function (Ractive) { - // CommonJS/Modules. - if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) { - factory( require( 'ractive' ) ); + var Adaptor = Ractive.adaptors.Ractive = { + filter: filter, + wrap: wrap + }; + + /* + * Advanced options: + * You can adjust these settings via `Ractive.adaptors.Ractive.maxKeyLength` + * and so on. There's usually no need to do that, but it may be good for + * optimizing tests. + */ + + Adaptor.fireWrapEvents = true; + Adaptor.maxKeyLength = 2048; + + /* + * Check if the child is an Ractive instance. + * + * Also, if this key has been wrapped before, don't rewrap it. (Happens on + * deeply-nested values, and .reset() for some reason.) + */ + + function filter (child, keypath, parent) { + if (!(child instanceof Ractive)) + return false; + + if (parent && + parent._ractiveWraps && + parent._ractiveWraps[keypath]) + return false; + + return true; + } + + /* + * Global write lock. + * This prevents infinite loops from happening where a parent will set a + * value on the child, and the child will attempt to write back to the + * parent, and so on. + */ + + var locked = Adaptor.locked = {}; + + function lock (key, fn) { + if (locked[key]) return; + try { + locked[key] = true; + return fn(); + } finally { + delete locked[key]; } + } - // AMD. - else if ( typeof define === 'function' && define.amd ) { - define([ 'ractive' ], factory ); - } + /* + * Returns a wrapped Adaptor for Ractive. + * See: http://docs.ractivejs.org/latest/writing-adaptor-plugins + */ - // Browser global. - else if ( global.Ractive ) { - factory( global.Ractive ); - } + function wrap (parent, child, keypath, prefixer) { + setup(); - else { - throw new Error( 'Could not find Ractive! It must be loaded before the ractive-adaptor-ractive plugin' ); - } - -}( typeof window !== 'undefined' ? window : this, function ( Ractive ) { - - 'use strict'; - - if ( !Ractive ) { - throw new Error( 'Could not find Ractive! Check your paths config' ); - } - - var Wrapper; - - // Save under this path. - Ractive.adaptors.Ractive = { - filter: function ( object ) { - return object instanceof Ractive; - }, - wrap: function ( ractive, otherRactive, keypath, prefixer ) { - return new Wrapper( ractive, otherRactive, keypath, prefixer ); - } + return { + get: get, + set: set, + reset: reset, + teardown: teardown }; - Wrapper = function ( ractive, otherRactive, keypath, prefixer ) { - var wrapper = this; + /* + * Initializes the adaptor. Performs a few tricks: + * + * [1] If the child has its own Ractive instances, recurse upwards. This + * will do `parent.set('child.grandchild', instance)` so that the + * `parent` can listen to the grandchild. + */ - // The original Ractive. - this.otherRactive = otherRactive; + function setup () { + checkForRecursion(); + markAsWrapped(); + parent.set(prefixer(get())); // [1] + child.on('change', onChange); - // Listen them `change`. - this.changeHandler = otherRactive.on( 'change', function ( changeHash ) { - // Only set if we are not setting them. - if (wrapper.otherSetting) { - return; - } - wrapper.setting = true; - ractive.set( prefixer( changeHash ) ); - wrapper.setting = false; + if (Adaptor.fireWrapEvents) { + child.fire('wrap', parent, keypath); + parent.fire('wrapchild', child, keypath); + } + } + + function teardown () { + delete parent._ractiveWraps[keypath]; + child.off('change', onChange); + + if (Adaptor.fireWrapEvents) { + child.fire('unwrap', parent, keypath); + parent.fire('unwrapchild', child, keypath); + } + } + + /* + * Propagate changes from child to parent. + * We well break it apart into key/vals and set those individually because + * some values may be locked. + */ + + function onChange (updates) { + each(updates, function (value, key) { + lock(child._guid + key, function () { + parent.set(keypath + '.' + key, value); }); + }); + } - // Listen them `reset`. - this.resetHandler = otherRactive.on( 'reset', function ( newData ) { - // Only set if we are not setting them. - if (wrapper.otherSetting) { - return; - } - wrapper.setting = true; - ractive.update( keypath ); - wrapper.setting = false; - }); + /* + * Returns all attributes of the child, including computed properties. + * See: https://github.com/ractivejs/ractive/issues/1250 + */ - // The opposite of a prefixer, setting properties on one level "up". - // https://github.com/ractivejs/ractive/blob/ccbe31bbfc488e780c8ffbea9c8b17cad6bc1c52/src/viewmodel/prototype/adapt.js#L52-L67 - var defixer = function( changeHash ) { - var obj = {}, key; - for (key in changeHash) { - // TODO: this is probably not complete! - obj[ key.split('.').slice(1).join('.') ] = changeHash[ key ]; - } - return obj; - } + function get () { + // Optimization: if there are no computed properties, returning all + // non-computed data should suffice. + if (!child.computed) return child.get(); - // Listen to our changes. In two-way binding DOM events do not go through - // `set` but trigger a `change` event. - ractive.on('change', function( changeHash ) { - wrapper.otherSetting = true; - otherRactive.set(defixer(changeHash)); - wrapper.otherSetting = false; - }); - }; + var re = {}; + + each(child.get(), function (val, key) { + re[key] = val; + }); - Wrapper.prototype = { - // Returns the value at keypath or all data. - get: function () { - return this.otherRactive.get(); - }, - // Updates data notifying observers of affected keypaths. - set: function ( keypath, value ) { - // Only set if the we didn't originate the change. - if (!this.setting) { - this.otherRactive.set( keypath, value ); - } - }, - // Resets the entire ractive.data object. - reset: function ( object ) { - if (this.setting) { - return; - } + each(child.computed, function (_, key) { + if (typeof re[key] === 'undefined') + re[key] = child.get(key); + }); - if ( object instanceof Ractive || typeof object !== 'object' ) { - return false; - } + return re; + } - this.otherRactive.reset( object ); - }, - // Unrenders this Ractive instance, removing any event handlers that - // were bound automatically by Ractive. - teardown: function () { - this.changeHandler.cancel(); - this.resetHandler.cancel(); - } - }; + function set (key, value) { + lock(child._guid + key, function () { + child.set(key, value); + }); + } + + /* + * Allow setting values by passing a POJO to .set(), for instance, + * `.set('child', { ... })`. If anything else is passed onto .set() + * (like another Ractive instance, or another adaptor'able), destroy + * this wrapper. + */ + + function reset (object) { + if (object && object.constructor === Object) { + child.set(object); + } else { + return false; + } + } + + /* + * Die on recursion. + * Keypath will look like 'child.sub.parent.child.sub.parent' ad nauseum. + */ + + function checkForRecursion () { + if (keypath && keypath.length > Adaptor.maxKeyLength) + throw new Error("Keypath too long (possible circular dependency)"); + } + + /* + * Let future wrappers know what we have wrapped Ractive instances. + * This value is used on `filter()`. + */ + + function markAsWrapped () { + if (!parent._ractiveWraps) parent._ractiveWraps = {}; + parent._ractiveWraps[keypath] = child; + } + } + + /* + * Cross-browser forEach helper + */ + + function each (obj, fn) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) fn(obj[key], key); + } + } })); ;/* Firebase v1.0.21 */ (function() {var h,aa=this;function n(a){return void 0!==a}function ba(){}function ca(a){a.sb=function(){return a.md?a.md:a.md=new a}}