Skip to content

Commit

Permalink
Issue #124: Use ES6 Promises instead of lavaca/util/Promise
Browse files Browse the repository at this point in the history
Using [this Promise API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise):
- Updated all functions which were directly constructing lavaca/util/Promise
  instances to construct Promise instances instead
- Updated all functions calling a `.then`, `.always`, `.success`, or `.error`
  method to only use ES6-compliant `.then` or `.catch` methods.
- Updated unit tests

I did skip updating lavaca/ui/Form and its unit tests... I'm somewhat sure it
will be removed in issue #121.

*Side effect*: ES6-ifying `mvc/View#render` into "sequential-looking" `.then`
chains resulted in removing two methods:
- `mvc/View#renderTemplate` because it boiled down to one statement:
  `template.render(model)`
- `mvc/View#bindRenderEvents` because it de-duplicated adding 'rendersuccess'
  and 'rendererror' onto the end of `render()` and `renderPageView()` at the
  expense of code flow readability imo. As an aside, I suspect we might be able
  to dedupe the entire `render()` and `renderPageView()` methods.

*Side effect*: lavaca/util/Map: synchronous AJAX is incompatible with ES6 Promises because
  they force asynchronicity. Change lavaca/util/Map to return a (non-ES6)
  jQuery Promise instead.
  • Loading branch information
zship committed Jul 11, 2014
1 parent 3a16510 commit 64ccae6
Show file tree
Hide file tree
Showing 25 changed files with 847 additions and 1,130 deletions.
4 changes: 3 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"globals": {
"define": false
"define": false,
"Promise": false
},

"node": true,
"browser": true,
"devel": true,

"sub": true,
Expand Down
5 changes: 4 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ module.exports = function(grunt) {
jasmine: {
all: {
// PhantomJS is not fully ES5-compatible; shim it
src: 'src/components/es5-shim/es5-shim.js',
src: [
'src/components/es5-shim/es5-shim.js',
'src/components/es6-shim/es6-shim.js'
],
options: {
specs: 'test/unit/**/*.js',
template: require('grunt-template-jasmine-requirejs'),
Expand Down
1 change: 1 addition & 0 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"require-dust": "git://github.com/georgehenderson/require-dust.git#master",
"mout": "~0.7.1",
"es5-shim": "~2.1.0",
"es6-shim": "0.13.0",
"requirejs ": "~2.1.8",
"iscroll ": "~5.0.5",
"hammerjs": "1.0.5"
Expand Down
104 changes: 46 additions & 58 deletions src/mvc/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ define(function(require) {
Connectivity = require('lavaca/net/Connectivity'),
Template = require('lavaca/ui/Template'),
Config = require('lavaca/util/Config'),
Promise = require('lavaca/util/Promise'),
Translation = require('lavaca/util/Translation');

function _stopEvent(e) {
Expand All @@ -27,8 +26,8 @@ define(function(require) {

function _isExternal(url) {
var match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);
if (typeof match[1] === 'string'
&& match[1].length > 0
if (typeof match[1] === 'string'
&& match[1].length > 0
&& match[1].toLowerCase() !== location.protocol) {
return true;
}
Expand Down Expand Up @@ -115,48 +114,49 @@ define(function(require) {
* @param {Event} e The event object
*/
onTapLink: function(e) {
      var link = $(e.currentTarget),
          defaultPrevented = e.isDefaultPrevented(),
          url = link.attr('href') || link.attr('data-href'),
          rel = link.attr('rel'),
          target = link.attr('target'),
          isExternal = link.is('[data-external]') || _isExternal(url),
var link = $(e.currentTarget),
defaultPrevented = e.isDefaultPrevented(),
url = link.attr('href') || link.attr('data-href'),
rel = link.attr('rel'),
target = link.attr('target'),
isExternal = link.is('[data-external]') || _isExternal(url),
metaKey = e.type === 'tap' ? (e.gesture.srcEvent.ctrlKey || e.gesture.srcEvent.metaKey) : (e.ctrlKey || e.metaKey);
if (metaKey) {
target = metaKey ? '_blank' : (target ? target : '_self');
}
      if (!defaultPrevented) {
if (!defaultPrevented) {
if (Device.isCordova() && target) {
e.preventDefault();
window.open(url, target || '_blank');
} else if (isExternal || target) {
window.open(url, target);
          return true;
        } else {
          e.preventDefault();
          if (rel === 'back') {
            History.back();
          } else if (rel === 'force-back' && url) {
return true;
} else {
e.preventDefault();
if (rel === 'back') {
History.back();
} else if (rel === 'force-back' && url) {
History.isRoutingBack = true;
this.router.exec(url, null, null).always(function() {
var _always = function() {
History.isRoutingBack = false;
});
};
this.router.exec(url, null, null).then(_always, _always);
} else if (rel === 'cancel') {
            this.viewManager.dismiss(e.currentTarget);
          } else if (url) {
            url = url.replace(/^\/?#/, '');
            this.router.exec(url).error(this.onInvalidRoute);
          }
        }
      }
    },
this.viewManager.dismiss(e.currentTarget);
} else if (url) {
url = url.replace(/^\/?#/, '');
this.router.exec(url).catch(this.onInvalidRoute);
}
}
}
},
/**
* Makes an AJAX request if the user is online. If the user is offline, the returned
* promise will be rejected with the string argument "offline". (Alias for [[Lavaca.net.Connectivity]].ajax)
* @method ajax
*
* @param {Object} opts jQuery-style AJAX options
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
ajax: function() {
return Connectivity.ajax.apply(Connectivity, arguments);
Expand All @@ -167,12 +167,9 @@ define(function(require) {
*
* @param {Object} args Data of any type from a resolved promise returned by Application.beforeInit(). Defaults to null.
*
* @return {Lavaca.util.Promise} A promise that resolves when the application is ready for use
* @return {Promise} A promise that resolves when the application is ready for use
*/
init: function(args) {
var promise = new Promise(this),
_cbPromise,
lastly;
Template.init();
/**
* View manager used to transition between UI states
Expand All @@ -191,32 +188,24 @@ define(function(require) {
*/
this.router = router.setViewManager(this.viewManager);


lastly = function() {
this.router.startHistory();
if (!this.router.hasNavigated) {
promise.when(
this.router.exec(this.initialHashRoute || this.initRoute, this.initState, this.initParams)
);
if (this.initState) {
History.replace(this.initState.state, this.initState.title, this.initState.url);
}
} else {
promise.resolve();
}
}.bind(this);

this.bindLinkHandler();

if (this._callback) {
_cbPromise = this._callback(args);
_cbPromise instanceof Promise ? _cbPromise.then(lastly, promise.rejector()) : lastly();
} else {
lastly();
}
return promise.then(function() {
this.trigger('ready');
});
return Promise.resolve()
.then(function() {
return this._callback(args);
}.bind(this))
.then(function() {
this.router.startHistory();
if (!this.router.hasNavigated) {
if (this.initState) {
History.replace(this.initState.state, this.initState.title, this.initState.url);
}
return this.router.exec(this.initialHashRoute || this.initRoute, this.initState, this.initParams);
}
}.bind(this))
.then(function() {
this.trigger('ready');
}.bind(this));
},
/**
* Binds a global link handler
Expand Down Expand Up @@ -250,11 +239,10 @@ define(function(require) {
*
* @param {Lavaca.util.Config} Config cache that's been initialized
*
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
beforeInit: function(Config) {
var promise = new Promise();
return promise.resolve(null);
return Promise.resolve(null);
}
});

Expand Down
9 changes: 4 additions & 5 deletions src/mvc/Collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ define(function(require) {
var Model = require('lavaca/mvc/Model'),
Connectivity = require('lavaca/net/Connectivity'),
ArrayUtils = require('lavaca/util/ArrayUtils'),
Promise = require('lavaca/util/Promise'),
clone = require('mout/lang/deepClone'),
merge = require('mout/object/merge');

Expand Down Expand Up @@ -188,7 +187,7 @@ define(function(require) {
* @return {Boolean} false if no items were able to be added, true otherwise.
*/
//@event addItem

insert: function(insertIndex, item /*, item1, item2, item3...*/) {
var result = false,
idAttribute = this.TModel.prototype.idAttribute,
Expand Down Expand Up @@ -647,7 +646,7 @@ define(function(require) {
* @method saveToServer
*
* @param {String} url The URL to which to post the data
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
saveToServer: function(url) {
return this.save(function(model, changedAttributes, attributes) {
Expand All @@ -659,13 +658,13 @@ define(function(require) {
changedAttributes[this.idAttribute] = id;
data = changedAttributes;
}
return (new Promise(this)).when(Connectivity.ajax({
return Connectivity.ajax({
url: url,
cache: false,
type: 'POST',
data: data,
dataType: 'json'
}));
});
});
},
/**
Expand Down
33 changes: 13 additions & 20 deletions src/mvc/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ define(function(require) {
var Connectivity = require('lavaca/net/Connectivity'),
History = require('lavaca/net/History'),
Disposable = require('lavaca/util/Disposable'),
Promise = require('lavaca/util/Promise'),
StringUtils = require('lavaca/util/StringUtils'),
Translation = require('lavaca/util/Translation');

Expand Down Expand Up @@ -43,35 +42,29 @@ define(function(require) {
*
* @param {String} action The name of the controller method to call
* @param {Object} params Key-value arguments to pass to the action
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
/**
* Executes an action on this controller
* @method exec
* @param {String} action The name of the controller method to call
* @param {Object} params Key-value arguments to pass to the action
* @param {Object} state A history record object
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
exec: function(action, params, state) {
this.params = params;
this.state = state;
var promise = new Promise(this),
model,
result;
var model;
if (state) {
model = state.state;
promise.success(function() {
document.title = state.title;
});
}
result = this[action](params, model);
if (result instanceof Promise) {
promise.when(result);
} else {
promise.resolve();
}
return promise;
return Promise.resolve(this[action](params, model))
.then(function() {
if (state) {
document.title = state.title;
}
});
},
/**
* Loads a view
Expand All @@ -81,10 +74,10 @@ define(function(require) {
* @param {Function} TView The type of view to load (should derive from [[Lavaca.mvc.View]])
* @param {Object} model The data object to pass to the view
* @param {Number} layer The integer indicating what UI layer the view sits on
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
view: function(cacheKey, TView, model, layer) {
return Promise.when(this, this.viewManager.load(cacheKey, TView, model, layer));
return this.viewManager.load(cacheKey, TView, model, layer);
},
/**
* Adds a state to the browser history
Expand Down Expand Up @@ -119,15 +112,15 @@ define(function(require) {
* @method redirect
*
* @param {String} str The URL string
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*
*/
/**
* Directs the user to another route
* @method redirect
* @param {String} str The URL string
* @param {Array} args Format arguments to insert into the URL
* @return {Lavaca.util.Promise} A promise
* @return {Promise} A promise
*/
redirect: function(str, args) {
return this.router.unlock().exec(this.url(str, args || []));
Expand Down
Loading

0 comments on commit 64ccae6

Please sign in to comment.