From 03050b9b9b86cb4ca317821a38981a233533d230 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Thu, 1 Dec 2016 13:40:31 +0100 Subject: [PATCH 01/79] Update to Marionette 3.1.0 (#109) * Use new addRegion syntax * Add draft upgrade guide --- docs/upgrade_guide.md | 116 ++++++++++++++++++ lib/marionnette_extensions/application.js | 24 ++-- lib/marionnette_extensions/page.js | 2 +- package.json | 6 +- project_template/app/app.coffee | 20 +-- project_template/app/application.coffee | 4 +- .../app/modules/home/home_app.coffee | 19 ++- project_template/package.json | 12 +- .../animatable_region.coffee | 8 +- src/marionnette_extensions/application.coffee | 18 ++- src/marionnette_extensions/page.coffee | 2 +- 11 files changed, 172 insertions(+), 59 deletions(-) create mode 100644 docs/upgrade_guide.md diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md new file mode 100644 index 0000000..46c72c5 --- /dev/null +++ b/docs/upgrade_guide.md @@ -0,0 +1,116 @@ +# Upgrade guide 1.1.0 -> 1.x.x + +In version 1.x.x the Marionette dependency has been updated to 3.1.0. +This means that your app has to update from Marionette 2.4.4 to 3.1.0 + +Marionatte has an [upgrade guide available online](http://marionettejs.com/docs/v3.1.0/upgrade.html). + +The best way to start is to checkout the updated example app. + +1. Update `app/application.coffee` You can probably look up the example app version + +2. Update `app/app.coffee`: + + Change the following: + + ```coffee + app.addInitializer -> + require('./modules/home/home_app').start() + ``` + + to: + + ```coffee + app.on 'before:start', -> + require('./modules/home/home_app') + ``` + + Change the kickoff of the backbone history: + + ```coffee + app.on 'start', (options) -> + Backbone.history.start() + ``` + + to: + + ```coffee + app.on 'before:start', (options) -> + # Bind the start event in after the inclusion of + # the modules, so that the routers are initialized + # before the kick-off of the backbone history + app.on 'start', (options) -> + Backbone.history.start() + ``` + + Update the implementation of the bus functions: + + ```coffee + app.bus.reqres.setHandler 'view:current', -> + app.getView() + + app.bus.reqres.setHandler 'uri:current', -> + Backbone.history.fragment + + app.bus.commands.setHandler 'navigate', (location, options = {}) -> + app.getRegion().navigate(location, options, Backbone.history) + + app.bus.commands.setHandler 'go-back', (where, opts) -> + where = undefined if where == '#' + app.getRegion().goBack(where, opts) + ``` + +3. The best approachs is to disable all modules, and enable/update them one by one, starting with the main module 'app' file: + + Change: + + ```coffee + MyModuleApp = app.module('my_module') + MyModuleApp.startWithParent = false + + class MyModuleApp.Router extends Marionette.AppRouter + appRoutes: + 'my_route/:resource_id' : 'openMyRoute' + + API = + openMyRoute: (resourceId) -> + app.mainRegion.show new EditorPage( + model: model + ) + + MyModuleApp.addInitializer -> + new EditorApp.Router + controller: API + + module.exports = EditorApp + ``` + + To: + + ```coffee + # Notice creation of module is gone; + # New name for router + + class MyModuleRouter extends Marionette.AppRouter + routes: # previously appRoutes + 'my_route/:resource_id' : 'openMyRoute' + + openMyRoute: (resourceId) -> # Method in class now + app.showView new EditorPage( # Use showView + model: model + ) + + app.on 'start', -> + new MyModuleRouter # No passing of controller + + # No Module exports + ``` + +4. Update all your views: + + * everything extending from `Marionette.ItemView` must now extend from `Marionette.View` + * `onShow` is gone. Use `onAttach` instead + * Update use of *Regions*. `@myRegion.show new MySubView` is now `@showChildView('myRegion', new MySubView)` + * `getChildView: ->` is now `childView: ->` + +With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). \ No newline at end of file diff --git a/lib/marionnette_extensions/application.js b/lib/marionnette_extensions/application.js index 4dab5ca..18130f1 100644 --- a/lib/marionnette_extensions/application.js +++ b/lib/marionnette_extensions/application.js @@ -15,24 +15,26 @@ Application = (function(superClass) { extend(Application, superClass); - function Application(opts) { + function Application() { + return Application.__super__.constructor.apply(this, arguments); + } + + Application.prototype.region = '#maji-app'; + + Application.prototype.regionClass = AnimatableRegion; + + Application.prototype.initialize = function(opts) { if (opts == null) { opts = {}; } - Application.__super__.constructor.apply(this, arguments); - _.defaults(opts, { - showTransitions: true - }); require('./marionette_renderer').setup(); require('../cordova_support'); this.bus = bus; - this.addRegions({ - mainRegion: new AnimatableRegion({ - el: '#maji-app', - showTransitions: opts.showTransitions - }) + _.defaults(opts, { + showTransitions: true }); - } + return this.getRegion().showTransitions = opts.showTransitions; + }; return Application; diff --git a/lib/marionnette_extensions/page.js b/lib/marionnette_extensions/page.js index 7397b07..a8b583b 100644 --- a/lib/marionnette_extensions/page.js +++ b/lib/marionnette_extensions/page.js @@ -31,7 +31,7 @@ return Page; - })(Marionette.LayoutView); + })(Marionette.View); module.exports = Page; diff --git a/package.json b/package.json index 4608803..8cc47c1 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,9 @@ "url": "https://github.com/kabisaict/maji" }, "devDependencies": { - "backbone": "^1.2.3", - "backbone.marionette": "^2.4.4", - "backbone.wreqr": "^1.3.5", + "backbone": "1.3.3", + "backbone.marionette": "3.1.0", + "backbone.wreqr": "^1.3.7", "browserify": "~13.0.0", "coffee-script": "1.9.3", "coffeeify": "^1.0.0", diff --git a/project_template/app/app.coffee b/project_template/app/app.coffee index a039012..aabb5fd 100644 --- a/project_template/app/app.coffee +++ b/project_template/app/app.coffee @@ -11,24 +11,28 @@ Maji = require('maji') app = new Maji.Application showTransitions: PageTransitionSupportDetector.supportsTransitions() -app.addInitializer -> - require('./modules/home/home_app').start() +app.on 'before:start', -> + # include modules + require('./modules/home/home_app') -app.on 'start', (options) -> - Backbone.history.start() - Backbone.history.navigate(Backbone.history.fragment) +app.on 'before:start', -> + # Bind the start event in after the inclusion of + # the modules, so that the routers are initialized + # before the kick-off of the backbone history + app.on 'start', (options) -> + Backbone.history.start() app.bus.reqres.setHandler 'view:current', -> - app.mainRegion.currentView + app.getView() app.bus.reqres.setHandler 'uri:current', -> Backbone.history.fragment app.bus.commands.setHandler 'navigate', (location, options = {}) -> - app.mainRegion.navigate(location, options, Backbone.history) + app.getRegion().navigate(location, options, Backbone.history) app.bus.commands.setHandler 'go-back', (where, opts) -> where = undefined if where == '#' - app.mainRegion.goBack(where, opts) + app.getRegion().goBack(where, opts) module.exports = app diff --git a/project_template/app/application.coffee b/project_template/app/application.coffee index cc395cf..c9f5084 100644 --- a/project_template/app/application.coffee +++ b/project_template/app/application.coffee @@ -9,7 +9,7 @@ $ -> # Stretch main container height so it's not resized when the viewport is # resized. This happens on Android when the keyboard pops up. setTimeout -> - window.containerOffsetTop = app.mainRegion.$el.offset().top + window.containerOffsetTop = $(app.getRegion().el).offset().top initialWindowHeight = $(window).height() - window.containerOffsetTop - app.mainRegion.$el.css('height', initialWindowHeight) + $(app.getRegion().el).css('height', initialWindowHeight) , 0 diff --git a/project_template/app/modules/home/home_app.coffee b/project_template/app/modules/home/home_app.coffee index 2ca3e05..8432c61 100644 --- a/project_template/app/modules/home/home_app.coffee +++ b/project_template/app/modules/home/home_app.coffee @@ -1,24 +1,19 @@ app = require('app/app') Marionette = require('backbone.marionette') -HomeApp = app.module('home') -HomeApp.startWithParent = false - -class HomeApp.Router extends Marionette.AppRouter - appRoutes: +class HomeRouter extends Marionette.AppRouter + routes: '' : 'home' 'detail' : 'detail' -API = home: -> IndexPage = require('./views/index_page') - app.mainRegion.show new IndexPage() + app.showView new IndexPage() + detail: -> DetailPage = require('./views/detail_page') - app.mainRegion.show new DetailPage() + app.showView new DetailPage() -HomeApp.addInitializer -> - new HomeApp.Router - controller: API +app.on 'start', -> + new HomeRouter -module.exports = HomeApp diff --git a/project_template/package.json b/project_template/package.json index 126c584..fed84d9 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -1,20 +1,20 @@ - { +{ "name": "##APP_NAME##", "version": "1.0.0", "private": true, "scripts": { - "postinstall": "npm dedupe" + "postinstall": "npm dedupe --loglevel=error" }, "dependencies": { - "backbone": "~1.2.3", + "backbone": "1.3.3", "backbone.localstorage": "~1.1.16", - "backbone.marionette": "~2.4.4", - "backbone.wreqr": "~1.3.5", + "backbone.marionette": "3.1.0", + "backbone.wreqr": "~1.3.7", "fastclick": "~1.0.6", "jquery": "~2.1.3", "underscore": "~1.8.3", "bugsnag-js": "~2.4.9", - "maji": "kabisa/maji" + "maji": "kabisa/maji#update-marionette" }, "devDependencies": { "acorn": "~2.6.4", diff --git a/src/marionnette_extensions/animatable_region.coffee b/src/marionnette_extensions/animatable_region.coffee index 552b497..5017e5e 100644 --- a/src/marionnette_extensions/animatable_region.coffee +++ b/src/marionnette_extensions/animatable_region.coffee @@ -94,13 +94,13 @@ class AnimatableRegion extends Marionette.Region newPage.$el.addClass('page-pre-in') newPage.$el.css('z-index', 10) - this.$el.append(newPage.$el) + @$el.append(newPage.$el) @_log 'Using transition', @transition, 'and back =', @back if @currentPage @currentPage.trigger 'transitionstart' - this.$el.addClass("viewport-transitioning viewport-#{@transition}") + @$el.addClass("viewport-transitioning viewport-#{@transition}") setTimeout((=> window.scrollTo(0,0) @@ -125,8 +125,8 @@ class AnimatableRegion extends Marionette.Region @back = undefined - this.$el.removeClass('viewport-transitioning') - this.$el.removeClass("viewport-#{@transition}") + @$el.removeClass('viewport-transitioning') + @$el.removeClass("viewport-#{@transition}") newPage.trigger 'transitioned' ), @_transitionDuration(@transition)) diff --git a/src/marionnette_extensions/application.coffee b/src/marionnette_extensions/application.coffee index 055dc13..b2d9d33 100644 --- a/src/marionnette_extensions/application.coffee +++ b/src/marionnette_extensions/application.coffee @@ -4,20 +4,16 @@ AnimatableRegion = require('./animatable_region') bus = require('../lib/bus') class Application extends Marionette.Application - constructor: (opts = {}) -> - super - - _.defaults opts, - showTransitions: true + region: '#maji-app' + regionClass: AnimatableRegion + initialize: (opts = {}) -> require('./marionette_renderer').setup() require('../cordova_support') - this.bus = bus + @bus = bus - @addRegions - mainRegion: new AnimatableRegion( - el: '#maji-app', - showTransitions: opts.showTransitions - ) + _.defaults opts, + showTransitions: true + @getRegion().showTransitions = opts.showTransitions module.exports = Application diff --git a/src/marionnette_extensions/page.coffee b/src/marionnette_extensions/page.coffee index cd5d2b1..143ebd3 100644 --- a/src/marionnette_extensions/page.coffee +++ b/src/marionnette_extensions/page.coffee @@ -1,6 +1,6 @@ Marionette = require('backbone.marionette') -class Page extends Marionette.LayoutView +class Page extends Marionette.View transition: 'slide' className: 'page' From 90317a8ec65968048b754352fdaaef942935183c Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 1 Dec 2016 14:43:04 +0100 Subject: [PATCH 02/79] Add preliminary changelog --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..684dc81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## 2.0.0 +### Added +- memo-is for using memoization in specs + +### Changed + +- Updated Marionette to 3.1 +- Updated Backbone to 1.3.3 +- Updated Mocha to 3.1.2 + +### Removed + +- Maji plugin management (`cordova/plugins.txt`) in favor of Cordova plugin management From beda3b5dd6658b3e75946887133376372771c7b8 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 1 Dec 2016 14:29:02 +0100 Subject: [PATCH 03/79] Fix maji branch to 2-0-stable for now --- project_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_template/package.json b/project_template/package.json index fed84d9..3bcb106 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -14,7 +14,7 @@ "jquery": "~2.1.3", "underscore": "~1.8.3", "bugsnag-js": "~2.4.9", - "maji": "kabisa/maji#update-marionette" + "maji": "kabisa/maji#2-0-stable" }, "devDependencies": { "acorn": "~2.6.4", From a5c8e80b3f9e488537f2c1eb554af3ccb028a499 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 1 Dec 2016 14:04:31 +0100 Subject: [PATCH 04/79] Don't `npm dedupe` on build `npm dedupe` in general is not really needed anymore with npm 3 as the dependency, tree is already mostly flat, and deduped by default. This also removes many warnings during build for dependencies that could not be deduped. --- project_template/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/project_template/package.json b/project_template/package.json index fed84d9..7d90ec9 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -2,9 +2,6 @@ "name": "##APP_NAME##", "version": "1.0.0", "private": true, - "scripts": { - "postinstall": "npm dedupe --loglevel=error" - }, "dependencies": { "backbone": "1.3.3", "backbone.localstorage": "~1.1.16", From 599fa383f831eef3bfc4701df7bafd4f2d958b98 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Mon, 24 Oct 2016 20:15:57 +0200 Subject: [PATCH 05/79] Update dependencies - Updated Backbone to 1.3.3 - Updated Mocha to 3.1.2 - Added When.js to support promises - Added Memo-is to support 'let' in tests --- project_template/package.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/project_template/package.json b/project_template/package.json index 24c0ae6..b8f8395 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -39,7 +39,7 @@ "karma-sinon-chai": "~1.2.0", "karma-phantomjs-launcher": "~1.0.0", "lolex": "^1.4.0", - "mocha": "~2.4.5", + "mocha": "~3.1.2", "node-sass": "~3.4.2", "onchange": "~2.1.2", "phantomjs-prebuilt": "~2.1.3", @@ -48,11 +48,13 @@ "uglifyify": "~3.0.1", "vinyl-fs": "~2.4.2", "watchify": "~3.7.0", - "yamlify": "~0.1.2" + "yamlify": "~0.1.2", + "memo-is": "0.0.2" }, "aliasify": { "aliases": { - "app": "./app" + "app": "./app", + "when": "when/dist/browser/when.min" } } } From 3054ad7494e4ce2e922dc187363344102cb3df39 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Mon, 24 Oct 2016 20:21:48 +0200 Subject: [PATCH 06/79] Setup extra test helpers --- project_template/spec/spec_helper.coffee | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/project_template/spec/spec_helper.coffee b/project_template/spec/spec_helper.coffee index e242af8..6827804 100644 --- a/project_template/spec/spec_helper.coffee +++ b/project_template/spec/spec_helper.coffee @@ -1,3 +1,29 @@ Backbone = require('backbone') # Backbone.$ is not automatically set when backbone is loaded as CommonJS module $ = Backbone.$ = require('jquery') + +# Allow to use memo-isation in tests +# +# describe 'some feature', -> +# +# data = memo().is -> { foo: 'bar' } +# +# it 'shows bar', -> +# expect(data().foo).to.eql 'bar' +# +# describe 'when undefined', -> +# data.is -> { foo: undefined } +# +# it 'shows undefined', -> +# expect(data().foo).to.be.undefined +# +window.memo = require('memo-is') + +When = require('when') +window.wait = (delay = 1) -> + d = When.defer() + setTimeout( + -> d.resolve() + delay + ) + d.promise From bb7229ccade18d5d40c5a8cf10663dc6998f2aa8 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Thu, 27 Oct 2016 08:34:24 +0200 Subject: [PATCH 07/79] Replace wait with waitFor --- project_template/spec/spec_helper.coffee | 25 +++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/project_template/spec/spec_helper.coffee b/project_template/spec/spec_helper.coffee index 6827804..0bf6b5a 100644 --- a/project_template/spec/spec_helper.coffee +++ b/project_template/spec/spec_helper.coffee @@ -20,10 +20,25 @@ $ = Backbone.$ = require('jquery') window.memo = require('memo-is') When = require('when') -window.wait = (delay = 1) -> + +# Use wait for to wait for data to come in, +# DOM elements to exist, or spies +# to have been called +# +# waitFor(=> @collection.last()).then (lastMessage) -> # assertions +# waitFor(=> @spy.called).then -> expect(spy).to.have.been.calledWith('args') +# waitFor(=> @view.$('element').length > 0).then -> # assertions +# +window.waitFor = (test) -> d = When.defer() - setTimeout( - -> d.resolve() - delay + i = setInterval( + -> + if (result = test()) + clearInterval i + d.resolve(result) + 2 ) - d.promise + d.promise.timeout(1500, new Error('waitFor never resolved')) + .catch (e) -> + clearInterval i + throw e From 56a4f639c119df3538a8819e174f5d789e95b3c7 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Thu, 1 Dec 2016 16:39:35 +0100 Subject: [PATCH 08/79] Replace When with new jQuery --- project_template/package.json | 5 ++--- project_template/spec/spec_helper.coffee | 14 ++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/project_template/package.json b/project_template/package.json index b8f8395..deb7513 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -8,7 +8,7 @@ "backbone.marionette": "3.1.0", "backbone.wreqr": "~1.3.7", "fastclick": "~1.0.6", - "jquery": "~2.1.3", + "jquery": "~3.0.0", "underscore": "~1.8.3", "bugsnag-js": "~2.4.9", "maji": "kabisa/maji#2-0-stable" @@ -53,8 +53,7 @@ }, "aliasify": { "aliases": { - "app": "./app", - "when": "when/dist/browser/when.min" + "app": "./app" } } } diff --git a/project_template/spec/spec_helper.coffee b/project_template/spec/spec_helper.coffee index 0bf6b5a..e6250ff 100644 --- a/project_template/spec/spec_helper.coffee +++ b/project_template/spec/spec_helper.coffee @@ -19,8 +19,6 @@ $ = Backbone.$ = require('jquery') # window.memo = require('memo-is') -When = require('when') - # Use wait for to wait for data to come in, # DOM elements to exist, or spies # to have been called @@ -30,15 +28,19 @@ When = require('when') # waitFor(=> @view.$('element').length > 0).then -> # assertions # window.waitFor = (test) -> - d = When.defer() + d = $.Deferred() + timeout = null i = setInterval( -> if (result = test()) clearInterval i + clearTimeout timeout d.resolve(result) 2 ) - d.promise.timeout(1500, new Error('waitFor never resolved')) - .catch (e) -> + timeout = setTimeout(1500, + -> clearInterval i - throw e + d.reject(new Error('waitFor never resolved')) + ) + d.promise() From 95dafd4193edfc5abb2514d4ac31f7cc791b5bb0 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Thu, 1 Dec 2016 16:46:11 +0100 Subject: [PATCH 09/79] Update waitFor --- project_template/spec/spec_helper.coffee | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/project_template/spec/spec_helper.coffee b/project_template/spec/spec_helper.coffee index e6250ff..f7e0860 100644 --- a/project_template/spec/spec_helper.coffee +++ b/project_template/spec/spec_helper.coffee @@ -32,15 +32,16 @@ window.waitFor = (test) -> timeout = null i = setInterval( -> - if (result = test()) - clearInterval i - clearTimeout timeout - d.resolve(result) + return unless (result = test()) + clearInterval i + clearTimeout timeout + d.resolve(result) 2 ) - timeout = setTimeout(1500, + timeout = setTimeout( -> clearInterval i d.reject(new Error('waitFor never resolved')) + 1500 ) d.promise() From c0fc5906d2692f44e0becaf65c79e6bcc5af3882 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Thu, 1 Dec 2016 16:48:00 +0100 Subject: [PATCH 10/79] Add jQuery to the changed list --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 684dc81..76291ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) +The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## 2.0.0 @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Updated Marionette to 3.1 - Updated Backbone to 1.3.3 - Updated Mocha to 3.1.2 +- Updated jQuery to 3.0.0 ### Removed From b60eb02380d40844028b4d7875dcf484e1ebcb43 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Thu, 1 Dec 2016 20:14:18 +0100 Subject: [PATCH 11/79] Rename marionnette folder. Fixes #98 --- lib/index.js | 6 +++--- .../animatable_region.js | 0 .../application.js | 0 .../marionette_renderer.js | 0 .../page.js | 0 lib/marionette_renderer.js | 2 +- .../animatable_region_spec.coffee | 4 ++-- src/index.coffee | 6 +++--- .../animatable_region.coffee | 0 .../application.coffee | 0 .../marionette_renderer.coffee | 0 .../page.coffee | 0 src/marionette_renderer.coffee | 2 +- 13 files changed, 10 insertions(+), 10 deletions(-) rename lib/{marionnette_extensions => marionette_extensions}/animatable_region.js (100%) rename lib/{marionnette_extensions => marionette_extensions}/application.js (100%) rename lib/{marionnette_extensions => marionette_extensions}/marionette_renderer.js (100%) rename lib/{marionnette_extensions => marionette_extensions}/page.js (100%) rename spec/{marionnette_extensions => marionette_extensions}/animatable_region_spec.coffee (85%) rename src/{marionnette_extensions => marionette_extensions}/animatable_region.coffee (100%) rename src/{marionnette_extensions => marionette_extensions}/application.coffee (100%) rename src/{marionnette_extensions => marionette_extensions}/marionette_renderer.coffee (100%) rename src/{marionnette_extensions => marionette_extensions}/page.coffee (100%) diff --git a/lib/index.js b/lib/index.js index 19a9d91..b73048c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -8,9 +8,9 @@ bus: require('./lib/bus'), I18n: require('./lib/i18n'), TemplateHelpers: require('./lib/template_helpers'), - Application: require('./marionnette_extensions/application'), - AnimatableRegion: require('./marionnette_extensions/animatable_region'), - Page: require('./marionnette_extensions/page'), + Application: require('./marionette_extensions/application'), + AnimatableRegion: require('./marionette_extensions/animatable_region'), + Page: require('./marionette_extensions/page'), ApplicationState: require('./storage/application_state'), Cache: require('./storage/cache'), JsonSetting: require('./storage/json_setting') diff --git a/lib/marionnette_extensions/animatable_region.js b/lib/marionette_extensions/animatable_region.js similarity index 100% rename from lib/marionnette_extensions/animatable_region.js rename to lib/marionette_extensions/animatable_region.js diff --git a/lib/marionnette_extensions/application.js b/lib/marionette_extensions/application.js similarity index 100% rename from lib/marionnette_extensions/application.js rename to lib/marionette_extensions/application.js diff --git a/lib/marionnette_extensions/marionette_renderer.js b/lib/marionette_extensions/marionette_renderer.js similarity index 100% rename from lib/marionnette_extensions/marionette_renderer.js rename to lib/marionette_extensions/marionette_renderer.js diff --git a/lib/marionnette_extensions/page.js b/lib/marionette_extensions/page.js similarity index 100% rename from lib/marionnette_extensions/page.js rename to lib/marionette_extensions/page.js diff --git a/lib/marionette_renderer.js b/lib/marionette_renderer.js index 187a316..5920bb5 100644 --- a/lib/marionette_renderer.js +++ b/lib/marionette_renderer.js @@ -1,5 +1,5 @@ // Generated by CoffeeScript 1.9.3 (function() { - module.exports = require('./marionnette_extensions/marionette_renderer'); + module.exports = require('./marionette_extensions/marionette_renderer'); }).call(this); diff --git a/spec/marionnette_extensions/animatable_region_spec.coffee b/spec/marionette_extensions/animatable_region_spec.coffee similarity index 85% rename from spec/marionnette_extensions/animatable_region_spec.coffee rename to spec/marionette_extensions/animatable_region_spec.coffee index 79f687d..5133c1b 100644 --- a/spec/marionnette_extensions/animatable_region_spec.coffee +++ b/spec/marionette_extensions/animatable_region_spec.coffee @@ -1,5 +1,5 @@ -AnimatableRegion = require '../../src/marionnette_extensions/animatable_region' -Page = require '../../src/marionnette_extensions/page' +AnimatableRegion = require '../../src/marionette_extensions/animatable_region' +Page = require '../../src/marionette_extensions/page' class TestPage extends Page render: -> diff --git a/src/index.coffee b/src/index.coffee index af0a21f..b05f6da 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -8,9 +8,9 @@ module.exports = I18n : require('./lib/i18n') TemplateHelpers : require('./lib/template_helpers') - Application : require('./marionnette_extensions/application') - AnimatableRegion : require('./marionnette_extensions/animatable_region') - Page : require('./marionnette_extensions/page') + Application : require('./marionette_extensions/application') + AnimatableRegion : require('./marionette_extensions/animatable_region') + Page : require('./marionette_extensions/page') ApplicationState : require('./storage/application_state') Cache : require('./storage/cache') diff --git a/src/marionnette_extensions/animatable_region.coffee b/src/marionette_extensions/animatable_region.coffee similarity index 100% rename from src/marionnette_extensions/animatable_region.coffee rename to src/marionette_extensions/animatable_region.coffee diff --git a/src/marionnette_extensions/application.coffee b/src/marionette_extensions/application.coffee similarity index 100% rename from src/marionnette_extensions/application.coffee rename to src/marionette_extensions/application.coffee diff --git a/src/marionnette_extensions/marionette_renderer.coffee b/src/marionette_extensions/marionette_renderer.coffee similarity index 100% rename from src/marionnette_extensions/marionette_renderer.coffee rename to src/marionette_extensions/marionette_renderer.coffee diff --git a/src/marionnette_extensions/page.coffee b/src/marionette_extensions/page.coffee similarity index 100% rename from src/marionnette_extensions/page.coffee rename to src/marionette_extensions/page.coffee diff --git a/src/marionette_renderer.coffee b/src/marionette_renderer.coffee index ce4aa7f..4c01f4b 100644 --- a/src/marionette_renderer.coffee +++ b/src/marionette_renderer.coffee @@ -1,2 +1,2 @@ # for backwards compatibility -module.exports = require('./marionnette_extensions/marionette_renderer') +module.exports = require('./marionette_extensions/marionette_renderer') From 372d660c829bb9666d96a94e1aad31db608443dc Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 5 Dec 2016 12:51:16 +0100 Subject: [PATCH 12/79] Use system font in favor of Lato * It's easy enough to include a custom font in your project if need be * Makes the app feel more native * Saves a bit on initial load time and binary size --- project_template/app/styles/application.scss | 4 +- project_template/app/styles/fonts/lato.scss | 35 ------------------ .../public/assets/fonts/lato-black.ttf | Bin 39048 -> 0 bytes .../public/assets/fonts/lato-bold.ttf | Bin 39164 -> 0 bytes .../public/assets/fonts/lato-italic.ttf | Bin 39696 -> 0 bytes .../public/assets/fonts/lato-regular.ttf | Bin 38588 -> 0 bytes 6 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 project_template/app/styles/fonts/lato.scss delete mode 100644 project_template/public/assets/fonts/lato-black.ttf delete mode 100644 project_template/public/assets/fonts/lato-bold.ttf delete mode 100644 project_template/public/assets/fonts/lato-italic.ttf delete mode 100644 project_template/public/assets/fonts/lato-regular.ttf diff --git a/project_template/app/styles/application.scss b/project_template/app/styles/application.scss index a59f7be..8a6c012 100644 --- a/project_template/app/styles/application.scss +++ b/project_template/app/styles/application.scss @@ -1,6 +1,5 @@ @import 'normalize'; @import 'maji/screen_transitions'; -@import 'fonts/lato'; @import 'fonts/icons'; @import 'variables'; @import 'forms'; @@ -11,7 +10,8 @@ html, body { margin: 0; padding: 0; - font-family: 'Lato'; + // use native font stack + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif; text-size-adjust: 100%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); user-select: none; diff --git a/project_template/app/styles/fonts/lato.scss b/project_template/app/styles/fonts/lato.scss deleted file mode 100644 index 7e4e21b..0000000 --- a/project_template/app/styles/fonts/lato.scss +++ /dev/null @@ -1,35 +0,0 @@ -// -// Font files can be found in /public/assets/fonts -// -@font-face { - font-family: 'Lato'; - font-style: normal; - font-weight: 400; - src: local('Lato Regular'), - local('Lato-Regular'), - url('fonts/lato-regular.ttf') format('truetype'); -} -@font-face { - font-family: 'Lato'; - font-style: normal; - font-weight: 700; - src: local('Lato Bold'), - local('Lato-Bold'), - url('fonts/lato-bold.ttf') format('truetype'); -} -@font-face { - font-family: 'Lato'; - font-style: normal; - font-weight: 900; - src: local('Lato Black'), - local('Lato-Black'), - url('fonts/lato-black.ttf') format('truetype'); -} -@font-face { - font-family: 'Lato'; - font-style: italic; - font-weight: 400; - src: local('Lato Italic'), - local('Lato-Italic'), - url('fonts/lato-italic.ttf') format('truetype'); -} diff --git a/project_template/public/assets/fonts/lato-black.ttf b/project_template/public/assets/fonts/lato-black.ttf deleted file mode 100644 index 543bc8bcac27f44dee555047abb1b42e41c76907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39048 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 diff --git a/project_template/public/assets/fonts/lato-italic.ttf b/project_template/public/assets/fonts/lato-italic.ttf deleted file mode 100644 index e2027fffccf8e7ec7c10864c3cf64de0fa62a111..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39696 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 qa-HDG#z~%&oZ=bDf2cFcoo`i#w?ue$P2BDmiq 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 VW4)*1g5YmnSk#bC|G8lOe*ug;7|8$t From af8ffa5734946bb8b1dd0f1bf1bcc9bcfddac8fe Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Mon, 5 Dec 2016 17:01:08 +0100 Subject: [PATCH 13/79] Replace bus with radio (#114) * Replace bus with radio * Use reference links for cleaner document * Reference to own branch for testing * Notify of removal of Maji application bus --- CHANGELOG.md | 1 + docs/upgrade_guide.md | 46 +++++++++++-------- lib/cordova_support.js | 10 ++-- lib/index.js | 1 - lib/lib/bus.js | 28 ----------- lib/marionette_extensions/application.js | 5 +- package.json | 2 +- project_template/app/app.coffee | 22 ++++----- .../app/modules/home/views/index_page.coffee | 1 - .../app/views/application_page.coffee | 6 +-- project_template/package.json | 4 +- src/cordova_support.coffee | 8 ++-- src/index.coffee | 1 - src/lib/bus.coffee | 13 ------ src/marionette_extensions/application.coffee | 2 - 15 files changed, 52 insertions(+), 98 deletions(-) delete mode 100644 lib/lib/bus.js delete mode 100644 src/lib/bus.coffee diff --git a/CHANGELOG.md b/CHANGELOG.md index 684dc81..281f168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,3 +17,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed - Maji plugin management (`cordova/plugins.txt`) in favor of Cordova plugin management +- Maji application bus in favor of Backbone.Radio diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index 46c72c5..8d5194e 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -1,9 +1,11 @@ -# Upgrade guide 1.1.0 -> 1.x.x +# Upgrade guide 1.1.0 -> 2.0.0 -In version 1.x.x the Marionette dependency has been updated to 3.1.0. -This means that your app has to update from Marionette 2.4.4 to 3.1.0 +In Maji 2.0.0 the Marionette dependency has been updated to 3.1.0. -Marionatte has an [upgrade guide available online](http://marionettejs.com/docs/v3.1.0/upgrade.html). +This means that your app has to update from Marionette 2.4.4 to 3.1.0. +The `Maji.bus` (using `backbone.wreqr`) is also removed in favor of using `backbone.radio`. + +Marionette has an [upgrade guide available online][marionette-upgrade], to find pointers not covered in this guide. The best way to start is to checkout the updated example app. @@ -11,6 +13,12 @@ The best way to start is to checkout the updated example app. 2. Update `app/app.coffee`: + Add in the top of the file: + + ```coffee + Radio = require('backbone.radio') + ``` + Change the following: ```coffee @@ -43,21 +51,18 @@ The best way to start is to checkout the updated example app. Backbone.history.start() ``` - Update the implementation of the bus functions: + Update the implementation of the bus functions to: ```coffee - app.bus.reqres.setHandler 'view:current', -> - app.getView() - - app.bus.reqres.setHandler 'uri:current', -> - Backbone.history.fragment - - app.bus.commands.setHandler 'navigate', (location, options = {}) -> - app.getRegion().navigate(location, options, Backbone.history) - - app.bus.commands.setHandler 'go-back', (where, opts) -> - where = undefined if where == '#' - app.getRegion().goBack(where, opts) + Radio.channel('app').reply( + 'view:current': -> app.getView() + 'uri:current': -> Backbone.history.fragment + 'navigate': (location, options = {}) -> + app.getRegion().navigate(location, options, Backbone.history) + 'go-back': (where, opts = {}) -> + where = undefined if where == '#' + app.getRegion().goBack(where, opts) + ) ``` 3. The best approachs is to disable all modules, and enable/update them one by one, starting with the main module 'app' file: @@ -113,4 +118,9 @@ The best way to start is to checkout the updated example app. * Update use of *Regions*. `@myRegion.show new MySubView` is now `@showChildView('myRegion', new MySubView)` * `getChildView: ->` is now `childView: ->` -With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). \ No newline at end of file +With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). + +5. Update all uses of the `Maji.bus` to Backbone.Radio ([see documentation of radio here][backbone-radio]) + +[marionette-upgrade]: http://marionettejs.com/docs/v3.1.0/upgrade.html +[backbone-radio]: https://github.com/marionettejs/backbone.radio \ No newline at end of file diff --git a/lib/cordova_support.js b/lib/cordova_support.js index fc9c6d5..37b720b 100644 --- a/lib/cordova_support.js +++ b/lib/cordova_support.js @@ -1,14 +1,10 @@ // Generated by CoffeeScript 1.9.3 (function() { - var $, bus, initCordova, publishOnBus; + var $, Radio, initCordova; $ = require('jquery'); - bus = require('./lib/bus'); - - publishOnBus = function(e) { - return bus.trigger("app:" + e.type); - }; + Radio = require('backbone.radio'); initCordova = function() { var eventName, i, len, ref; @@ -19,7 +15,7 @@ for (i = 0, len = ref.length; i < len; i++) { eventName = ref[i]; $(document).on(eventName, function(e) { - return publishOnBus(e); + return Radio.channel('app').trigger(e.type); }); } return $(function() { diff --git a/lib/index.js b/lib/index.js index b73048c..8cfa5e9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,7 +5,6 @@ UnrecoverableError: require('./errors/base_error').UnrecoverableError, ServiceError: require('./errors/service_error'), ServiceUnavailableError: require('./errors/service_unavailable_error'), - bus: require('./lib/bus'), I18n: require('./lib/i18n'), TemplateHelpers: require('./lib/template_helpers'), Application: require('./marionette_extensions/application'), diff --git a/lib/lib/bus.js b/lib/lib/bus.js deleted file mode 100644 index 4b2cdad..0000000 --- a/lib/lib/bus.js +++ /dev/null @@ -1,28 +0,0 @@ -// Generated by CoffeeScript 1.9.3 -(function() { - var Wreqr, commands, reqres, vent; - - Wreqr = require('backbone.wreqr'); - - commands = new Wreqr.Commands(); - - vent = new Wreqr.EventAggregator(); - - reqres = new Wreqr.RequestResponse(); - - module.exports = { - commands: commands, - vent: vent, - reqres: reqres, - execute: function() { - return commands.execute.apply(commands, arguments); - }, - request: function() { - return reqres.request.apply(reqres, arguments); - }, - trigger: function() { - return vent.trigger.apply(vent, arguments); - } - }; - -}).call(this); diff --git a/lib/marionette_extensions/application.js b/lib/marionette_extensions/application.js index 18130f1..d3ed131 100644 --- a/lib/marionette_extensions/application.js +++ b/lib/marionette_extensions/application.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.9.3 (function() { - var AnimatableRegion, Application, Marionette, _, bus, + var AnimatableRegion, Application, Marionette, _, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; @@ -10,8 +10,6 @@ AnimatableRegion = require('./animatable_region'); - bus = require('../lib/bus'); - Application = (function(superClass) { extend(Application, superClass); @@ -29,7 +27,6 @@ } require('./marionette_renderer').setup(); require('../cordova_support'); - this.bus = bus; _.defaults(opts, { showTransitions: true }); diff --git a/package.json b/package.json index 8cc47c1..1a2ff68 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "devDependencies": { "backbone": "1.3.3", "backbone.marionette": "3.1.0", - "backbone.wreqr": "^1.3.7", + "backbone.radio": "^2.0.0", "browserify": "~13.0.0", "coffee-script": "1.9.3", "coffeeify": "^1.0.0", diff --git a/project_template/app/app.coffee b/project_template/app/app.coffee index aabb5fd..a84d6ef 100644 --- a/project_template/app/app.coffee +++ b/project_template/app/app.coffee @@ -5,6 +5,7 @@ require('./config/template_helpers') Backbone = require('backbone') Marionette = require('backbone.marionette') +Radio = require('backbone.radio') PageTransitionSupportDetector = require('./support/page_transition_support_detector') Maji = require('maji') @@ -22,17 +23,14 @@ app.on 'before:start', -> app.on 'start', (options) -> Backbone.history.start() -app.bus.reqres.setHandler 'view:current', -> - app.getView() - -app.bus.reqres.setHandler 'uri:current', -> - Backbone.history.fragment - -app.bus.commands.setHandler 'navigate', (location, options = {}) -> - app.getRegion().navigate(location, options, Backbone.history) - -app.bus.commands.setHandler 'go-back', (where, opts) -> - where = undefined if where == '#' - app.getRegion().goBack(where, opts) +Radio.channel('app').reply( + 'view:current': -> app.getView() + 'uri:current': -> Backbone.history.fragment + 'navigate': (location, options = {}) -> + app.getRegion().navigate(location, options, Backbone.history) + 'go-back': (where, opts) -> + where = undefined if where == '#' + app.getRegion().goBack(where, opts) +) module.exports = app diff --git a/project_template/app/modules/home/views/index_page.coffee b/project_template/app/modules/home/views/index_page.coffee index 4c43d12..bd449e6 100644 --- a/project_template/app/modules/home/views/index_page.coffee +++ b/project_template/app/modules/home/views/index_page.coffee @@ -1,7 +1,6 @@ ApplicationPage = require('app/views/application_page') template = require('../templates/index') $ = require('jquery') -bus = require('maji').bus class IndexPage extends ApplicationPage template: template diff --git a/project_template/app/views/application_page.coffee b/project_template/app/views/application_page.coffee index d667d09..48d5b62 100644 --- a/project_template/app/views/application_page.coffee +++ b/project_template/app/views/application_page.coffee @@ -1,5 +1,5 @@ Maji = require('maji') -bus = require('../app').bus +Radio = require('backbone.radio') class ApplicationPage extends Maji.Page events: @@ -8,7 +8,7 @@ class ApplicationPage extends Maji.Page constructor: -> super - @listenTo bus.vent, 'app:backbutton', @onBackButton + @listenTo Radio.channel('app'), 'backbutton', @onBackButton goHome: (e) -> e && e.preventDefault() @@ -27,6 +27,6 @@ class ApplicationPage extends Maji.Page false navigate: (href, options = {}) -> - bus.commands.execute 'navigate', href, options + Radio.channel('app').request('navigate', href, options) module.exports = ApplicationPage diff --git a/project_template/package.json b/project_template/package.json index 24c0ae6..df3b3a5 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -6,12 +6,12 @@ "backbone": "1.3.3", "backbone.localstorage": "~1.1.16", "backbone.marionette": "3.1.0", - "backbone.wreqr": "~1.3.7", + "backbone.radio": "^2.0.0", "fastclick": "~1.0.6", "jquery": "~2.1.3", "underscore": "~1.8.3", "bugsnag-js": "~2.4.9", - "maji": "kabisa/maji#2-0-stable" + "maji": "kabisa/maji#replace-bus-with-radio" }, "devDependencies": { "acorn": "~2.6.4", diff --git a/src/cordova_support.coffee b/src/cordova_support.coffee index 497b5d3..21d8668 100644 --- a/src/cordova_support.coffee +++ b/src/cordova_support.coffee @@ -1,8 +1,6 @@ -$ = require('jquery') -bus = require('./lib/bus') +$ = require('jquery') +Radio = require('backbone.radio') -publishOnBus = (e) -> - bus.trigger("app:#{e.type}") initCordova = -> $(document).on 'deviceready', -> @@ -10,7 +8,7 @@ initCordova = -> for eventName in ['pause', 'resume', 'backbutton', 'offline', 'online'] $(document).on eventName, (e) -> - publishOnBus(e) + Radio.channel('app').trigger(e.type) $ -> $('body').addClass("platform-#{cordova.platformId}") diff --git a/src/index.coffee b/src/index.coffee index b05f6da..ad2471f 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -4,7 +4,6 @@ module.exports = ServiceError : require('./errors/service_error') ServiceUnavailableError : require('./errors/service_unavailable_error') - bus : require('./lib/bus') I18n : require('./lib/i18n') TemplateHelpers : require('./lib/template_helpers') diff --git a/src/lib/bus.coffee b/src/lib/bus.coffee deleted file mode 100644 index 463b743..0000000 --- a/src/lib/bus.coffee +++ /dev/null @@ -1,13 +0,0 @@ -Wreqr = require('backbone.wreqr') - -commands = new Wreqr.Commands() -vent = new Wreqr.EventAggregator() -reqres = new Wreqr.RequestResponse() - -module.exports = - commands : commands - vent : vent - reqres : reqres - execute : -> commands.execute.apply(commands, arguments) - request : -> reqres.request.apply(reqres, arguments) - trigger : -> vent.trigger.apply(vent, arguments) diff --git a/src/marionette_extensions/application.coffee b/src/marionette_extensions/application.coffee index b2d9d33..daf2eb6 100644 --- a/src/marionette_extensions/application.coffee +++ b/src/marionette_extensions/application.coffee @@ -1,7 +1,6 @@ _ = require('underscore') Marionette = require('backbone.marionette') AnimatableRegion = require('./animatable_region') -bus = require('../lib/bus') class Application extends Marionette.Application region: '#maji-app' @@ -10,7 +9,6 @@ class Application extends Marionette.Application initialize: (opts = {}) -> require('./marionette_renderer').setup() require('../cordova_support') - @bus = bus _.defaults opts, showTransitions: true From 1d99ec42b744526f19e32b6c59e657529064fa1b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 1 Dec 2016 13:50:47 +0100 Subject: [PATCH 14/79] Remove Maji plugin management in favor of Cordova plugin management --- project_template/cordova/config.xml | 3 +++ project_template/cordova/plugins.txt | 3 --- script/_functions | 3 ++- script/prepare-cordova-platform | 23 +++++++++-------------- 4 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 project_template/cordova/plugins.txt diff --git a/project_template/cordova/config.xml b/project_template/cordova/config.xml index f366b34..6fd8b1a 100644 --- a/project_template/cordova/config.xml +++ b/project_template/cordova/config.xml @@ -22,4 +22,7 @@ + + + diff --git a/project_template/cordova/plugins.txt b/project_template/cordova/plugins.txt deleted file mode 100644 index 4270a10..0000000 --- a/project_template/cordova/plugins.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Add your plugins here, one per line -org.apache.cordova.splashscreen -com.mallzee.plugin.networkactivity https://github.com/mallzee/network-activity.git#0904b83c3109560bad8e7dd9be1320bb9303899d diff --git a/script/_functions b/script/_functions index c5b29fd..7920a69 100755 --- a/script/_functions +++ b/script/_functions @@ -17,7 +17,8 @@ function brew_available() { } function warning() { - echo "$(tput setaf 3)$@$(tput sgr0)" + [[ $# -eq 0 ]] && local message=$(cat -) || local message="$@" + echo "$(tput setaf 3)$message$(tput sgr0)" } function npm_package_available() { diff --git a/script/prepare-cordova-platform b/script/prepare-cordova-platform index d5fc2bd..129ba00 100755 --- a/script/prepare-cordova-platform +++ b/script/prepare-cordova-platform @@ -6,22 +6,17 @@ pushd cordova >/dev/null 2>&1 platform_installed $PLATFORM || cordova platforms add $PLATFORM cordova prepare $PLATFORM - # install cordova plugins if [ -f plugins.txt ]; then - while read PLUGIN_LINE; do - [[ "$PLUGIN_LINE" == \#* ]] && continue + warning </dev/null 2>&1 || cordova plugins add $PLUGIN_FETCH_TARGET - done < plugins.txt +Refer to the Cordova documentation for details: +https://cordova.apache.org/docs/en/latest/platform_plugin_versioning_ref/ +EOF + exit 1 fi popd >/dev/null 2>&1 From 4be4a318e32389dc11509b42191d2be4bac9902d Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 5 Dec 2016 20:46:30 +0100 Subject: [PATCH 15/79] Add section about Cordova plugin management to upgrade guide --- docs/upgrade_guide.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index 8d5194e..d7fb32f 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -118,8 +118,16 @@ The best way to start is to checkout the updated example app. * Update use of *Regions*. `@myRegion.show new MySubView` is now `@showChildView('myRegion', new MySubView)` * `getChildView: ->` is now `childView: ->` -With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). +5. Maji 2.x uses Cordova plugin management + +This means that the Cordova plugins you wish to use should now be defined in `cordova/config.xml`, instead of `cordova/plugins.txt`. + +Refer to the Cordova documentation for details: https://cordova.apache.org/docs/en/latest/platform_plugin_versioning_ref/ + + + +With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). 5. Update all uses of the `Maji.bus` to Backbone.Radio ([see documentation of radio here][backbone-radio]) [marionette-upgrade]: http://marionettejs.com/docs/v3.1.0/upgrade.html From ff36d3ba48caa0d7f6a0fd47bc68b5b38610630c Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 5 Dec 2016 20:54:05 +0100 Subject: [PATCH 16/79] Fix maji npm package back to 2-0-stable branch --- project_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_template/package.json b/project_template/package.json index df3b3a5..d7f74c2 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -11,7 +11,7 @@ "jquery": "~2.1.3", "underscore": "~1.8.3", "bugsnag-js": "~2.4.9", - "maji": "kabisa/maji#replace-bus-with-radio" + "maji": "kabisa/maji#2-0-stable" }, "devDependencies": { "acorn": "~2.6.4", From 4da6d071d027970b38119a6ccc7db3082f714cf8 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 7 Dec 2016 14:47:09 +0100 Subject: [PATCH 17/79] Use phantomjs retrieved from NPM. Fixes #92 We already installed phantomjs via NPM, as well as via Homebrew if that was available. Karma would already use the phantomjs binary provided by NPM whereas Poltergeist would use the one in $PATH. This commit cleans up that mess and ensures that only a single phantomjs binary is installed, regardless of platform (one less dependency on Homebrew) :-) --- README.md | 5 ++--- dockerfiles/ci/Dockerfile | 7 ------- project_template/bin/setup | 4 ---- project_template/dockerfiles/ci/Dockerfile | 7 ------- project_template/spec/spec_helper.rb | 5 ++++- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 901481a..bc316b1 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,7 @@ Before you can use Maji, make sure you have the following: ### Linux * [Nodejs](http://nodejs.org) allows JavaScript to be run Server-side -* [Apache Ant](http://ant.apache.org) is used for building Java Applications -* [PhantomJS](http://phantomjs.org/download.html) is a headless WebKit Browser, scriptable with a JavaScript API +* [Apache Ant](http://ant.apache.org) is used for building Android Applications ## Getting started @@ -69,7 +68,7 @@ To start your app, `cd` into its directory, execute `make watch` and navigate to ### Running tests To run test, you have several options: -* To run JavaScript tests run `bin/karma start`. This will start a Karma server with Phantomjs and will continuously watch your Javascript files and run tests on changes. +* To run JavaScript tests run `bin/karma start`. This will start a Karma server with Phantomjs headless browser and will continuously watch your Javascript files and run tests on changes. * To run JavaScript tests once, run `bin/karma start --single-run`. * To run features specs once, run `bundle exec rspec`. * To run all tests once, run `bin/ci`. diff --git a/dockerfiles/ci/Dockerfile b/dockerfiles/ci/Dockerfile index 55a89b1..75c8089 100644 --- a/dockerfiles/ci/Dockerfile +++ b/dockerfiles/ci/Dockerfile @@ -17,12 +17,6 @@ RUN apt-get update \ RUN curl -sL https://deb.nodesource.com/setup_5.x | bash - \ && apt-get install --yes nodejs -# Install PhantomJS 2.1.1 -RUN cd /tmp && curl -L https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -o phantomjs-2.1.1-linux-x86_64.tar.bz2 \ - && tar xjf phantomjs-2.1.1-linux-x86_64.tar.bz2 \ - && mv /tmp/phantomjs-*/bin/phantomjs /usr/bin/ \ - && rm -r /tmp/phantomjs* - ENV CONTAINER_INIT /usr/local/bin/init-container RUN echo '#!/usr/bin/env bash' > $CONTAINER_INIT ; chmod +x $CONTAINER_INIT @@ -37,5 +31,4 @@ RUN echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen ENV LC_ALL en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US.UTF-8 -ENV PHANTOMJS_BIN /usr/bin/phantomjs ENV BUNDLE_PATH /cache/bundle diff --git a/project_template/bin/setup b/project_template/bin/setup index 56ff5d6..60dc0bb 100755 --- a/project_template/bin/setup +++ b/project_template/bin/setup @@ -7,10 +7,6 @@ if brew_available ; then brew_dep_installed 'node' || brew install node brew_dep_installed 'ant' || brew install ant - if ! command_available 'phantomjs' ; then - brew install phantomjs - fi - # npm is included with node in newer version, so this normally shouldn't be needed. command_available 'npm' || brew install npm else diff --git a/project_template/dockerfiles/ci/Dockerfile b/project_template/dockerfiles/ci/Dockerfile index 55a89b1..75c8089 100644 --- a/project_template/dockerfiles/ci/Dockerfile +++ b/project_template/dockerfiles/ci/Dockerfile @@ -17,12 +17,6 @@ RUN apt-get update \ RUN curl -sL https://deb.nodesource.com/setup_5.x | bash - \ && apt-get install --yes nodejs -# Install PhantomJS 2.1.1 -RUN cd /tmp && curl -L https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -o phantomjs-2.1.1-linux-x86_64.tar.bz2 \ - && tar xjf phantomjs-2.1.1-linux-x86_64.tar.bz2 \ - && mv /tmp/phantomjs-*/bin/phantomjs /usr/bin/ \ - && rm -r /tmp/phantomjs* - ENV CONTAINER_INIT /usr/local/bin/init-container RUN echo '#!/usr/bin/env bash' > $CONTAINER_INIT ; chmod +x $CONTAINER_INIT @@ -37,5 +31,4 @@ RUN echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen ENV LC_ALL en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US.UTF-8 -ENV PHANTOMJS_BIN /usr/bin/phantomjs ENV BUNDLE_PATH /cache/bundle diff --git a/project_template/spec/spec_helper.rb b/project_template/spec/spec_helper.rb index 13fbf16..965b882 100644 --- a/project_template/spec/spec_helper.rb +++ b/project_template/spec/spec_helper.rb @@ -11,7 +11,10 @@ end Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new(app, {extensions: ['spec/support/function_bind_polyfill.js']}) + Capybara::Poltergeist::Driver.new(app, { + extensions: ['spec/support/function_bind_polyfill.js'], + phantomjs: File.expand_path('node_modules/phantomjs-prebuilt/bin/phantomjs') + }) end Capybara.default_driver = :poltergeist From da85aa845595aae73fe377ea58711f78c5224acb Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 8 Dec 2016 16:58:22 +0100 Subject: [PATCH 18/79] Upgrade node-sass Old version of node-sass spits out some ugly error messages when run on Node >= 6 --- project_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_template/package.json b/project_template/package.json index 875454c..4ca743b 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -40,7 +40,7 @@ "karma-phantomjs-launcher": "~1.0.0", "lolex": "^1.4.0", "mocha": "~3.1.2", - "node-sass": "~3.4.2", + "node-sass": "~3.13.0", "onchange": "~2.1.2", "phantomjs-prebuilt": "~2.1.3", "sinon-chai": "2.8.0", From 37828c956843efcf0919f9dd6f4300d0f4cf5b3d Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 8 Dec 2016 17:33:12 +0100 Subject: [PATCH 19/79] Install latest ios-deploy and ios-sim Also remove no longer needed workaround for aliasify --- project_template/bin/setup | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/project_template/bin/setup b/project_template/bin/setup index 60dc0bb..ea4ce0b 100755 --- a/project_template/bin/setup +++ b/project_template/bin/setup @@ -14,20 +14,11 @@ else fi if [[ "$OSTYPE" == "darwin"* ]]; then - npm_package_available 'ios-deploy@1.8.5' || npm install 'ios-deploy@1.8.5' - npm_package_available 'ios-sim@5.0.6' || npm install 'ios-sim@5.0.6' + npm_package_available 'ios-deploy@1.8.5' || npm install 'ios-deploy@1.9.0' + npm_package_available 'ios-sim@5.0.6' || npm install 'ios-sim@5.0.12' fi npm install - -# Temporary fix because falafel has a dependency on acorn 0.11.0, this can be removed when those packages are upgraded -# my-app@1.0.0 -# └─┬ aliasify@1.8.0 -# └─┬ browserify-transform-tools@1.3.3 -# └─┬ falafel@1.0.1 -# └── acorn@0.11.0 -npm upgrade acorn - bundle install if [ -d .git ]; then From f42ab2de8735025ee82c8457b7d0a2cc89e29b5b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 8 Dec 2016 17:41:03 +0100 Subject: [PATCH 20/79] Don't install dependencies via Homebrew These dependencies are all documented in the README and should be installed by the user manually. Homebrew is just *a* package manager, we shouldn't depend on it. This also leaves the freedom for people to install dependencies via other means, even if they have brew installed. --- project_template/bin/_functions | 8 -------- project_template/bin/setup | 10 ---------- script/_functions | 8 -------- 3 files changed, 26 deletions(-) diff --git a/project_template/bin/_functions b/project_template/bin/_functions index 6703601..1f2e358 100755 --- a/project_template/bin/_functions +++ b/project_template/bin/_functions @@ -12,18 +12,10 @@ function _md5() { return 0 } -function brew_dep_installed() { - brew list | grep "^$@$" >/dev/null 2>&1 -} - function command_available() { command -v "$@" >/dev/null 2>&1 } -function brew_available() { - command_available 'brew' -} - function warning() { echo "$(tput setaf 3)$@$(tput sgr0)" } diff --git a/project_template/bin/setup b/project_template/bin/setup index ea4ce0b..2f2f248 100755 --- a/project_template/bin/setup +++ b/project_template/bin/setup @@ -3,16 +3,6 @@ source "$(dirname ${BASH_SOURCE[0]})/_functions" PROJECT=$(basename $(pwd)) echo "Setting up $PROJECT" -if brew_available ; then - brew_dep_installed 'node' || brew install node - brew_dep_installed 'ant' || brew install ant - - # npm is included with node in newer version, so this normally shouldn't be needed. - command_available 'npm' || brew install npm -else - warning 'Warning: Homebrew not detected. Not installing some dependencies.' -fi - if [[ "$OSTYPE" == "darwin"* ]]; then npm_package_available 'ios-deploy@1.8.5' || npm install 'ios-deploy@1.9.0' npm_package_available 'ios-sim@5.0.6' || npm install 'ios-sim@5.0.12' diff --git a/script/_functions b/script/_functions index 7920a69..0acbbb6 100755 --- a/script/_functions +++ b/script/_functions @@ -4,18 +4,10 @@ function platform_installed() { cordova platforms | head -n 1 | grep $1 >/dev/null } -function brew_dep_installed() { - brew list | grep "^$@$" >/dev/null 2>&1 -} - function command_available() { command -v "$@" >/dev/null 2>&1 } -function brew_available() { - command_available 'brew' -} - function warning() { [[ $# -eq 0 ]] && local message=$(cat -) || local message="$@" echo "$(tput setaf 3)$message$(tput sgr0)" From 8bbdc90398028130e210fa36a1d3315c262db58f Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Fri, 9 Dec 2016 14:37:52 +0100 Subject: [PATCH 21/79] Remove references to Homebrew from docs --- README.md | 14 ++------------ project_template/README.md | 6 ++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bc316b1..154fec9 100644 --- a/README.md +++ b/README.md @@ -17,18 +17,8 @@ Before you can use Maji, make sure you have the following: ### General -* Ruby, for the Cucumber specs -* NodeJS, for the build system (`bin/setup` will install this if you've got Homebrew) -* Homebrew (`bin/setup` will use this to hook you up with all of the dependencies, except Ruby) - -### Mac OS X - -* [Homebrew](http://brew.sh) a Mac OS X package manager - -### Linux - -* [Nodejs](http://nodejs.org) allows JavaScript to be run Server-side -* [Apache Ant](http://ant.apache.org) is used for building Android Applications +* Ruby + Bundler, for the integration specs +* NodeJS + NPM, for the build system ## Getting started diff --git a/project_template/README.md b/project_template/README.md index b489bb3..b54be53 100644 --- a/project_template/README.md +++ b/project_template/README.md @@ -27,9 +27,8 @@ ### General -* Ruby, for the Cucumber specs -* NodeJS, for the build system (`bin/setup` will install this if you've got Homebrew) -* Homebrew (`bin/setup` will use this to hook you up with all of the dependencies, except Ruby) +* Ruby + Bundler, for the integration specs +* NodeJS + NPM, for the build system ### iOS @@ -41,5 +40,4 @@ * Android SDK * Android platform tools installed * Android platform 10+. -* Ant (`brew install ant`) * `android` and `adb` in your $PATH (add `path/to/android-sdk-macosx/tools` and `path/to/android-sdk-macosx/platform-tools` to your $PATH). From 3e12d66e468ac456417af1a153cede2f82dc50a2 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Fri, 9 Dec 2016 07:49:10 +0100 Subject: [PATCH 22/79] Fix broken page transitions after upgrade to Marionette 3 --- lib/marionette_extensions/animatable_region.js | 11 ++++++++++- src/marionette_extensions/animatable_region.coffee | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/marionette_extensions/animatable_region.js b/lib/marionette_extensions/animatable_region.js index 4a7d4cf..53b9ac0 100644 --- a/lib/marionette_extensions/animatable_region.js +++ b/lib/marionette_extensions/animatable_region.js @@ -24,6 +24,7 @@ }); this.showTransitions = opts.showTransitions; this.navigationStack = new NavigationStack(); + this.transitioning = false; AnimatableRegion.__super__.constructor.apply(this, arguments); } @@ -65,6 +66,12 @@ return window.location.hash = route; }; + AnimatableRegion.prototype._empty = function(view, shouldDestroy) { + if (!this.transitioning) { + return AnimatableRegion.__super__._empty.apply(this, arguments); + } + }; + AnimatableRegion.prototype.show = function(view, options) { var ref; if (options == null) { @@ -78,9 +85,9 @@ if (this.transition == null) { this.transition = view.transition || options.transition; } + this.transitioning = true; if (this.transition) { this.currentPage = this.currentView; - options.preventDestroy = true; } return AnimatableRegion.__super__.show.call(this, view, options); }; @@ -146,6 +153,7 @@ _this.currentPage = null; } _this.back = void 0; + _this.transitioning = false; _this.$el.removeClass('viewport-transitioning'); _this.$el.removeClass("viewport-" + _this.transition); return newPage.trigger('transitioned'); @@ -155,6 +163,7 @@ } else { newPage.$el.removeClass('page-pre-in'); this.back = void 0; + this.transitioning = false; return newPage.trigger('transitioned'); } }; diff --git a/src/marionette_extensions/animatable_region.coffee b/src/marionette_extensions/animatable_region.coffee index 5017e5e..ff00104 100644 --- a/src/marionette_extensions/animatable_region.coffee +++ b/src/marionette_extensions/animatable_region.coffee @@ -10,6 +10,7 @@ class AnimatableRegion extends Marionette.Region @showTransitions = opts.showTransitions @navigationStack = new NavigationStack() + @transitioning = false super @@ -35,6 +36,12 @@ class AnimatableRegion extends Marionette.Region window.location.hash = route + _empty: (view, shouldDestroy) -> + # we manage destroying views ourselves, if we're transitioning + # In this case we need to prevent Marionette from destroying the currentView + # before we've transitioned to the new view + super unless @transitioning + show: (view, options = {}) -> view._parent = this return super(view, options) unless @showTransitions @@ -42,10 +49,10 @@ class AnimatableRegion extends Marionette.Region # determine what transition to use, @navigationOptions takes precedence @transition = @navigationOptions?.transition @transition ?= view.transition || options.transition + @transitioning = true if @transition @currentPage = @currentView - options.preventDestroy = true super(view, options) @@ -124,6 +131,7 @@ class AnimatableRegion extends Marionette.Region @currentPage = null @back = undefined + @transitioning = false @$el.removeClass('viewport-transitioning') @$el.removeClass("viewport-#{@transition}") @@ -134,6 +142,7 @@ class AnimatableRegion extends Marionette.Region else newPage.$el.removeClass('page-pre-in') @back = undefined + @transitioning = false newPage.trigger 'transitioned' _isNavigatingBack: (fragment) -> From 74371c08045df785313e9064b58edfab549813dc Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 14 Dec 2016 08:28:27 +0100 Subject: [PATCH 23/79] Update package version to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1a2ff68..f8912b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maji", - "version": "1.1.0", + "version": "2.0.0", "license": "MIT", "main": "lib/index.js", "bin": { From d7692d4dde47a664d08cc26c97e4490ce1f57e02 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 14 Dec 2016 08:57:44 +0100 Subject: [PATCH 24/79] Fix reinstall of ios-deploy and ios-sim on each bin/setup --- project_template/bin/setup | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_template/bin/setup b/project_template/bin/setup index 2f2f248..a11a524 100755 --- a/project_template/bin/setup +++ b/project_template/bin/setup @@ -4,8 +4,8 @@ PROJECT=$(basename $(pwd)) echo "Setting up $PROJECT" if [[ "$OSTYPE" == "darwin"* ]]; then - npm_package_available 'ios-deploy@1.8.5' || npm install 'ios-deploy@1.9.0' - npm_package_available 'ios-sim@5.0.6' || npm install 'ios-sim@5.0.12' + npm_package_available 'ios-deploy@1.9.0' || npm install 'ios-deploy@1.9.0' + npm_package_available 'ios-sim@5.0.12' || npm install 'ios-sim@5.0.12' fi npm install From 15b212bc82ded1f82b5084e24dbf69c4ee18512c Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 21 Dec 2016 09:20:07 +0100 Subject: [PATCH 25/79] Defer installing ios-sim and ios-deploy until ios platform is added This ensures that you don't need Xcode/Xcode Command Line Tools unless you actually want to build for iOS. --- cordova/hooks/before_platform_add.js | 30 ++++++++++++++++++++++++++++ project_template/bin/_functions | 5 ----- project_template/bin/setup | 5 ----- project_template/cordova/config.xml | 2 ++ 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 cordova/hooks/before_platform_add.js diff --git a/cordova/hooks/before_platform_add.js b/cordova/hooks/before_platform_add.js new file mode 100644 index 0000000..9552b80 --- /dev/null +++ b/cordova/hooks/before_platform_add.js @@ -0,0 +1,30 @@ +function ensureDependencyInstalled(Q, mod) { + try { + require(`${mod}/package.json`); + return Q(); + } catch(e) { + console.log(`Installing ${mod}`); + const deferred = Q.defer(); + + exec(`npm install ${mod}`, function(exitCode, stdout, stderr) { + if(exitCode == 0) + deferred.resolve(); + else + deferred.reject(); + }); + + return deferred.promise; + } +} + +module.exports = function(context) { + if(context.opts.cordova.platforms.includes('ios')) { + context.requireCordovaModule('shelljs/global'); + + var Q = context.requireCordovaModule('q'); + return Q.all([ + ensureDependencyInstalled(Q, 'ios-sim'), + ensureDependencyInstalled(Q, 'ios-deploy') + ]); + } +} diff --git a/project_template/bin/_functions b/project_template/bin/_functions index 1f2e358..f97f1dc 100755 --- a/project_template/bin/_functions +++ b/project_template/bin/_functions @@ -19,8 +19,3 @@ function command_available() { function warning() { echo "$(tput setaf 3)$@$(tput sgr0)" } - -function npm_package_available() { - local package=$(npm list --depth=0 2>/dev/null | grep "$@" | cut -d ' ' -f2) - [[ $package == "$@" ]] -} diff --git a/project_template/bin/setup b/project_template/bin/setup index a11a524..d8d6a03 100755 --- a/project_template/bin/setup +++ b/project_template/bin/setup @@ -3,11 +3,6 @@ source "$(dirname ${BASH_SOURCE[0]})/_functions" PROJECT=$(basename $(pwd)) echo "Setting up $PROJECT" -if [[ "$OSTYPE" == "darwin"* ]]; then - npm_package_available 'ios-deploy@1.9.0' || npm install 'ios-deploy@1.9.0' - npm_package_available 'ios-sim@5.0.12' || npm install 'ios-sim@5.0.12' -fi - npm install bundle install diff --git a/project_template/cordova/config.xml b/project_template/cordova/config.xml index 6fd8b1a..cca682b 100644 --- a/project_template/cordova/config.xml +++ b/project_template/cordova/config.xml @@ -5,6 +5,8 @@ + + From 20b377a57a8b5afb8c47053adb9c119adf6cd126 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 21 Dec 2016 16:23:48 +0100 Subject: [PATCH 26/79] Refer to NPM package in README instead of GitHub --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 154fec9..64a27f6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Before you can use Maji, make sure you have the following: To create a new app execute the following commands in your shell: ``` -$ npm install kabisa/maji +$ npm install maji $ ./node_modules/.bin/maji new org.example.my-app /desired/path/to/your/project/ $ cd /desired/path/to/your/project/ $ bin/setup From c2491b722ca6b74d1cceb876449512fa7e27bf0b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 22 Dec 2016 12:07:12 +0100 Subject: [PATCH 27/79] Update Cordova to latest version --- project_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_template/package.json b/project_template/package.json index 4ca743b..8d7c652 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -23,7 +23,7 @@ "aliasify": "~1.9.0", "chai": "~3.5.0", "coffeelint": "~1.15.0", - "cordova": "~6.0.0", + "cordova": "~6.4.0", "envify": "~3.4.0", "exorcist": "~0.4.0", "chokidar": "~1.4.1", From ad3dd27a9d0630d8ff4e6d8625ff65d131dbe504 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 22 Dec 2016 12:35:01 +0100 Subject: [PATCH 28/79] Use Cordova platform management Cordova now has this feature built in. A huge plus is that it allows to pin specific versions, something that maji didn't allow. --- CHANGELOG.md | 1 + docs/upgrade_guide.md | 58 ++++++++++++++++++++--------- project_template/cordova/config.xml | 3 ++ script/_functions | 4 -- script/prepare-cordova-platform | 1 - 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2773a51..84337ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,3 +19,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Maji plugin management (`cordova/plugins.txt`) in favor of Cordova plugin management - Maji application bus in favor of Backbone.Radio +- Maji Cordova platform management, see upgrade guide for details diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index d7fb32f..ea731a6 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -1,4 +1,8 @@ -# Upgrade guide 1.1.0 -> 2.0.0 +# Upgrade guide Maji 1.1.0 -> 2.0.0 + +Maji 2.0 contains several (breaking) changes. Follow the steps outlined in this document to upgrade your app to Maji 2.0. + +## MarionetteJS In Maji 2.0.0 the Marionette dependency has been updated to 3.1.0. @@ -14,34 +18,34 @@ The best way to start is to checkout the updated example app. 2. Update `app/app.coffee`: Add in the top of the file: - + ```coffee Radio = require('backbone.radio') ``` Change the following: - + ```coffee app.addInitializer -> require('./modules/home/home_app').start() ``` - + to: - + ```coffee app.on 'before:start', -> require('./modules/home/home_app') ``` - + Change the kickoff of the backbone history: - + ```coffee app.on 'start', (options) -> Backbone.history.start() ``` - + to: - + ```coffee app.on 'before:start', (options) -> # Bind the start event in after the inclusion of @@ -50,9 +54,9 @@ The best way to start is to checkout the updated example app. app.on 'start', (options) -> Backbone.history.start() ``` - + Update the implementation of the bus functions to: - + ```coffee Radio.channel('app').reply( 'view:current': -> app.getView() @@ -63,6 +67,7 @@ The best way to start is to checkout the updated example app. where = undefined if where == '#' app.getRegion().goBack(where, opts) ) + ``` ``` 3. The best approachs is to disable all modules, and enable/update them one by one, starting with the main module 'app' file: @@ -88,11 +93,11 @@ The best way to start is to checkout the updated example app. controller: API module.exports = EditorApp - ``` - + ``` + To: - - ```coffee + +```coffee # Notice creation of module is gone; # New name for router @@ -109,7 +114,7 @@ The best way to start is to checkout the updated example app. new MyModuleRouter # No passing of controller # No Module exports - ``` +``` 4. Update all your views: @@ -119,16 +124,33 @@ The best way to start is to checkout the updated example app. * `getChildView: ->` is now `childView: ->` -5. Maji 2.x uses Cordova plugin management +## Cordova plugin management + +Maji 2.x uses Cordova plugin management This means that the Cordova plugins you wish to use should now be defined in `cordova/config.xml`, instead of `cordova/plugins.txt`. Refer to the Cordova documentation for details: https://cordova.apache.org/docs/en/latest/platform_plugin_versioning_ref/ +## Cordova platform management + +In Maji 2.0, Maji no longer automatically adds Cordova platforms when you invoke `maji run` or `maji build`. Instead it is expected that you define the platform versions in your `cordova/config.xml` like so: + +```xml + + + +``` + +All projects created with Maji 2.0 are configured like this out of the box. + +## Maji bus + +Update all uses of the `Maji.bus` to Backbone.Radio ([see documentation of radio here][backbone-radio]) + With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). -5. Update all uses of the `Maji.bus` to Backbone.Radio ([see documentation of radio here][backbone-radio]) [marionette-upgrade]: http://marionettejs.com/docs/v3.1.0/upgrade.html [backbone-radio]: https://github.com/marionettejs/backbone.radio \ No newline at end of file diff --git a/project_template/cordova/config.xml b/project_template/cordova/config.xml index cca682b..998b2db 100644 --- a/project_template/cordova/config.xml +++ b/project_template/cordova/config.xml @@ -7,6 +7,9 @@ + + + diff --git a/script/_functions b/script/_functions index 0acbbb6..978927f 100755 --- a/script/_functions +++ b/script/_functions @@ -1,9 +1,5 @@ set -e -function platform_installed() { - cordova platforms | head -n 1 | grep $1 >/dev/null -} - function command_available() { command -v "$@" >/dev/null 2>&1 } diff --git a/script/prepare-cordova-platform b/script/prepare-cordova-platform index 129ba00..5a374b1 100755 --- a/script/prepare-cordova-platform +++ b/script/prepare-cordova-platform @@ -3,7 +3,6 @@ source "$(dirname ${BASH_SOURCE[0]})/_functions" PLATFORM=$1 pushd cordova >/dev/null 2>&1 - platform_installed $PLATFORM || cordova platforms add $PLATFORM cordova prepare $PLATFORM if [ -f plugins.txt ]; then From 60b8a67c8f8bc7fcf70da710e734b0f182e4959b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Sun, 25 Dec 2016 20:55:25 +0100 Subject: [PATCH 29/79] Add maji-dev-server Contains functionality that previously lived in each project. --- package.json | 9 +++++-- src/maji-dev-server.js | 60 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100755 src/maji-dev-server.js diff --git a/package.json b/package.json index f8912b0..785c49d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "license": "MIT", "main": "lib/index.js", "bin": { - "maji": "./lib/cli.js" + "maji": "./lib/cli.js", + "maji-dev-server": "./src/maji-dev-server.js" }, "repository": { "type": "git", @@ -34,6 +35,10 @@ }, "dependencies": { "i18n-js": "http://github.com/fnando/i18n-js/archive/v3.0.0.rc8.tar.gz", - "commander": "~2.6.0" + "commander": "~2.9.0", + "express": "4.14.0", + "tiny-lr": "1.0.3", + "chokidar": "1.6.1", + "connect-livereload": "0.6.0" } } diff --git a/src/maji-dev-server.js b/src/maji-dev-server.js new file mode 100755 index 0000000..ec0afc4 --- /dev/null +++ b/src/maji-dev-server.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node +var path = require('path'); +var express = require('express'); +var tinylr = require('tiny-lr'); +var chokidar = require('chokidar'); +var program = require('commander'); + +var parseBoolean = function(value) { + return (value === 'true'); +}; + +var parsePort = function(value) { + return parseInt(value) || null; +}; + +program + .usage('[options] ') + .option('-p, --port [port]', 'Port to listen on [9090]', parsePort, 9090) + .option('-l, --livereload [flag]', 'Enable livereload [true]', parseBoolean, true) + .parse(process.argv); + +var port = program.port; +var livereload = program.livereload; +var assetPath = program.args[0]; + +if (! assetPath) { + program.outputHelp(); + process.exit(1); +} + +var server = express(); +var startFileWatcher = function() { + var watcher = chokidar.watch(assetPath + '/**/*'); + watcher.on('ready', function(){ + console.log('Livereload enabled. Watching for changes in', assetPath); + + watcher.on('all', function(event, path) { + tinylr.changed(path) + }); + }); +}; + +if(livereload) { + server + .use(tinylr.middleware({ app: server, dashboard: true })) + .use(require('connect-livereload')({ + src: '/livereload.js?snipver=1', + include: [ '/', '.*\.html'] + })); +} + +server + .use(express.static(assetPath, { + etag: false, + lastModified: false + })) + .listen(port, function() { + console.log('Server listening on', port); + if(livereload) startFileWatcher(); + }); From 37a8f383efc95d43a1926cd607f15539351cf4e7 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Sun, 25 Dec 2016 20:58:31 +0100 Subject: [PATCH 30/79] Remove stuff no longer needed from project template Replaced by maji-dev-server --- project_template/Makefile | 12 ++---------- project_template/package.json | 3 --- project_template/script/inject-livereload.js | 17 ----------------- project_template/script/livereload.js | 15 --------------- 4 files changed, 2 insertions(+), 45 deletions(-) delete mode 100644 project_template/script/inject-livereload.js delete mode 100755 project_template/script/livereload.js diff --git a/project_template/Makefile b/project_template/Makefile index 5b971ea..82281f4 100644 --- a/project_template/Makefile +++ b/project_template/Makefile @@ -2,7 +2,6 @@ export DIST_DIR ?= ./dist export SHELL := /bin/bash -e -o pipefail export PATH := $(PATH):$(shell npm bin) export APP_ENV ?=development -SERVER_PORT ?= 9090 BROWSERIFY_BASE_CONFIG = \ -t coffeeify \ -t aliasify \ @@ -19,7 +18,6 @@ production: build-statics: cp -R public/* $(DIST_DIR)/ - [ '$(LIVERELOAD)' = 'true' ] && node script/inject-livereload.js '$(DIST_DIR)/index.html' || exit 0 revhash: source bin/_functions ; HASH=$$(cat $(DIST_DIR)/assets/app.js | _md5) && \ @@ -40,20 +38,14 @@ build-css: build-icons: DIST_DIR='$(DIST_DIR)' node script/build-iconfont.js -livereload: - [ -n "$$LIVERELOAD" ] && ((sleep 5 && node script/livereload.js '$(DIST_DIR)/**/*') &) || exit 0 - -check-server-port: - @nc -z 127.0.0.1 $(SERVER_PORT) >/dev/null && echo 'Port $(SERVER_PORT) occupied, is a server already running?' && exit 1 || exit 0 - serve: DIST_DIR := './tmp/watch-build' -serve: check-server-port clean build-statics build-icons build-css livereload +serve: clean build-statics build-icons build-css onchange 'public/**/*' -- make build-statics & CSS_OUTPUT_STYLE=expanded onchange 'app/styles/**/*.scss' -- make build-css & onchange 'app/styles/icons/*.svg' -- make build-icons & watchify -d -v $(BROWSERIFY_BASE_CONFIG) -x bugsnag-js \ app/application.coffee -o $(DIST_DIR)/assets/app.js & - sleep 2 && http-server -p $(SERVER_PORT) $(DIST_DIR) + sleep 2 && maji-dev-server $(DIST_DIR) --port=$(SERVER_PORT) --livereload=$(LIVERELOAD) watch: serve diff --git a/project_template/package.json b/project_template/package.json index 8d7c652..440ab4c 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -26,11 +26,9 @@ "cordova": "~6.4.0", "envify": "~3.4.0", "exorcist": "~0.4.0", - "chokidar": "~1.4.1", "gulp-iconfont": "~6.0.0", "gulp-iconfont-css": "~2.0.0", "haml-coffee-browserify": "~0.0.4", - "http-server": "~0.9.0", "sinon": "~1.17.2", "karma": "~0.13.22", "karma-browserify": "~5.0.2", @@ -44,7 +42,6 @@ "onchange": "~2.1.2", "phantomjs-prebuilt": "~2.1.3", "sinon-chai": "2.8.0", - "tiny-lr": "~0.2.1", "uglifyify": "~3.0.1", "vinyl-fs": "~2.4.2", "watchify": "~3.7.0", diff --git a/project_template/script/inject-livereload.js b/project_template/script/inject-livereload.js deleted file mode 100644 index 3ae8339..0000000 --- a/project_template/script/inject-livereload.js +++ /dev/null @@ -1,17 +0,0 @@ -var fs = require('fs'); - -var injectLiveReloadTag = function(html) { - var livereloadScriptTag = ""; - - var bodyTagLocation = html.indexOf(''); - - if(bodyTagLocation != -1) { - html = html.slice(0, bodyTagLocation) + livereloadScriptTag + html.slice(bodyTagLocation); - } - - return html; -}; - -var file = process.argv[2]; -var html = fs.readFileSync(file).toString(); -fs.writeFileSync(file, injectLiveReloadTag(html)); diff --git a/project_template/script/livereload.js b/project_template/script/livereload.js deleted file mode 100755 index ab95594..0000000 --- a/project_template/script/livereload.js +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -var tinylr = require('tiny-lr'); -var chokidar = require('chokidar'); - -var lr = new tinylr.Server -lr.listen(35729, function (err) { if(err) { throw err } }); - -var watcher = chokidar.watch(process.argv[2]); -watcher.on('ready', function(){ - watcher.on('all', function(event, path) { - console.log('Reloading browser...') - lr.changed({body: {files: [path]}}) - }); -}); From f48c85fb987105fc241a68d760138d799515d7a5 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 2 Jan 2017 12:56:07 +0100 Subject: [PATCH 31/79] Downgrade rainbow gem to workaround issue in new version --- project_template/Gemfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project_template/Gemfile b/project_template/Gemfile index 7ae4ec8..e355a20 100644 --- a/project_template/Gemfile +++ b/project_template/Gemfile @@ -5,4 +5,7 @@ group :development, :test do gem 'capybara', '~> 2.4.4' gem 'poltergeist', '~> 1.6.0' gem 'scss_lint', '0.38.0' + + # temporary workaround for https://github.com/sickill/rainbow/issues/44 + gem 'rainbow', '2.1.0' end From 8dc2a86d42f66aa2834c87928553f41f39581f16 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 09:11:58 +0100 Subject: [PATCH 32/79] Upgrade CI to Node 6 --- dockerfiles/ci/Dockerfile | 3 +-- project_template/dockerfiles/ci/Dockerfile | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dockerfiles/ci/Dockerfile b/dockerfiles/ci/Dockerfile index 75c8089..8030d32 100644 --- a/dockerfiles/ci/Dockerfile +++ b/dockerfiles/ci/Dockerfile @@ -13,8 +13,7 @@ RUN apt-get update \ locales \ && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Install NodeJS 5 -RUN curl -sL https://deb.nodesource.com/setup_5.x | bash - \ +RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \ && apt-get install --yes nodejs ENV CONTAINER_INIT /usr/local/bin/init-container diff --git a/project_template/dockerfiles/ci/Dockerfile b/project_template/dockerfiles/ci/Dockerfile index 75c8089..8030d32 100644 --- a/project_template/dockerfiles/ci/Dockerfile +++ b/project_template/dockerfiles/ci/Dockerfile @@ -13,8 +13,7 @@ RUN apt-get update \ locales \ && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Install NodeJS 5 -RUN curl -sL https://deb.nodesource.com/setup_5.x | bash - \ +RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \ && apt-get install --yes nodejs ENV CONTAINER_INIT /usr/local/bin/init-container From 73fc853a1b26bdcb8f6c17dc8bb5ad500ca0be25 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 09:35:03 +0100 Subject: [PATCH 33/79] Document Node 6 requirement + add to package.json NPM will now warn when trying to install Maji on Node < 6 --- CHANGELOG.md | 1 + README.md | 2 +- package.json | 3 +++ project_template/README.md | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84337ac..9e01dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Updated Backbone to 1.3.3 - Updated Mocha to 3.1.2 - Updated jQuery to 3.0.0 +- Node.js >= 6 is now required ### Removed diff --git a/README.md b/README.md index 64a27f6..dd241b1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Before you can use Maji, make sure you have the following: ### General * Ruby + Bundler, for the integration specs -* NodeJS + NPM, for the build system +* Node.js >= 6 + NPM, for the build system ## Getting started diff --git a/package.json b/package.json index 785c49d..2f31c96 100644 --- a/package.json +++ b/package.json @@ -40,5 +40,8 @@ "tiny-lr": "1.0.3", "chokidar": "1.6.1", "connect-livereload": "0.6.0" + }, + "engines": { + "node": ">=6" } } diff --git a/project_template/README.md b/project_template/README.md index b54be53..852dea8 100644 --- a/project_template/README.md +++ b/project_template/README.md @@ -28,7 +28,7 @@ ### General * Ruby + Bundler, for the integration specs -* NodeJS + NPM, for the build system +* Node.js >=6 + NPM, for the build system ### iOS From f4d5832a119a72e0a83948774ec67b4aa946636b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 08:21:16 +0100 Subject: [PATCH 34/79] Fix FastClick for webpack compatibility https://github.com/ftlabs/fastclick/issues/398 --- project_template/app/application.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_template/app/application.coffee b/project_template/app/application.coffee index c9f5084..5d48cdd 100644 --- a/project_template/app/application.coffee +++ b/project_template/app/application.coffee @@ -1,10 +1,10 @@ $ = require('jquery') -attachFastClick = require('fastclick') +FastClick = require('fastclick') app = require('./app') $ -> app.start() - attachFastClick(document.body) + FastClick.attach(document.body) # Stretch main container height so it's not resized when the viewport is # resized. This happens on Android when the keyboard pops up. From 23d4483fb9cb97c6bbe8deca4da57c108eb07208 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 08:46:00 +0100 Subject: [PATCH 35/79] Switch from Browserify to webpack for new Maji projects --- README.md | 2 +- project_template/Makefile | 14 +------ project_template/app/config/settings.coffee | 6 +-- project_template/karma.conf.js | 16 +++----- project_template/package.json | 33 ++++++---------- project_template/spec/spec_index.coffee | 14 +++++++ project_template/webpack.config.js | 44 +++++++++++++++++++++ 7 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 project_template/spec/spec_index.coffee create mode 100644 project_template/webpack.config.js diff --git a/README.md b/README.md index dd241b1..9def3fa 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ A Maji Mobile App comes with several frameworks built-in and configured to work * [MarionetteJS](http://marionettejs.com) Marionette simplifies Backbone Views * [FastClick](http://ftlabs.github.io/fastclick/) disable the delay between click and the action on iOS * [jQuery](http://jquery.com) JavaScript library for working with the DOM - * [Browserify](http://browserify.org) is a JavaScript module system that supports CommonJS syntax + * [Webpack](https://webpack.js.org/) is a JavaScript module bundler * [BugSnagJS](https://github.com/bugsnag/bugsnag-js) JavaScript client for [BugSnag](http://bugsnag.com/) exception tracker * [Karma](http://karma-runner.github.io/) is a JavaScript test runner * [MochaJS](http://mochajs.org) a JavaScript testing framework that supports a BDD style of writing tests diff --git a/project_template/Makefile b/project_template/Makefile index 82281f4..35cd03f 100644 --- a/project_template/Makefile +++ b/project_template/Makefile @@ -2,14 +2,6 @@ export DIST_DIR ?= ./dist export SHELL := /bin/bash -e -o pipefail export PATH := $(PATH):$(shell npm bin) export APP_ENV ?=development -BROWSERIFY_BASE_CONFIG = \ - -t coffeeify \ - -t aliasify \ - -t yamlify \ - -t [ haml-coffee-browserify ] \ - -t [ envify purge ] \ - -t brfs \ - --extension .hamlc --extension .coffee dist: clean build-statics build-js build-icons build-css revhash @@ -29,8 +21,7 @@ revhash: perl -i -pe s/app.css/app-$$HASH.css/ dist/index.html build-js: - browserify -g uglifyify $(BROWSERIFY_BASE_CONFIG) --debug \ - app/application.coffee | exorcist $(DIST_DIR)/assets/app.js.map | sed '/^\s*$$/d' > $(DIST_DIR)/assets/app.js + webpack --output-path $(DIST_DIR)/assets/ build-css: node-sass --stdout --output-style $${CSS_OUTPUT_STYLE:-compressed} --include-path vendor/styles app/styles/application.scss | postcss --use autoprefixer --autoprefixer.browsers 'ios >= 8, android >= 4, ie >=10' > $(DIST_DIR)/assets/app.css @@ -43,8 +34,7 @@ serve: clean build-statics build-icons build-css onchange 'public/**/*' -- make build-statics & CSS_OUTPUT_STYLE=expanded onchange 'app/styles/**/*.scss' -- make build-css & onchange 'app/styles/icons/*.svg' -- make build-icons & - watchify -d -v $(BROWSERIFY_BASE_CONFIG) -x bugsnag-js \ - app/application.coffee -o $(DIST_DIR)/assets/app.js & + webpack --output-path $(DIST_DIR)/assets/ --watch & sleep 2 && maji-dev-server $(DIST_DIR) --port=$(SERVER_PORT) --livereload=$(LIVERELOAD) watch: serve diff --git a/project_template/app/config/settings.coffee b/project_template/app/config/settings.coffee index 6580af6..031cef4 100644 --- a/project_template/app/config/settings.coffee +++ b/project_template/app/config/settings.coffee @@ -1,5 +1 @@ -fs = require('fs') - -module.exports = JSON.parse( - fs.readFileSync(__dirname + "/settings.#{process.env.APP_ENV}.json", 'utf8') -) +module.exports = require("./settings.#{process.env.APP_ENV}.json") diff --git a/project_template/karma.conf.js b/project_template/karma.conf.js index 0827179..36a0145 100644 --- a/project_template/karma.conf.js +++ b/project_template/karma.conf.js @@ -3,16 +3,14 @@ module.exports = function(karma) { karma.set({ - frameworks: [ 'mocha', 'sinon-chai', 'browserify' ], + frameworks: [ 'mocha', 'sinon-chai' ], files: [ - { pattern: 'spec/spec_helper.coffee', watched: false, included: true, served: true }, - { pattern: 'spec/**/*spec.coffee', watched: false, included: true, served: true } + 'spec/spec_index.coffee' ], preprocessors: { - 'spec/spec_helper.coffee': [ 'browserify' ], - 'spec/**/*spec.coffee': [ 'browserify' ] + 'spec/spec_index.coffee': ['webpack'] }, client: { @@ -25,11 +23,9 @@ module.exports = function(karma) { reporters: ['mocha'], browsers: [ 'PhantomJS' ], - // browserify configuration - browserify: { - debug: true, - extensions: ['.hamlc', '.coffee'], - transform: [ 'coffeeify', 'aliasify', 'yamlify', 'haml-coffee-browserify', ['envify', { _: 'purge' }], 'brfs' ] + webpack: require('./webpack.config.js'), + webpackMiddleware: { + stats: 'errors-only' } }); }; diff --git a/project_template/package.json b/project_template/package.json index 440ab4c..40683ff 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -1,5 +1,5 @@ { - "name": "##APP_NAME##", + "name": "example", "version": "1.0.0", "private": true, "dependencies": { @@ -14,43 +14,32 @@ "maji": "kabisa/maji#2-0-stable" }, "devDependencies": { - "acorn": "~2.6.4", "autoprefixer": "~6.3.3", - "postcss-cli": "~2.5.1", - "brfs": "~1.4.3", - "browserify": "~13.0.0", - "coffeeify": "~2.0.1", - "aliasify": "~1.9.0", "chai": "~3.5.0", + "coffee-loader": "^0.7.2", "coffeelint": "~1.15.0", "cordova": "~6.4.0", - "envify": "~3.4.0", - "exorcist": "~0.4.0", "gulp-iconfont": "~6.0.0", "gulp-iconfont-css": "~2.0.0", "haml-coffee-browserify": "~0.0.4", - "sinon": "~1.17.2", + "json-loader": "^0.5.4", "karma": "~0.13.22", - "karma-browserify": "~5.0.2", "karma-mocha": "~0.2.2", "karma-mocha-reporter": "~2.0.0", - "karma-sinon-chai": "~1.2.0", "karma-phantomjs-launcher": "~1.0.0", - "lolex": "^1.4.0", + "karma-sinon-chai": "~1.2.0", + "karma-webpack": "^1.8.1", + "memo-is": "0.0.2", "mocha": "~3.1.2", "node-sass": "~3.13.0", "onchange": "~2.1.2", "phantomjs-prebuilt": "~2.1.3", + "postcss-cli": "~2.5.1", + "sinon": "~1.17.2", "sinon-chai": "2.8.0", - "uglifyify": "~3.0.1", + "transform-loader": "^0.2.3", "vinyl-fs": "~2.4.2", - "watchify": "~3.7.0", - "yamlify": "~0.1.2", - "memo-is": "0.0.2" - }, - "aliasify": { - "aliases": { - "app": "./app" - } + "webpack": "^2.2.0-rc.3", + "yaml-loader": "^0.4.0" } } diff --git a/project_template/spec/spec_index.coffee b/project_template/spec/spec_index.coffee new file mode 100644 index 0000000..864df66 --- /dev/null +++ b/project_template/spec/spec_index.coffee @@ -0,0 +1,14 @@ +# https://github.com/webpack/karma-webpack#alternative-usage +# +# Without this, karma and webpack don't play nice together since webpack +# will compile a bundle with jquery, marionette etc for each spec file. +# +# With this, all specs are compiled together. Single specs can be run with +# mochas describe.only feature. +testsContext = require.context('.', true, /_spec$/) +testsContext.keys().forEach (path) -> + try + testsContext(path) + catch err + console.error('[ERROR] WITH SPEC FILE:', path) + console.error(err) diff --git a/project_template/webpack.config.js b/project_template/webpack.config.js new file mode 100644 index 0000000..4c5b318 --- /dev/null +++ b/project_template/webpack.config.js @@ -0,0 +1,44 @@ +const path = require('path'); +const webpack = require('webpack'); + +const APP_ENV = process.env.APP_ENV || 'development'; +const IS_PROD_BUILD = ! ["development", "test"].includes(APP_ENV); + +const plugins = [ + new webpack.DefinePlugin({ + 'process.env':{ + 'APP_ENV': JSON.stringify(APP_ENV), + } + }) +]; + +if(IS_PROD_BUILD) plugins.push(new webpack.optimize.UglifyJsPlugin({minimize: true})); + +module.exports = { + entry: './app/application.coffee', + output: { + path: path.resolve(__dirname, 'dist/assets'), + filename: 'app.js' + }, + module: { + rules: [ + { test: /\.coffee$/, loader: 'coffee-loader' }, + { test: /\.hamlc$/, loader: 'transform-loader?haml-coffee-browserify' }, + { test: /\.json$/, loader: 'json-loader' }, + { test: /\.yml$/, loader: 'json-loader!yaml-loader' }, + ] + }, + resolve: { + extensions: ['.js', '.coffee', '.hamlc'], + alias: { + 'app': path.resolve(__dirname, 'app/') + } + }, + node: { + process: false + }, + plugins: plugins, + performance: { + hints: false + } +} From cc439e17f32dd309c7a8c6d274701c7b5b819655 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 09:17:37 +0100 Subject: [PATCH 36/79] Update documentation --- CHANGELOG.md | 1 + docs/upgrade_guide.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e01dee..2367f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Updated Mocha to 3.1.2 - Updated jQuery to 3.0.0 - Node.js >= 6 is now required +- Switched from Browserify to webpack module bundler ### Removed diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index ea731a6..fab676c 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -148,7 +148,9 @@ All projects created with Maji 2.0 are configured like this out of the box. Update all uses of the `Maji.bus` to Backbone.Radio ([see documentation of radio here][backbone-radio]) +## Switch from Browserify to webpack (optional) +With Maji 2.x new projects use webpack for module bundling. While not required it's recommended that you migrate your projects to webpack, see [this Pull Request](https://github.com/kabisa/maji/pull/128) for reasoning and benefits. This PR can be used as a basis to migrate your project to webpack. With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). From 451c3a3eb46ee950d8654e368a2faaab4a0787f5 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 10:04:42 +0100 Subject: [PATCH 37/79] Upgrade coffeelint and node-sass Upgrade coffeelint removes one deprecation warning :-) --- project_template/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_template/package.json b/project_template/package.json index 40683ff..6aa423d 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -17,7 +17,7 @@ "autoprefixer": "~6.3.3", "chai": "~3.5.0", "coffee-loader": "^0.7.2", - "coffeelint": "~1.15.0", + "coffeelint": "~1.16.0", "cordova": "~6.4.0", "gulp-iconfont": "~6.0.0", "gulp-iconfont-css": "~2.0.0", @@ -31,7 +31,7 @@ "karma-webpack": "^1.8.1", "memo-is": "0.0.2", "mocha": "~3.1.2", - "node-sass": "~3.13.0", + "node-sass": "~4.1.1", "onchange": "~2.1.2", "phantomjs-prebuilt": "~2.1.3", "postcss-cli": "~2.5.1", From 315d33ec23f63e1b78429db2b7b5733177dcb89e Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 4 Jan 2017 11:09:42 +0100 Subject: [PATCH 38/79] Improve project Makefile * Supports Make stale file detection. E.g. don't build anything that doesn't need building (this is a HUGE speed improvement). * Clean up build output. No need to show all the noisy output of build tools. --- project_template/Makefile | 97 ++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/project_template/Makefile b/project_template/Makefile index 35cd03f..9bfffec 100644 --- a/project_template/Makefile +++ b/project_template/Makefile @@ -1,49 +1,82 @@ -export DIST_DIR ?= ./dist -export SHELL := /bin/bash -e -o pipefail +export SHELL := /usr/bin/env bash -e -o pipefail export PATH := $(PATH):$(shell npm bin) export APP_ENV ?=development +DIST_DIR := ./dist +BUILD_DIR ?= ./tmp/build +WATCH_BUILD_DIR := ./tmp/watch-build +JS_BUNDLE_MAIN := app/application.coffee -dist: clean build-statics build-js build-icons build-css revhash +WEBPACK_FILES := $(shell find app -name '*.coffee' -o -name '*.hamlc' -o -name '*.yml' -o -name '*.json') +SCSS_FILES := $(shell find app/styles -name '*.scss') +PUBLIC_FILES := $(shell find public -type f) +ICON_FILES := $(shell find app/styles/icons -name '*.svg') +ICON_FILES_TARGET := $(patsubst %, $(BUILD_DIR)/assets/fonts/icons.%, woff eot ttf) +PUBLIC_FILES_TARGET := $(PUBLIC_FILES:public/%=$(BUILD_DIR)/%) +ALL_TARGETS := $(BUILD_DIR)/assets/app.js \ + $(ICON_FILES_TARGET) \ + $(BUILD_DIR)/assets/app.css \ + $(PUBLIC_FILES_TARGET) -production: - APP_ENV=production make build +dist: check-env $(ALL_TARGETS) copy-to-dist -build-statics: - cp -R public/* $(DIST_DIR)/ +$(BUILD_DIR)/assets/app.js: $(WEBPACK_FILES) + @echo Compiling js + @mkdir -p $(@D) + @webpack --hide-modules --output-path $(BUILD_DIR)/assets/ -revhash: - source bin/_functions ; HASH=$$(cat $(DIST_DIR)/assets/app.js | _md5) && \ - mv $(DIST_DIR)/assets/app.js $(DIST_DIR)/assets/app-$$HASH.js && \ - perl -i -pe s/app.js/app-$$HASH.js/ dist/index.html +$(BUILD_DIR)/assets/app.css: $(SCSS_FILES) $(ICON_FILES) + @echo Compiling sass + @mkdir -p $(@D) + @node-sass --stdout --output-style $${CSS_OUTPUT_STYLE:-compressed} --include-path vendor/styles app/styles/application.scss | postcss --use autoprefixer --autoprefixer.browsers 'ios >= 8, android >= 4, ie >=10' > $(BUILD_DIR)/assets/app.css - source bin/_functions ; HASH=$$(cat $(DIST_DIR)/assets/app.css | _md5) && \ - mv $(DIST_DIR)/assets/app.css $(DIST_DIR)/assets/app-$$HASH.css && \ - perl -i -pe s/app.css/app-$$HASH.css/ dist/index.html +$(ICON_FILES_TARGET): $(ICON_FILES) + @echo Building icon font + @DIST_DIR='$(BUILD_DIR)' node script/build-iconfont.js -build-js: - webpack --output-path $(DIST_DIR)/assets/ +$(BUILD_DIR)/%: public/% + @echo "Copying $<" + @mkdir -p $(@D) + @cp "$<" "$@" -build-css: - node-sass --stdout --output-style $${CSS_OUTPUT_STYLE:-compressed} --include-path vendor/styles app/styles/application.scss | postcss --use autoprefixer --autoprefixer.browsers 'ios >= 8, android >= 4, ie >=10' > $(DIST_DIR)/assets/app.css +copy-to-dist: + @echo Copying to $(DIST_DIR) + @rm -rf $(DIST_DIR)/* + @mkdir -p $(DIST_DIR) + @cp -R $(BUILD_DIR)/* $(DIST_DIR) -build-icons: - DIST_DIR='$(DIST_DIR)' node script/build-iconfont.js + @source bin/_functions ; HASH=$$(cat $(DIST_DIR)/assets/app.js | _md5) && \ + mv $(DIST_DIR)/assets/app.js $(DIST_DIR)/assets/app-$$HASH.js && \ + perl -i -pe s/app.js/app-$$HASH.js/ $(DIST_DIR)/index.html -serve: DIST_DIR := './tmp/watch-build' -serve: clean build-statics build-icons build-css - onchange 'public/**/*' -- make build-statics & - CSS_OUTPUT_STYLE=expanded onchange 'app/styles/**/*.scss' -- make build-css & - onchange 'app/styles/icons/*.svg' -- make build-icons & - webpack --output-path $(DIST_DIR)/assets/ --watch & - sleep 2 && maji-dev-server $(DIST_DIR) --port=$(SERVER_PORT) --livereload=$(LIVERELOAD) + @source bin/_functions ; HASH=$$(cat $(DIST_DIR)/assets/app.css | _md5) && \ + mv $(DIST_DIR)/assets/app.css $(DIST_DIR)/assets/app-$$HASH.css && \ + perl -i -pe s/app.css/app-$$HASH.css/ $(DIST_DIR)/index.html -watch: serve +clean: + @rm -rf $(BUILD_DIR) + @rm -rf $(WATCH_BUILD_DIR) test: bin/ci -clean: - rm -rf $(DIST_DIR) && mkdir -p $(DIST_DIR)/assets +check-env: + @# check if we previously build with a different APP_ENV. If the APP_ENV has changed + @# we need to recompile + @[ "$$(cat tmp/.build-env 2>/dev/null)" == "$(APP_ENV)" ] || (echo '$(APP_ENV)' > tmp/.build-env && touch $(JS_BUNDLE_MAIN)) + @echo Building with APP_ENV = $(APP_ENV) + +watch: + @BUILD_DIR=$(WATCH_BUILD_DIR) $(MAKE) doWatch + +doWatch: check-env $(ALL_TARGETS) + @onchange 'public/**/*' -- $(MAKE) FILE='{{changed}}' copy-watched-file & + @CSS_OUTPUT_STYLE=expanded onchange 'app/styles/**/*.scss' -- $(MAKE) $(BUILD_DIR)/assets/app.css & + @onchange 'app/styles/icons/*.svg' -- $(MAKE) -s $(BUILD_DIR)/assets/fonts/icons.eot & + @webpack --output-path $(BUILD_DIR)/assets/ --watch & + @sleep 2 && maji-dev-server $(BUILD_DIR) --port=$(SERVER_PORT) --livereload=$(LIVERELOAD) + +copy-watched-file: + @echo "Copying $(FILE)" + @cp "$(FILE)" "$(BUILD_DIR)/" -.PHONY: clean build-statics build-js build-css build-icons serve watch -.SILENT: serve revhash +.PHONY: clean dist test check-env copy-to-dist watch doWatch copy-watched-file From 1eb9aefa89df268bd6679856dfcb8fd833c49723 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 9 Jan 2017 09:34:52 +0100 Subject: [PATCH 39/79] Fix copying assets during watch build The `cp` used before would not preserve directory structure. --- project_template/Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/project_template/Makefile b/project_template/Makefile index 9bfffec..e052404 100644 --- a/project_template/Makefile +++ b/project_template/Makefile @@ -69,14 +69,13 @@ watch: @BUILD_DIR=$(WATCH_BUILD_DIR) $(MAKE) doWatch doWatch: check-env $(ALL_TARGETS) - @onchange 'public/**/*' -- $(MAKE) FILE='{{changed}}' copy-watched-file & + @onchange 'public/**/*' -- $(MAKE) BUILD_DIR="$(WATCH_BUILD_DIR)" FILE='{{changed}}' copy-watched-file & @CSS_OUTPUT_STYLE=expanded onchange 'app/styles/**/*.scss' -- $(MAKE) $(BUILD_DIR)/assets/app.css & @onchange 'app/styles/icons/*.svg' -- $(MAKE) -s $(BUILD_DIR)/assets/fonts/icons.eot & @webpack --output-path $(BUILD_DIR)/assets/ --watch & @sleep 2 && maji-dev-server $(BUILD_DIR) --port=$(SERVER_PORT) --livereload=$(LIVERELOAD) -copy-watched-file: - @echo "Copying $(FILE)" - @cp "$(FILE)" "$(BUILD_DIR)/" + +copy-watched-file: $(FILE:public/%=$(BUILD_DIR)/%) .PHONY: clean dist test check-env copy-to-dist watch doWatch copy-watched-file From d7dd990b3513c7c676d0cd814f90602e527f8ddd Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Tue, 10 Jan 2017 20:06:05 +0100 Subject: [PATCH 40/79] Pass all arguments to the Marionette renderer Provides access to the view, which is the third argument to render --- lib/marionette_extensions/marionette_renderer.js | 4 ++-- src/marionette_extensions/marionette_renderer.coffee | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/marionette_extensions/marionette_renderer.js b/lib/marionette_extensions/marionette_renderer.js index 65e375e..f436530 100644 --- a/lib/marionette_extensions/marionette_renderer.js +++ b/lib/marionette_extensions/marionette_renderer.js @@ -11,7 +11,7 @@ module.exports.setup = function() { var defaultRender; defaultRender = Marionette.Renderer.render; - return Marionette.Renderer.render = function(template, data) { + return Marionette.Renderer.render = function() { data.t = function() { return I18n.t.apply(I18n, arguments); }; @@ -19,7 +19,7 @@ return I18n.l.apply(I18n, arguments); }; data.h = templateHelpers; - return defaultRender(template, data); + return defaultRender.apply(Marionette.Renderer, arguments); }; }; diff --git a/src/marionette_extensions/marionette_renderer.coffee b/src/marionette_extensions/marionette_renderer.coffee index ae837a2..1f77db6 100644 --- a/src/marionette_extensions/marionette_renderer.coffee +++ b/src/marionette_extensions/marionette_renderer.coffee @@ -5,9 +5,9 @@ templateHelpers = require('../lib/template_helpers') module.exports.setup = -> defaultRender = Marionette.Renderer.render - Marionette.Renderer.render = (template, data) -> + Marionette.Renderer.render = -> data.t = -> I18n.t.apply(I18n, arguments) data.l = -> I18n.l.apply(I18n, arguments) data.h = templateHelpers - defaultRender(template, data) + defaultRender.apply(Marionette.Renderer, arguments) From 9b7e9e3311929a74573069aec4371628fa53c0b5 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Tue, 10 Jan 2017 20:12:43 +0100 Subject: [PATCH 41/79] Fix marionette renderer --- lib/marionette_extensions/marionette_renderer.js | 2 +- src/marionette_extensions/marionette_renderer.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/marionette_extensions/marionette_renderer.js b/lib/marionette_extensions/marionette_renderer.js index f436530..70bf81a 100644 --- a/lib/marionette_extensions/marionette_renderer.js +++ b/lib/marionette_extensions/marionette_renderer.js @@ -11,7 +11,7 @@ module.exports.setup = function() { var defaultRender; defaultRender = Marionette.Renderer.render; - return Marionette.Renderer.render = function() { + return Marionette.Renderer.render = function(template, data) { data.t = function() { return I18n.t.apply(I18n, arguments); }; diff --git a/src/marionette_extensions/marionette_renderer.coffee b/src/marionette_extensions/marionette_renderer.coffee index 1f77db6..521b614 100644 --- a/src/marionette_extensions/marionette_renderer.coffee +++ b/src/marionette_extensions/marionette_renderer.coffee @@ -5,7 +5,7 @@ templateHelpers = require('../lib/template_helpers') module.exports.setup = -> defaultRender = Marionette.Renderer.render - Marionette.Renderer.render = -> + Marionette.Renderer.render = (template, data) -> data.t = -> I18n.t.apply(I18n, arguments) data.l = -> I18n.l.apply(I18n, arguments) data.h = templateHelpers From ddd35ce30c976755c5c1c573a1bfbb2acca8f183 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 11 Jan 2017 12:46:46 +0100 Subject: [PATCH 42/79] Publish deviceready on app channel It was notably missing from all published events :-) --- spec/cordova_support_spec.coffee | 14 ++++++++++++++ src/cordova_support.coffee | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 spec/cordova_support_spec.coffee diff --git a/spec/cordova_support_spec.coffee b/spec/cordova_support_spec.coffee new file mode 100644 index 0000000..2bdcc28 --- /dev/null +++ b/spec/cordova_support_spec.coffee @@ -0,0 +1,14 @@ +window.cordova = { platformId: 'web' } +require('./spec_helper') +require('../src/cordova_support') + +Radio = require('backbone.radio') + +describe 'CordovaSupport', -> + ['deviceready', 'pause', 'resume', 'backbutton', 'offline', 'online'].forEach (event) -> + it "publishes '#{event}' on the app channel", (done) -> + Radio.channel('app').on 'deviceready', done + $(document).trigger('deviceready') + + it 'adds a class on document.body indicating cordova platformId', -> + expect($(document.body)).to.have.class('platform-web') diff --git a/src/cordova_support.coffee b/src/cordova_support.coffee index 21d8668..4d52b8f 100644 --- a/src/cordova_support.coffee +++ b/src/cordova_support.coffee @@ -6,7 +6,7 @@ initCordova = -> $(document).on 'deviceready', -> require('./cordova/ios_network_activity').init() - for eventName in ['pause', 'resume', 'backbutton', 'offline', 'online'] + for eventName in ['deviceready', 'pause', 'resume', 'backbutton', 'offline', 'online'] $(document).on eventName, (e) -> Radio.channel('app').trigger(e.type) From b7f24a0f270a228363895a1718fe9b99a59c6417 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 11 Jan 2017 13:31:24 +0100 Subject: [PATCH 43/79] Fix forgot to make coffeescript :-) --- lib/cordova_support.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cordova_support.js b/lib/cordova_support.js index 37b720b..c94aedc 100644 --- a/lib/cordova_support.js +++ b/lib/cordova_support.js @@ -11,7 +11,7 @@ $(document).on('deviceready', function() { return require('./cordova/ios_network_activity').init(); }); - ref = ['pause', 'resume', 'backbutton', 'offline', 'online']; + ref = ['deviceready', 'pause', 'resume', 'backbutton', 'offline', 'online']; for (i = 0, len = ref.length; i < len; i++) { eventName = ref[i]; $(document).on(eventName, function(e) { From fb408a94a3f42b5c06ff447a9f40402fd5e8686b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 12 Jan 2017 09:11:26 +0100 Subject: [PATCH 44/79] Improve hamlc compile error reporting by switching to hamlc-loader --- project_template/package.json | 3 +-- project_template/webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/project_template/package.json b/project_template/package.json index 6aa423d..9d9f895 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -21,7 +21,7 @@ "cordova": "~6.4.0", "gulp-iconfont": "~6.0.0", "gulp-iconfont-css": "~2.0.0", - "haml-coffee-browserify": "~0.0.4", + "hamlc-loader": "kabisa/hamlc-loader#fbd17dda7c5610dd42a88403e1d175754975d70e", "json-loader": "^0.5.4", "karma": "~0.13.22", "karma-mocha": "~0.2.2", @@ -37,7 +37,6 @@ "postcss-cli": "~2.5.1", "sinon": "~1.17.2", "sinon-chai": "2.8.0", - "transform-loader": "^0.2.3", "vinyl-fs": "~2.4.2", "webpack": "^2.2.0-rc.3", "yaml-loader": "^0.4.0" diff --git a/project_template/webpack.config.js b/project_template/webpack.config.js index 4c5b318..24ec666 100644 --- a/project_template/webpack.config.js +++ b/project_template/webpack.config.js @@ -23,7 +23,7 @@ module.exports = { module: { rules: [ { test: /\.coffee$/, loader: 'coffee-loader' }, - { test: /\.hamlc$/, loader: 'transform-loader?haml-coffee-browserify' }, + { test: /\.hamlc$/, loader: 'hamlc-loader' }, { test: /\.json$/, loader: 'json-loader' }, { test: /\.yml$/, loader: 'json-loader!yaml-loader' }, ] From a5b61e33b6bbc3876905147a013bcf5d527f1f01 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Fri, 13 Jan 2017 08:10:06 +0100 Subject: [PATCH 45/79] Add cordova whitelist plugin Without this it's not possible to make remote requests. Before this functionality was part of Cordova core. --- project_template/cordova/config.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/project_template/cordova/config.xml b/project_template/cordova/config.xml index 998b2db..ce25ce3 100644 --- a/project_template/cordova/config.xml +++ b/project_template/cordova/config.xml @@ -30,4 +30,5 @@ + From 4cff6c8c0d87f77102a56ab2932b0a566463d908 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 16 Jan 2017 09:20:53 +0100 Subject: [PATCH 46/79] Add --cors option to maji-dev-server Adds simple, wide open cors config. Suitable for development only. --- src/maji-dev-server.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/maji-dev-server.js b/src/maji-dev-server.js index ec0afc4..19fa730 100755 --- a/src/maji-dev-server.js +++ b/src/maji-dev-server.js @@ -17,10 +17,12 @@ program .usage('[options] ') .option('-p, --port [port]', 'Port to listen on [9090]', parsePort, 9090) .option('-l, --livereload [flag]', 'Enable livereload [true]', parseBoolean, true) + .option('-c, --cors', 'Enable cors') .parse(process.argv); var port = program.port; var livereload = program.livereload; +var cors = program.cors; var assetPath = program.args[0]; if (! assetPath) { @@ -49,6 +51,14 @@ if(livereload) { })); } +if(cors) { + server.use(function(req, res, next) { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', req.get('Access-Control-Request-Headers') || '*'); + next(); + }); +} + server .use(express.static(assetPath, { etag: false, From 60aa8d77d82066f60a64b071f9c069ff2b75eba3 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 09:50:39 +0100 Subject: [PATCH 47/79] Pass literall args to cordova on `maji build/run` Fixes #104 Allows for example to specify the target ios emulator like so: `bin/maji run -e ios -- --target='iPhone 6'` (Note the additonal `--`) --- lib/cli.js | 7 ++++--- src/cli.coffee | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 9504c6b..aa13b38 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,7 +1,8 @@ #!/usr/bin/env node // Generated by CoffeeScript 1.9.3 (function() { - var executeScript, path, program, spawn; + var executeScript, path, program, spawn, + slice = [].slice; spawn = require('child_process').spawn; @@ -28,13 +29,13 @@ program.command('run ').description('build and run a native app for the specified platform').option('-e, --emulator', 'run on emulator instead of an actual device').action(function(platform, options) { var deviceTypeArg; deviceTypeArg = options.emulator ? '--emulator' : '--device'; - return executeScript('run-on-device', [platform, deviceTypeArg]); + return executeScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(program.args))); }); program.command('build ').description('build a native app for the specified platform').option('--release', 'create a release build').action(function(platform, options) { var releaseArg; releaseArg = options.release ? '--release' : '--debug'; - return executeScript('build-app', [platform, releaseArg]); + return executeScript('build-app', [platform, releaseArg].concat(slice.call(program.args))); }); program.command('new ').description('create a new Maji app').on('--help', function() { diff --git a/src/cli.coffee b/src/cli.coffee index d6c03cf..fb3b180 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -22,7 +22,7 @@ program .option('-e, --emulator', 'run on emulator instead of an actual device') .action (platform, options) -> deviceTypeArg = if options.emulator then '--emulator' else '--device' - executeScript('run-on-device', [platform, deviceTypeArg]) + executeScript('run-on-device', [platform, deviceTypeArg, program.args...]) program .command('build ') @@ -30,7 +30,7 @@ program .option('--release', 'create a release build') .action (platform, options) -> releaseArg = if options.release then '--release' else '--debug' - executeScript('build-app', [platform, releaseArg]) + executeScript('build-app', [platform, releaseArg, program.args...]) program .command('new ') From f6b61cd91539d1796db57579365b172bb98e9679 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 10:05:27 +0100 Subject: [PATCH 48/79] Upgrade cordova platforms to latest version --- project_template/cordova/config.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_template/cordova/config.xml b/project_template/cordova/config.xml index ce25ce3..b398ddf 100644 --- a/project_template/cordova/config.xml +++ b/project_template/cordova/config.xml @@ -7,8 +7,8 @@ - - + + From e1d23428df1f2960d142e0e83457c0b3c1f805f8 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 15:58:38 +0100 Subject: [PATCH 49/79] Remove reference to Browserify --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 8228f58..b11ef94 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ * Fast Node based build system * Compilation of Coffeescript and Sass * Javascript bundling and minification including source maps - * CommonJS module system using Browserify + * CommonJS module system using Webpack * CSS autoprefixer (no more vendor prefixes!) * Iconfont builder. Drop an SVG in your project and it's instantly available in your iconfont. [Read more](./icons.md). * Livereload integration. Make changes and your browser will automatically reload, works on mobile too. From 8ff3a3f3c06167ea27edb941a16578a8c529d8cc Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 14:09:10 +0100 Subject: [PATCH 50/79] Add convenience methods on maji CLI * `maji start` * `maji test` * `maji test --watch` --- lib/cli.js | 89 ++++++++++++++++++++++++++++------- project_template/package.json | 8 ++++ src/cli.coffee | 76 ++++++++++++++++++++++++------ 3 files changed, 140 insertions(+), 33 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index aa13b38..17b85c0 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,51 +1,104 @@ #!/usr/bin/env node // Generated by CoffeeScript 1.9.3 (function() { - var executeScript, path, program, spawn, + var executeScript, parseBoolean, parsePort, path, program, runCmd, runNpm, spawn, slice = [].slice; spawn = require('child_process').spawn; path = require('path'); + parseBoolean = function(value) { + return value === 'true'; + }; + + parsePort = function(value) { + return parseInt(value) || null; + }; + program = require('commander'); program.version('1.0.0'); - executeScript = function(scriptName, args) { - var child; - child = spawn(path.resolve(__dirname + ("/../script/" + scriptName)), args); - child.stdout.on('data', function(data) { - return process.stdout.write(data); - }); - child.stderr.on('data', function(data) { - return process.stderr.write(data.toString()); + runNpm = function(args, env_args) { + if (env_args == null) { + env_args = {}; + } + return runCmd('npm', slice.call(args).concat(['--silent']), env_args); + }; + + runCmd = function(cmd, args, env_args) { + var child, env, ref, ref1; + if (env_args == null) { + env_args = {}; + } + env = Object.create(process.env); + Object.assign(env, env_args); + child = spawn(cmd, args, { + env: env, + stdio: 'inherit' }); + if ((ref = child.stdout) != null) { + ref.on('data', function(data) { + return process.stdout.write(data); + }); + } + if ((ref1 = child.stderr) != null) { + ref1.on('data', function(data) { + return process.stderr.write(data.toString()); + }); + } return child.on('exit', function(exitCode) { return process.exit(exitCode); }); }; - program.command('run ').description('build and run a native app for the specified platform').option('-e, --emulator', 'run on emulator instead of an actual device').action(function(platform, options) { + executeScript = function(scriptName, args) { + return runCmd(path.resolve(__dirname + ("/../script/" + scriptName)), args); + }; + + program.command('new ').description('Create a new Maji app').on('--help', function() { + return console.log(' Example:\n maji new org.example.my-app ~/Code/my-app'); + }).action(function(packageName, path) { + if (!packageName.match(/.*\..*\..*/)) { + console.log('Please specify a valid package name, for example org.example.my-app'); + process.exit(1); + } + return executeScript('create-project', [packageName, path]); + }); + + program.command('run ').description('Build and run a native app for the specified platform').option('-e, --emulator', 'run on emulator instead of an actual device').action(function(platform, options) { var deviceTypeArg; deviceTypeArg = options.emulator ? '--emulator' : '--device'; return executeScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(program.args))); }); - program.command('build ').description('build a native app for the specified platform').option('--release', 'create a release build').action(function(platform, options) { + program.command('build ').description('Build a native app for the specified platform').option('--release', 'create a release build').action(function(platform, options) { var releaseArg; releaseArg = options.release ? '--release' : '--debug'; return executeScript('build-app', [platform, releaseArg].concat(slice.call(program.args))); }); - program.command('new ').description('create a new Maji app').on('--help', function() { - return console.log(' Example:\n maji new org.example.my-app ~/Code/my-app'); - }).action(function(packageName, path) { - if (!packageName.match(/.*\..*\..*/)) { - console.log('Please specify a valid package name, for example org.example.my-app'); - process.exit(1); + program.command('test').option('--watch', 'Run tests when project files change').option('--unit', 'Run unit tests').option('--integration', 'Run integration tests').description('Run your project tests').action(function(options) { + if (options.watch) { + return runNpm(['run', 'test:watch']); } - return executeScript('create-project', [packageName, path]); + if (options.unit) { + return runNpm(['run', 'test:unit']); + } + if (options.features) { + return runNpm(['run', 'test:integration']); + } + return runNpm(['test']); + }); + + program.command('start').description('Run the maji dev server and compile changes on the fly').option('-p --port [port]', 'Port to listen on [9090]', parsePort, 9090).option('-l --livereload [flag]', 'Enable livereload [true]', parseBoolean, false).action(function(options) { + var env; + env = { + 'SERVER_PORT': options.port, + 'LIVERELOAD': options.livereload + }; + return runCmd('npm', ['start'], env); }); program.on('--help', function() { diff --git a/project_template/package.json b/project_template/package.json index 9d9f895..b466566 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -2,6 +2,14 @@ "name": "example", "version": "1.0.0", "private": true, + "scripts": { + "test": "bin/ci", + "test:unit": "APP_ENV=test karma start --single-run", + "test:features": "APP_ENV=test PRE_BUILT=true bundle exec rspec", + "test:watch": "APP_ENV=test karma start --watch", + "start": "make watch", + "build": "make dist" + }, "dependencies": { "backbone": "1.3.3", "backbone.localstorage": "~1.1.16", diff --git a/src/cli.coffee b/src/cli.coffee index fb3b180..68401d5 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -1,24 +1,50 @@ spawn = require('child_process').spawn path = require('path') +parseBoolean = (value) -> + value == 'true' + +parsePort = (value) -> + parseInt(value) || null + program = require('commander') program.version('1.0.0') -executeScript = (scriptName, args) -> - child = spawn(path.resolve(__dirname + "/../script/#{scriptName}"), args) +runNpm = (args, env_args = {}) -> + runCmd('npm', [args..., '--silent'], env_args) + +runCmd = (cmd, args, env_args = {}) -> + env = Object.create(process.env) + Object.assign(env, env_args) - child.stdout.on 'data', (data) -> + child = spawn(cmd, args, { env: env, stdio: 'inherit' }) + + child.stdout?.on 'data', (data) -> process.stdout.write(data) - child.stderr.on 'data', (data) -> + child.stderr?.on 'data', (data) -> process.stderr.write(data.toString()) child.on 'exit', (exitCode) -> process.exit(exitCode) +executeScript = (scriptName, args) -> + runCmd(path.resolve(__dirname + "/../script/#{scriptName}"), args) + +program + .command('new ') + .description('Create a new Maji app') + .on '--help', -> + console.log ' Example:\n maji new org.example.my-app ~/Code/my-app' + .action (packageName, path) -> + if ! packageName.match /.*\..*\..*/ + console.log 'Please specify a valid package name, for example org.example.my-app' + process.exit(1) + executeScript('create-project', [packageName, path]) + program .command('run ') - .description('build and run a native app for the specified platform') + .description('Build and run a native app for the specified platform') .option('-e, --emulator', 'run on emulator instead of an actual device') .action (platform, options) -> deviceTypeArg = if options.emulator then '--emulator' else '--device' @@ -26,22 +52,42 @@ program program .command('build ') - .description('build a native app for the specified platform') + .description('Build a native app for the specified platform') .option('--release', 'create a release build') .action (platform, options) -> releaseArg = if options.release then '--release' else '--debug' executeScript('build-app', [platform, releaseArg, program.args...]) program - .command('new ') - .description('create a new Maji app') - .on '--help', -> - console.log ' Example:\n maji new org.example.my-app ~/Code/my-app' - .action (packageName, path) -> - if ! packageName.match /.*\..*\..*/ - console.log 'Please specify a valid package name, for example org.example.my-app' - process.exit(1) - executeScript('create-project', [packageName, path]) + .command('test') + .option('--watch', 'Run tests when project files change') + .option('--unit', 'Run unit tests') + .option('--integration', 'Run integration tests') + .description('Run your project tests') + .action (options) -> + if options.watch + return runNpm(['run', 'test:watch']) + + if options.unit + return runNpm(['run', 'test:unit']) + + if options.features + return runNpm(['run', 'test:integration']) + + runNpm(['test']) + +program + .command('start') + .description('Run the maji dev server and compile changes on the fly') + .option('-p --port [port]', 'Port to listen on [9090]', parsePort, 9090) + .option('-l --livereload [flag]', 'Enable livereload [true]', parseBoolean, false) + .action (options) -> + env = { + 'SERVER_PORT': options.port, + 'LIVERELOAD': options.livereload + } + + runCmd('npm', ['start'], env) program.on '--help', -> process.exit(1) From 524fefc3779a71d87c2a798a7dbf874f4dafd649 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 14:14:48 +0100 Subject: [PATCH 51/79] Document new Maji CLI commands --- docs/upgrade_guide.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index fab676c..d45e2f6 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -154,5 +154,31 @@ With Maji 2.x new projects use webpack for module bundling. While not required i With these steps your app should become functional again, depending on how much custom stuff your app uses (like overwriting private implementations of Marionette). +## Maji CLI + +Maji 2.x adds several new subcommands to the Maji CLI, streamlining the development workflow to have a single way to interact with your project: + +* `maji start` - starts the development server +* `maji build` - build static assets +* `maji test` - runs your projects tests once +* `maji test --unit` - runs your unit test once +* `maji test --integration` - runs your integration test once +* `maji test --watch` - runs your tests whenever files are changed + +All these commands delegate to NPM run scripts. When upgrading from Maji 1.x to Maji 2.x you can add the following snippet to your `package.json` to add support for these commands: + +```json +"scripts": { + "test": "bin/ci", + "test:unit": "APP_ENV=test karma start --single-run", + "test:features": "APP_ENV=test PRE_BUILT=true bundle exec rspec", + "test:watch": "APP_ENV=test karma start --watch", + "start": "make watch", + "build": "make dist" +} +``` + +Should you choose the use a different test runner for example then the only thing that needs to change are the NPM scripts defined in your `package.json` and everything will continue to work. + [marionette-upgrade]: http://marionettejs.com/docs/v3.1.0/upgrade.html -[backbone-radio]: https://github.com/marionettejs/backbone.radio \ No newline at end of file +[backbone-radio]: https://github.com/marionettejs/backbone.radio From da77ed6f8173cb81238541b1c2a08b66361c823c Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 15:27:26 +0100 Subject: [PATCH 52/79] Update README --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9def3fa..049901f 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,11 @@ Your new Maji app will now be generated at the supplied path. Commands: - run [options] build and run a native app for the specified platform - build [options] build a native app for the specified platform - new create a new Maji app + new Create a new Maji app + run [options] Build and run a native app for the specified platform + build [options] [platform] Build a native app for the specified platform + test [options] Run your project tests + start [options] Run the maji dev server and compile changes on the fly Options: @@ -53,15 +55,15 @@ Your new Maji app will now be generated at the supplied path. ### Starting in the browser -To start your app, `cd` into its directory, execute `make watch` and navigate to http://localhost:9090/ with your browser. +To start your app, `cd` into its directory, execute `bin/maji start` and navigate to http://localhost:9090/ with your browser. ### Running tests To run test, you have several options: -* To run JavaScript tests run `bin/karma start`. This will start a Karma server with Phantomjs headless browser and will continuously watch your Javascript files and run tests on changes. -* To run JavaScript tests once, run `bin/karma start --single-run`. -* To run features specs once, run `bundle exec rspec`. -* To run all tests once, run `bin/ci`. +* To run JavaScript tests run `bin/maji test --watch`. This will start a Karma server with Phantomjs headless browser and will continuously watch your Javascript files and run tests on changes. +* To run JavaScript tests once, run `bin/maji test --unit`. +* To run features specs once, run `bin/maji test --integration`. +* To run all tests once, run `bin/maji test`. ### Creating builds From a5227e40a24dbb9d812dcf9fd99e6ddd9b8bdc13 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 15:41:20 +0100 Subject: [PATCH 53/79] Build static assets if no platform is passed to `maji build` --- lib/cli.js | 10 +++++++--- src/cli.coffee | 9 ++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 17b85c0..990e43a 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -73,10 +73,14 @@ return executeScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(program.args))); }); - program.command('build ').description('Build a native app for the specified platform').option('--release', 'create a release build').action(function(platform, options) { + program.command('build [platform]').description('Build a native app for the specified platform').option('--release', 'create a release build').action(function(platform, options) { var releaseArg; - releaseArg = options.release ? '--release' : '--debug'; - return executeScript('build-app', [platform, releaseArg].concat(slice.call(program.args))); + if (platform) { + releaseArg = options.release ? '--release' : '--debug'; + return executeScript('build-app', [platform, releaseArg].concat(slice.call(program.args))); + } else { + return runNpm(['run', 'build']); + } }); program.command('test').option('--watch', 'Run tests when project files change').option('--unit', 'Run unit tests').option('--integration', 'Run integration tests').description('Run your project tests').action(function(options) { diff --git a/src/cli.coffee b/src/cli.coffee index 68401d5..6fc0e36 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -51,12 +51,15 @@ program executeScript('run-on-device', [platform, deviceTypeArg, program.args...]) program - .command('build ') + .command('build [platform]') .description('Build a native app for the specified platform') .option('--release', 'create a release build') .action (platform, options) -> - releaseArg = if options.release then '--release' else '--debug' - executeScript('build-app', [platform, releaseArg, program.args...]) + if platform + releaseArg = if options.release then '--release' else '--debug' + executeScript('build-app', [platform, releaseArg, program.args...]) + else + runNpm(['run', 'build']) program .command('test') From ee2bac53881e5e35e0442454b9892aef5bcdb290 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 15:41:29 +0100 Subject: [PATCH 54/79] Only reinstall cordova plugins on release builds No need to reinstall plugins all the time --- script/build-app | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/script/build-app b/script/build-app index f0e28d5..9c292c2 100755 --- a/script/build-app +++ b/script/build-app @@ -4,8 +4,10 @@ export APP_ENV=$APP_ENV export PATH=$PATH:$(npm bin) export APP_PATH=$(pwd) -# force reinstall all cordova plugins, in case those are updated -cd cordova && cordova plugins ls | cut -d ' ' -f 1 | xargs cordova plugin rm +if [[ $* == *--release* ]]; then + # force reinstall all cordova plugins, in case those are updated + cd cordova && cordova plugins ls | cut -d ' ' -f 1 | xargs cordova plugin rm +fi echo "Building with environment: $APP_ENV" cd $APP_PATH && $(dirname ${BASH_SOURCE[0]})/cordova-exec build "$@" From aad7e63abf2c9ce0c731d38ef8fe5206890bde3a Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 15:49:41 +0100 Subject: [PATCH 55/79] Allow to pass APP_ENV via --env parameter Default to 'development' for run, and 'production' for build --- lib/cli.js | 35 +++++++++++++++++++++++------------ src/cli.coffee | 26 +++++++++++++++++++------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 990e43a..79cb449 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,7 +1,7 @@ #!/usr/bin/env node // Generated by CoffeeScript 1.9.3 (function() { - var executeScript, parseBoolean, parsePort, path, program, runCmd, runNpm, spawn, + var parseBoolean, parsePort, path, program, runCmd, runNpm, runScript, spawn, slice = [].slice; spawn = require('child_process').spawn; @@ -53,8 +53,11 @@ }); }; - executeScript = function(scriptName, args) { - return runCmd(path.resolve(__dirname + ("/../script/" + scriptName)), args); + runScript = function(scriptName, args, env_args) { + if (env_args == null) { + env_args = {}; + } + return runCmd(path.resolve(__dirname + ("/../script/" + scriptName)), args, env_args); }; program.command('new ').description('Create a new Maji app').on('--help', function() { @@ -64,22 +67,30 @@ console.log('Please specify a valid package name, for example org.example.my-app'); process.exit(1); } - return executeScript('create-project', [packageName, path]); + return runScript('create-project', [packageName, path]); }); - program.command('run ').description('Build and run a native app for the specified platform').option('-e, --emulator', 'run on emulator instead of an actual device').action(function(platform, options) { - var deviceTypeArg; + program.command('run ').description('Build and run a native app for the specified platform').option('-e, --emulator', 'run on emulator instead of an actual device').option('--env --environment [environment]', 'APP_ENV to run with [development]').action(function(platform, options) { + var app_env, deviceTypeArg, env; + app_env = options.environment || 'development'; + env = { + 'APP_ENV': app_env + }; deviceTypeArg = options.emulator ? '--emulator' : '--device'; - return executeScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(program.args))); + return runScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(program.args)), env); }); - program.command('build [platform]').description('Build a native app for the specified platform').option('--release', 'create a release build').action(function(platform, options) { - var releaseArg; + program.command('build [platform]').description('Build a native app for the specified platform').option('--release', 'create a release build').option('--env --environment [environment]', 'APP_ENV to build with [production]').action(function(platform, options) { + var app_env, env, releaseArg; + app_env = options.environment || 'production'; + env = { + 'APP_ENV': app_env + }; if (platform) { releaseArg = options.release ? '--release' : '--debug'; - return executeScript('build-app', [platform, releaseArg].concat(slice.call(program.args))); + return runScript('build-app', [platform, releaseArg].concat(slice.call(program.args)), env); } else { - return runNpm(['run', 'build']); + return runNpm(['run', 'build'], env); } }); @@ -102,7 +113,7 @@ 'SERVER_PORT': options.port, 'LIVERELOAD': options.livereload }; - return runCmd('npm', ['start'], env); + return runNpm(['start'], env); }); program.on('--help', function() { diff --git a/src/cli.coffee b/src/cli.coffee index 6fc0e36..043e56c 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -28,8 +28,8 @@ runCmd = (cmd, args, env_args = {}) -> child.on 'exit', (exitCode) -> process.exit(exitCode) -executeScript = (scriptName, args) -> - runCmd(path.resolve(__dirname + "/../script/#{scriptName}"), args) +runScript = (scriptName, args, env_args = {}) -> + runCmd(path.resolve(__dirname + "/../script/#{scriptName}"), args, env_args) program .command('new ') @@ -40,26 +40,38 @@ program if ! packageName.match /.*\..*\..*/ console.log 'Please specify a valid package name, for example org.example.my-app' process.exit(1) - executeScript('create-project', [packageName, path]) + runScript('create-project', [packageName, path]) program .command('run ') .description('Build and run a native app for the specified platform') .option('-e, --emulator', 'run on emulator instead of an actual device') + .option('--env --environment [environment]', 'APP_ENV to run with [development]') .action (platform, options) -> + app_env = options.environment || 'development' + env = { + 'APP_ENV': app_env + } + deviceTypeArg = if options.emulator then '--emulator' else '--device' - executeScript('run-on-device', [platform, deviceTypeArg, program.args...]) + runScript('run-on-device', [platform, deviceTypeArg, program.args...], env) program .command('build [platform]') .description('Build a native app for the specified platform') .option('--release', 'create a release build') + .option('--env --environment [environment]', 'APP_ENV to build with [production]') .action (platform, options) -> + app_env = options.environment || 'production' + env = { + 'APP_ENV': app_env + } + if platform releaseArg = if options.release then '--release' else '--debug' - executeScript('build-app', [platform, releaseArg, program.args...]) + runScript('build-app', [platform, releaseArg, program.args...], env) else - runNpm(['run', 'build']) + runNpm(['run', 'build'], env) program .command('test') @@ -90,7 +102,7 @@ program 'LIVERELOAD': options.livereload } - runCmd('npm', ['start'], env) + runNpm(['start'], env) program.on '--help', -> process.exit(1) From 5581c390dfc6848a58253f2462edee6f1ef548b6 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:20:06 +0100 Subject: [PATCH 56/79] Fix test:features -> test:integration --- docs/upgrade_guide.md | 2 +- lib/cli.js | 2 +- project_template/package.json | 2 +- src/cli.coffee | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index d45e2f6..5171f8e 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -171,7 +171,7 @@ All these commands delegate to NPM run scripts. When upgrading from Maji 1.x to "scripts": { "test": "bin/ci", "test:unit": "APP_ENV=test karma start --single-run", - "test:features": "APP_ENV=test PRE_BUILT=true bundle exec rspec", + "test:integration": "APP_ENV=test PRE_BUILT=true bundle exec rspec", "test:watch": "APP_ENV=test karma start --watch", "start": "make watch", "build": "make dist" diff --git a/lib/cli.js b/lib/cli.js index 79cb449..6f4284e 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -101,7 +101,7 @@ if (options.unit) { return runNpm(['run', 'test:unit']); } - if (options.features) { + if (options.integration) { return runNpm(['run', 'test:integration']); } return runNpm(['test']); diff --git a/project_template/package.json b/project_template/package.json index b466566..baa5a3f 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -5,7 +5,7 @@ "scripts": { "test": "bin/ci", "test:unit": "APP_ENV=test karma start --single-run", - "test:features": "APP_ENV=test PRE_BUILT=true bundle exec rspec", + "test:integration": "APP_ENV=test PRE_BUILT=true bundle exec rspec", "test:watch": "APP_ENV=test karma start --watch", "start": "make watch", "build": "make dist" diff --git a/src/cli.coffee b/src/cli.coffee index 043e56c..c6f4b9a 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -86,7 +86,7 @@ program if options.unit return runNpm(['run', 'test:unit']) - if options.features + if options.integration return runNpm(['run', 'test:integration']) runNpm(['test']) From 39e199313a1103bd88bc824db68d0cd50376a60f Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:24:39 +0100 Subject: [PATCH 57/79] Update project template README with maji CLI --- project_template/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/project_template/README.md b/project_template/README.md index 852dea8..920e0c4 100644 --- a/project_template/README.md +++ b/project_template/README.md @@ -8,13 +8,13 @@ ## Development workflow -* While developing you can run a local serve using `make watch`. This will start a server on http://localhost:9090. -* To create a static HTML5 app build run `make dist`. The app will be build into the `dist/` directory. +* While developing you can run a local serve using `bin/maji start`. This will start a server on http://localhost:9090. +* To create a static HTML5 app build run `bin/maji build`. The app will be build into the `dist/` directory. * To run the app on a connected mobile device run `bin/maji run `. * To build a Cordova app run `bin/maji build `. -* To run Javascript tests run `bin/karma start`. This will start a Karma server with Phantomjs and will continuously watch your Javascript files and run tests on changes. -* To run features specs run `bundle exec rspec`. -* To run all tests run `bin/ci`. +* To run Javascript tests run `bin/maji test --watch`. This will start a Karma server with Phantomjs and will continuously watch your Javascript files and run tests on changes. +* To run integration specs run `bin/maji test --integration`. +* To run all tests run `bin/maji test`. ## Packaging native apps / running on your device From 1fedc3103c6e47e5914f81656d80e403ba071f79 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:24:56 +0100 Subject: [PATCH 58/79] Replace `make dist` with `maji build` in tooling --- project_template/spec/spec_helper.rb | 2 +- script/cordova-exec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project_template/spec/spec_helper.rb b/project_template/spec/spec_helper.rb index 965b882..9202633 100644 --- a/project_template/spec/spec_helper.rb +++ b/project_template/spec/spec_helper.rb @@ -21,7 +21,7 @@ if ENV['PRE_BUILT'] # Make sure our app is built - system('make dist') + system('bin/maji build') Capybara.app = Rack::File.new('dist/') else Capybara.app_host = 'http://localhost:9090/' diff --git a/script/cordova-exec b/script/cordova-exec index c434f2f..a5b1ff5 100755 --- a/script/cordova-exec +++ b/script/cordova-exec @@ -8,7 +8,7 @@ source "$(dirname ${BASH_SOURCE[0]})/_functions" PLATFORM=$2 # build all assets -make dist +bin/maji build export PATH=$(npm bin):$PATH $(dirname ${BASH_SOURCE[0]})/prepare-cordova-platform "${@:2}" From a9e0f20df640f5afb4ff29e882e09149a505f040 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:25:15 +0100 Subject: [PATCH 59/79] Remove karma binstub Karma should now be ran using `maji test --unit` or `maji test --watch` --- project_template/bin/karma | 3 --- 1 file changed, 3 deletions(-) delete mode 100755 project_template/bin/karma diff --git a/project_template/bin/karma b/project_template/bin/karma deleted file mode 100755 index 32b9b67..0000000 --- a/project_template/bin/karma +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -export APP_ENV=test -./node_modules/karma/bin/karma "$@" From 67ef70a2fb99e49860b349674bd482a0c8ef13ca Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:27:54 +0100 Subject: [PATCH 60/79] Fix livereload CLI document --- lib/cli.js | 2 +- src/cli.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 6f4284e..c30814b 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -107,7 +107,7 @@ return runNpm(['test']); }); - program.command('start').description('Run the maji dev server and compile changes on the fly').option('-p --port [port]', 'Port to listen on [9090]', parsePort, 9090).option('-l --livereload [flag]', 'Enable livereload [true]', parseBoolean, false).action(function(options) { + program.command('start').description('Run the maji dev server and compile changes on the fly').option('-p --port [port]', 'Port to listen on [9090]', parsePort, 9090).option('-l --livereload [flag]', 'Enable livereload [false]', parseBoolean, false).action(function(options) { var env; env = { 'SERVER_PORT': options.port, diff --git a/src/cli.coffee b/src/cli.coffee index c6f4b9a..06b4b1f 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -95,7 +95,7 @@ program .command('start') .description('Run the maji dev server and compile changes on the fly') .option('-p --port [port]', 'Port to listen on [9090]', parsePort, 9090) - .option('-l --livereload [flag]', 'Enable livereload [true]', parseBoolean, false) + .option('-l --livereload [flag]', 'Enable livereload [false]', parseBoolean, false) .action (options) -> env = { 'SERVER_PORT': options.port, From 02a3ec8f9f4a61a54b9ebde796653407560bb311 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:31:29 +0100 Subject: [PATCH 61/79] Use npm-link to test project template against current branch --- bin/test-project-template | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/test-project-template b/bin/test-project-template index c29baf0..bd5a4f4 100755 --- a/bin/test-project-template +++ b/bin/test-project-template @@ -2,9 +2,11 @@ set -e TMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t maji-tests) +npm link lib/cli.js new nl.kabisa.test-app $TMP_DIR pushd $TMP_DIR bin/setup + npm link maji bin/ci popd From 4d2ec03795c49687eaf6d5a180bc6a10922434ad Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 18 Jan 2017 16:35:40 +0100 Subject: [PATCH 62/79] Use npm cpmmands in bin/ci --- project_template/bin/ci | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_template/bin/ci b/project_template/bin/ci index 918e467..4add609 100755 --- a/project_template/bin/ci +++ b/project_template/bin/ci @@ -3,8 +3,8 @@ set -e export APP_ENV=test -PRE_BUILT=true bundle exec rspec -./node_modules/karma/bin/karma start --singleRun +npm run test:unit +npm run test:integration find app/ \ -type f -name '*.coffee' \ From 45fd074af3b48498c655e5e5fdf1db2145927236 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 19 Jan 2017 08:49:46 +0100 Subject: [PATCH 63/79] Fix bugsnag warnings in development With browserify bugsnag-js was excluded in development builds. With the switch to webpack this was no longer the case, unintentionally. Excluding bugsnag from the development bundle seems a bit overkill, so instead with this commit we simply don't initialize bugsnag unless a bugsnag API key is included. --- project_template/app/config/bugsnag.coffee | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/project_template/app/config/bugsnag.coffee b/project_template/app/config/bugsnag.coffee index ebc3034..00ab229 100644 --- a/project_template/app/config/bugsnag.coffee +++ b/project_template/app/config/bugsnag.coffee @@ -1,7 +1,5 @@ Settings = require('./settings') -try +if Settings.bugsnagApiKey Bugsnag = require('bugsnag-js') - Bugsnag.apiKey = Settings.bugsnagApiKey if Settings.bugsnagApiKey -catch - # ignore, bugsnag may not be included + Bugsnag.apiKey = Settings.bugsnagApiKey From e18fc68bbf67b89b8e6ac4412117e19d0d7c69c4 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 19 Jan 2017 10:57:37 +0100 Subject: [PATCH 64/79] Fix literal args parsing Unfortunately commander.js has some funky behaviour, where program.args only returns an array in some cases which makes it unusable for retrieving the literal args. --- lib/cli.js | 14 +++++++++++--- src/cli.coffee | 12 ++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index c30814b..a3e4844 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,7 +1,7 @@ #!/usr/bin/env node // Generated by CoffeeScript 1.9.3 (function() { - var parseBoolean, parsePort, path, program, runCmd, runNpm, runScript, spawn, + var literalArgs, parseBoolean, parsePort, path, program, runCmd, runNpm, runScript, spawn, slice = [].slice; spawn = require('child_process').spawn; @@ -60,6 +60,14 @@ return runCmd(path.resolve(__dirname + ("/../script/" + scriptName)), args, env_args); }; + literalArgs = function() { + if (program.rawArgs.indexOf('--') === -1) { + return []; + } else { + return program.rawArgs.slice(program.rawArgs.indexOf('--') + 1); + } + }; + program.command('new ').description('Create a new Maji app').on('--help', function() { return console.log(' Example:\n maji new org.example.my-app ~/Code/my-app'); }).action(function(packageName, path) { @@ -77,7 +85,7 @@ 'APP_ENV': app_env }; deviceTypeArg = options.emulator ? '--emulator' : '--device'; - return runScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(program.args)), env); + return runScript('run-on-device', [platform, deviceTypeArg].concat(slice.call(literalArgs())), env); }); program.command('build [platform]').description('Build a native app for the specified platform').option('--release', 'create a release build').option('--env --environment [environment]', 'APP_ENV to build with [production]').action(function(platform, options) { @@ -88,7 +96,7 @@ }; if (platform) { releaseArg = options.release ? '--release' : '--debug'; - return runScript('build-app', [platform, releaseArg].concat(slice.call(program.args)), env); + return runScript('build-app', [platform, releaseArg].concat(slice.call(literalArgs())), env); } else { return runNpm(['run', 'build'], env); } diff --git a/src/cli.coffee b/src/cli.coffee index 06b4b1f..6c3e53f 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -31,6 +31,14 @@ runCmd = (cmd, args, env_args = {}) -> runScript = (scriptName, args, env_args = {}) -> runCmd(path.resolve(__dirname + "/../script/#{scriptName}"), args, env_args) +literalArgs = -> + # commander.js program.args is broken for this purpose + # https://github.com/tj/commander.js/issues/582 + if program.rawArgs.indexOf('--') == -1 + [] + else + program.rawArgs.slice(program.rawArgs.indexOf('--') + 1) + program .command('new ') .description('Create a new Maji app') @@ -54,7 +62,7 @@ program } deviceTypeArg = if options.emulator then '--emulator' else '--device' - runScript('run-on-device', [platform, deviceTypeArg, program.args...], env) + runScript('run-on-device', [platform, deviceTypeArg, literalArgs()...], env) program .command('build [platform]') @@ -69,7 +77,7 @@ program if platform releaseArg = if options.release then '--release' else '--debug' - runScript('build-app', [platform, releaseArg, program.args...], env) + runScript('build-app', [platform, releaseArg, literalArgs()...], env) else runNpm(['run', 'build'], env) From 1e8164de4419f108b88fddc56e7d0191dc11d044 Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Fri, 20 Jan 2017 13:29:50 +0100 Subject: [PATCH 65/79] return a hardcode new version variable --- src/cli.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.coffee b/src/cli.coffee index 6c3e53f..e7c94f9 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -8,7 +8,7 @@ parsePort = (value) -> parseInt(value) || null program = require('commander') -program.version('1.0.0') +program.version('2.0.0') runNpm = (args, env_args = {}) -> runCmd('npm', [args..., '--silent'], env_args) From cd3ff3fa49fad966a9d0a4def166b271773505a8 Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Fri, 20 Jan 2017 14:52:51 +0100 Subject: [PATCH 66/79] Load the version from our package.json instead of a hardcoded value --- lib/cli.js | 6 ++++-- src/cli.coffee | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index a3e4844..da53a5d 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,13 +1,15 @@ #!/usr/bin/env node // Generated by CoffeeScript 1.9.3 (function() { - var literalArgs, parseBoolean, parsePort, path, program, runCmd, runNpm, runScript, spawn, + var literalArgs, maji_package, parseBoolean, parsePort, path, program, runCmd, runNpm, runScript, spawn, slice = [].slice; spawn = require('child_process').spawn; path = require('path'); + maji_package = require('../package.json'); + parseBoolean = function(value) { return value === 'true'; }; @@ -18,7 +20,7 @@ program = require('commander'); - program.version('1.0.0'); + program.version(maji_package.version); runNpm = function(args, env_args) { if (env_args == null) { diff --git a/src/cli.coffee b/src/cli.coffee index e7c94f9..a4031b0 100644 --- a/src/cli.coffee +++ b/src/cli.coffee @@ -1,5 +1,6 @@ spawn = require('child_process').spawn path = require('path') +maji_package = require('../package.json') parseBoolean = (value) -> value == 'true' @@ -8,7 +9,7 @@ parsePort = (value) -> parseInt(value) || null program = require('commander') -program.version('2.0.0') +program.version(maji_package.version) runNpm = (args, env_args = {}) -> runCmd('npm', [args..., '--silent'], env_args) From dfa5785b42cb43032097d53b3fab8c3f053b79fd Mon Sep 17 00:00:00 2001 From: Roel Nieskens Date: Fri, 20 Jan 2017 16:38:42 +0100 Subject: [PATCH 67/79] Use CSS directory structure and flexbox-based CSS for example app --- .../app/modules/home/home_app.coffee | 1 - .../app/modules/home/templates/detail.hamlc | 11 +- .../app/modules/home/templates/index.hamlc | 28 +-- .../app/modules/home/views/index_page.coffee | 7 +- project_template/app/styles/_forms.scss | 41 ---- project_template/app/styles/application.scss | 178 ++++++------------ .../app/styles/components/_content.scss | 14 ++ .../app/styles/components/_header.scss | 50 +++++ .../styles/components/_navigation-list.scss | 33 ++++ .../app/styles/components/_page.scss | 12 ++ .../app/styles/elements/_base.scss | 52 +++++ .../app/styles/generic/_box-sizing.scss | 7 + .../app/styles/generic/_reset.scss | 48 +++++ project_template/app/styles/icons/back.svg | 9 +- project_template/app/styles/icons/forward.svg | 1 + .../app/styles/objects/_media-object.scss | 26 +++ .../app/styles/platforms/ios.scss | 19 -- .../app/styles/settings/_colors.scss | 49 +++++ .../app/styles/settings/_general.scss | 15 ++ .../app/styles/settings/_typography.scss | 15 ++ .../app/styles/tools/_mixins.scss | 0 project_template/app/styles/variables.scss | 13 -- 22 files changed, 403 insertions(+), 226 deletions(-) delete mode 100644 project_template/app/styles/_forms.scss create mode 100644 project_template/app/styles/components/_content.scss create mode 100644 project_template/app/styles/components/_header.scss create mode 100644 project_template/app/styles/components/_navigation-list.scss create mode 100644 project_template/app/styles/components/_page.scss create mode 100644 project_template/app/styles/elements/_base.scss create mode 100644 project_template/app/styles/generic/_box-sizing.scss create mode 100644 project_template/app/styles/generic/_reset.scss create mode 100644 project_template/app/styles/icons/forward.svg create mode 100644 project_template/app/styles/objects/_media-object.scss delete mode 100644 project_template/app/styles/platforms/ios.scss create mode 100644 project_template/app/styles/settings/_colors.scss create mode 100644 project_template/app/styles/settings/_general.scss create mode 100644 project_template/app/styles/settings/_typography.scss create mode 100644 project_template/app/styles/tools/_mixins.scss delete mode 100644 project_template/app/styles/variables.scss diff --git a/project_template/app/modules/home/home_app.coffee b/project_template/app/modules/home/home_app.coffee index 8432c61..c019e02 100644 --- a/project_template/app/modules/home/home_app.coffee +++ b/project_template/app/modules/home/home_app.coffee @@ -16,4 +16,3 @@ class HomeRouter extends Marionette.AppRouter app.on 'start', -> new HomeRouter - diff --git a/project_template/app/modules/home/templates/detail.hamlc b/project_template/app/modules/home/templates/detail.hamlc index 963f838..1a66aac 100644 --- a/project_template/app/modules/home/templates/detail.hamlc +++ b/project_template/app/modules/home/templates/detail.hamlc @@ -1,5 +1,8 @@ -.header - %a{href:'#/', class:'btn left', 'data-rel': 'back'} - = 'Detail' +%header.app-header + %a.app-header-back{href:'#/', 'data-rel': 'back'} + %h1= 'Detail' -.body + +%main.app-content + %p + This is the detail page \ No newline at end of file diff --git a/project_template/app/modules/home/templates/index.hamlc b/project_template/app/modules/home/templates/index.hamlc index 388c080..a8796f7 100644 --- a/project_template/app/modules/home/templates/index.hamlc +++ b/project_template/app/modules/home/templates/index.hamlc @@ -1,15 +1,19 @@ -.header= '##APP_NAME##' +%header.app-header + %h1= '##APP_NAME##' -.body - %p.welcome{style: 'margin-top: 0'}= @t('hello') +%main.app-content + %p= @t('hello') - %p - %strong Some example transitions: + %p + %strong Some example transitions: - %ul.listview - %li - %a{href: '#/detail', 'data-transition': 'slide'}= 'Slide' - %li - %a{href: '#/detail', 'data-transition': 'slideup'}= 'Slide up' - %li - %a{href: '#/detail', 'data-transition': 'flip'}= 'Flip' + %ul.navigation-list + %li + %a{href: '#/detail', 'data-transition': 'slide'} + %span= 'Slide' + %li + %a{href: '#/detail', 'data-transition': 'slideup'} + %span= 'Slide up' + %li + %a{href: '#/detail', 'data-transition': 'flip'} + %span= 'Flip' diff --git a/project_template/app/modules/home/views/index_page.coffee b/project_template/app/modules/home/views/index_page.coffee index bd449e6..f7c050b 100644 --- a/project_template/app/modules/home/views/index_page.coffee +++ b/project_template/app/modules/home/views/index_page.coffee @@ -6,10 +6,9 @@ class IndexPage extends ApplicationPage template: template events: - 'click': (e) -> - e.preventDefault() - target = $(e.target) - + 'click a[data-transition]': (e) -> + target = $(e.currentTarget) @navigate(target.attr('href'), transition: target.data('transition')) + return false module.exports = IndexPage diff --git a/project_template/app/styles/_forms.scss b/project_template/app/styles/_forms.scss deleted file mode 100644 index a1e4f1e..0000000 --- a/project_template/app/styles/_forms.scss +++ /dev/null @@ -1,41 +0,0 @@ -:focus { - outline: none; -} - -::-moz-focus-inner { - border: 0; -} - -form { - input[type=text], input[type=password], textarea, input[type=number] { - width: 100%; - - padding: 10px; - margin-bottom: 5px; - - outline: none; - background: $color-form-input-bg; - border: 1px solid $color-form-input-border; - - appearance: none; - box-sizing: border-box; - box-shadow: none; - border-radius: 0; - - &:focus { - border: 1px solid $color-form-input-border-focus; - } - } - - input[type=submit] { - width: 100%; - background: $color-primary; - color: $color-primary-text; - -webkit-appearance: none; - outline: none; - - border: 0; - border-radius: 2px; - padding: 10px 19px; - } -} diff --git a/project_template/app/styles/application.scss b/project_template/app/styles/application.scss index 8a6c012..46d38bc 100644 --- a/project_template/app/styles/application.scss +++ b/project_template/app/styles/application.scss @@ -1,127 +1,57 @@ -@import 'normalize'; -@import 'maji/screen_transitions'; -@import 'fonts/icons'; -@import 'variables'; -@import 'forms'; -@import 'platforms/ios'; - @charset 'utf-8'; -html, body { - margin: 0; - padding: 0; - // use native font stack - font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif; - text-size-adjust: 100%; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - user-select: none; -} - -a, a:active, a:hover, a:visited { - text-decoration: none; - color: $color-link; -} - -#maji-app { - position: relative; -} - -.page { - position: absolute; - width: 100%; - height: 100%; - left: 0px; - top: 0px; - - z-index: 2; - background: $color-page-background; - - &.animated .header, &.slideout { - position: absolute; - } -} - -.header { - left: 0; - right: 0; - position: absolute; - z-index: 1000; - - margin: 0; - height: 30px; - line-height: 30px; - - padding: 10px; - background: $color-header-bg; - - text-align: center; - color: $color-header-text; - font-size: 1.2em; - - .btn { - position: absolute; - top: 0px; - - color: $color-header-text; - font-size: 0.8em; - - &[data-rel=back] { - @include icon(back); - margin-left: -10px; - - // scss-lint:disable NestingDepth - &:before { float: left; } - } - - &.left { - left: 5px; - } - - &.right { - right: 5px; - } - } -} - -.body { - padding: 65px 15px 15px; - box-sizing: border-box; -} - -.btn { - display: inline-block; - padding: 10px; -} - -.listview { - list-style-type: none; - padding: 0; - - margin: -15px; - - li { - @include icon(back, after); - - position: relative; - padding: 10px 15px; - - border-bottom: 1px solid $color-listview-li-border; - - &:after { - font-size: 12px; - position: absolute; - top: 50%; - right: 10px; - margin-top: -6px; - transform: rotate(180deg); - } - - a { - display: block; - margin: -10px -15px; - padding: 10px 15px; +/////////////////////////////////////////////////////////// +// +// Use Maji's app-like screen transitions +// +/////////////////////////////////////////////////////////// +@import 'maji/screen_transitions'; +@import 'fonts/icons'; - -webkit-tap-highlight-color: rgba(130, 130, 130, 0.2); - } - } -} +/////////////////////////////////////////////////////////// +// +// Global settings for the project +// +/////////////////////////////////////////////////////////// +@import 'settings/general'; +@import 'settings/colors'; +@import 'settings/typography'; + +/////////////////////////////////////////////////////////// +// +// Globally available tooling +// +/////////////////////////////////////////////////////////// +@import 'tools/mixins'; + +/////////////////////////////////////////////////////////// +// +// High-level styles +// +/////////////////////////////////////////////////////////// +@import 'generic/reset'; +@import 'generic/box-sizing'; + +/////////////////////////////////////////////////////////// +// +// Bare, unclassed Elements +// +/////////////////////////////////////////////////////////// +@import 'elements/base'; + +/////////////////////////////////////////////////////////// +// +// Basic building blocks of the layout +// +/////////////////////////////////////////////////////////// +@import 'objects/media-object'; + +/////////////////////////////////////////////////////////// +// +// UI components +// +/////////////////////////////////////////////////////////// +@import 'components/page'; +@import 'components/header'; +@import 'components/content'; +@import 'components/navigation-list'; diff --git a/project_template/app/styles/components/_content.scss b/project_template/app/styles/components/_content.scss new file mode 100644 index 0000000..6d359fc --- /dev/null +++ b/project_template/app/styles/components/_content.scss @@ -0,0 +1,14 @@ +/////////////////////////////////////////////////////////// +// +// Main content for your application +// +/////////////////////////////////////////////////////////// +.app-content { + background: $page-background; + color: $text-color; + + overflow-y: auto; + flex: 1; + padding: $page-padding; + +} \ No newline at end of file diff --git a/project_template/app/styles/components/_header.scss b/project_template/app/styles/components/_header.scss new file mode 100644 index 0000000..390bb61 --- /dev/null +++ b/project_template/app/styles/components/_header.scss @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////// +// +// Header for your application +// +/////////////////////////////////////////////////////////// +.app-header { + background: $header-background; + color: $header-text; + height: $header-height; + + display: flex; + justify-content: center; + align-items: center; + + // Make room for iOS bar + .platform-ios & { + height: calc(#{$header-height} + #{$header-ios-offset}); + padding-top: $header-ios-offset; + } + + a, + h1 { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + a { + width: $header-button-width; + } + + h1 { + flex: 1; + + &:first-child { + // Compensate for no button on the left side + margin-left: $header-button-width; + } + + &:last-child { + // Compensate for no button on the right side + margin-right: $header-button-width; + } + } +} + +.app-header-back { + @include icon(back); +} \ No newline at end of file diff --git a/project_template/app/styles/components/_navigation-list.scss b/project_template/app/styles/components/_navigation-list.scss new file mode 100644 index 0000000..817448e --- /dev/null +++ b/project_template/app/styles/components/_navigation-list.scss @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////// +// +// List of clickable links +// +/////////////////////////////////////////////////////////// +.navigation-list { + li { + margin-left: -$page-padding; + margin-right: -$page-padding; + border-bottom: 1px solid $navigation-list-border; + + &:first-child { + border-top: 1px solid $navigation-list-border; + } + } + + a { + @include icon(forward, after); + + height: $navigation-list-height; + padding: 0 $page-padding; + display: flex; + align-items: center; + + span { + flex: 1; + } + + &::after { + color: $navigation-list-chevron; + } + } +} \ No newline at end of file diff --git a/project_template/app/styles/components/_page.scss b/project_template/app/styles/components/_page.scss new file mode 100644 index 0000000..ea4bd67 --- /dev/null +++ b/project_template/app/styles/components/_page.scss @@ -0,0 +1,12 @@ +/////////////////////////////////////////////////////////// +// +// Maji page +// +/////////////////////////////////////////////////////////// +.page { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} diff --git a/project_template/app/styles/elements/_base.scss b/project_template/app/styles/elements/_base.scss new file mode 100644 index 0000000..46ff691 --- /dev/null +++ b/project_template/app/styles/elements/_base.scss @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////// +// +// Base styles +// +/////////////////////////////////////////////////////////// +html { + background: $page-background; + font-family: $default-font-stack; + text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + // Avoid horizontal squeeze under minimum mobile screen width + min-width: 320px; +} + +html, +body, +#maji-app { + position: relative; + height: 100%; + margin: 0; + padding: 0; + line-height: 1.25; +} + +// Maji sets height hardcoded on this element. We don't want +// that so we overwrite it here. Better would be to not +// set this height at all! (TODO) +#maji-app { + // scss-lint:disable ImportantRule + height: 100% !important; +} + +button, +a { + -webkit-tap-highlight-color: transparent; + text-decoration: none; +} + +a, +a:hover, +a:active, +a:visited { + color: inherit; +} + +strong { + font-weight: $font-weight-bold; +} + +p { + margin-bottom: 1em; +} \ No newline at end of file diff --git a/project_template/app/styles/generic/_box-sizing.scss b/project_template/app/styles/generic/_box-sizing.scss new file mode 100644 index 0000000..3e0af98 --- /dev/null +++ b/project_template/app/styles/generic/_box-sizing.scss @@ -0,0 +1,7 @@ +/* apply a natural box layout model to all elements, but allowing components to change */ +html { + box-sizing: border-box; +} +*, *:before, *:after { + box-sizing: inherit; +} \ No newline at end of file diff --git a/project_template/app/styles/generic/_reset.scss b/project_template/app/styles/generic/_reset.scss new file mode 100644 index 0000000..af94440 --- /dev/null +++ b/project_template/app/styles/generic/_reset.scss @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/project_template/app/styles/icons/back.svg b/project_template/app/styles/icons/back.svg index df7f320..414df9d 100644 --- a/project_template/app/styles/icons/back.svg +++ b/project_template/app/styles/icons/back.svg @@ -1,8 +1 @@ - - - - - - + \ No newline at end of file diff --git a/project_template/app/styles/icons/forward.svg b/project_template/app/styles/icons/forward.svg new file mode 100644 index 0000000..3a41f60 --- /dev/null +++ b/project_template/app/styles/icons/forward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/project_template/app/styles/objects/_media-object.scss b/project_template/app/styles/objects/_media-object.scss new file mode 100644 index 0000000..f859666 --- /dev/null +++ b/project_template/app/styles/objects/_media-object.scss @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////// +// +// Media object +// +/////////////////////////////////////////////////////////// +.media { + display: flex; + align-items: flex-start; +} + +.media-image { + margin-right: 0.5em; + + &.right { + margin-right: auto; + margin-left: 0.5em; + } +} + +.media-body { + flex: 1 0 auto; + + &.center { + align-self: center; + } +} diff --git a/project_template/app/styles/platforms/ios.scss b/project_template/app/styles/platforms/ios.scss deleted file mode 100644 index 501cfa7..0000000 --- a/project_template/app/styles/platforms/ios.scss +++ /dev/null @@ -1,19 +0,0 @@ -.platform-ios { - $offset-top: 15px; - - #maji-app { - margin-top: $offset-top; - } - - .header { - &:before { - content: ''; - background: $color-header-bg; - height: $offset-top; - display: block; - position: absolute; - left: 0; right: 0; - top: -$offset-top; - } - } -} diff --git a/project_template/app/styles/settings/_colors.scss b/project_template/app/styles/settings/_colors.scss new file mode 100644 index 0000000..349340b --- /dev/null +++ b/project_template/app/styles/settings/_colors.scss @@ -0,0 +1,49 @@ + +/////////////////////////////////////////////////////////// +// +// Base brand colors +// +/////////////////////////////////////////////////////////// +$brand-black: black; +$brand-white: white; +$brand-grey-medium: #777; +$brand-grey-dark: #333; +$brand-red: red; +$brand-green: green; +$brand-orange: orange; +$brand-blue: deepskyblue; + +/////////////////////////////////////////////////////////// +// +// General colors +// +/////////////////////////////////////////////////////////// +$text-color: $brand-black; +$page-background: $brand-white; + +/////////////////////////////////////////////////////////// +// +// Button colors +// +/////////////////////////////////////////////////////////// +$primary-button-background: $brand-blue; +$primary-button-text: $brand-white; + +$secondary-button-background: $brand-grey-medium; +$secondary-button-text: $brand-white; + +/////////////////////////////////////////////////////////// +// +// Header +// +/////////////////////////////////////////////////////////// +$header-background: $brand-blue; +$header-text: $text-color; + +/////////////////////////////////////////////////////////// +// +// Navigation list +// +/////////////////////////////////////////////////////////// +$navigation-list-border: $brand-grey-medium; +$navigation-list-chevron: $brand-blue; \ No newline at end of file diff --git a/project_template/app/styles/settings/_general.scss b/project_template/app/styles/settings/_general.scss new file mode 100644 index 0000000..8abc711 --- /dev/null +++ b/project_template/app/styles/settings/_general.scss @@ -0,0 +1,15 @@ +/////////////////////////////////////////////////////////// +// +// Generic values +// +/////////////////////////////////////////////////////////// +$page-padding: 1rem; // Distance from edge of screen +$border-radius: 0.5rem; +$border-radius-small: 0.125rem; +$easing: cubic-bezier(0.4, 0, 0.2, 1); // Uniform animation easing + +$header-height: 3rem; +$header-ios-offset: 16px; +$header-button-width: $header-height; + +$navigation-list-height: 3rem; \ No newline at end of file diff --git a/project_template/app/styles/settings/_typography.scss b/project_template/app/styles/settings/_typography.scss new file mode 100644 index 0000000..45d73f3 --- /dev/null +++ b/project_template/app/styles/settings/_typography.scss @@ -0,0 +1,15 @@ +/////////////////////////////////////////////////////////// +// +// Typography related values +// +/////////////////////////////////////////////////////////// +$default-font-stack: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif; + +$font-size-huge: 2.75rem; +$font-size-large: 1.75rem; +$font-size-medium: 1.25rem; +$font-size-regular: 1rem; +$font-size-small: 0.75rem; + +$font-weight-normal: 400; +$font-weight-bold: 700; diff --git a/project_template/app/styles/tools/_mixins.scss b/project_template/app/styles/tools/_mixins.scss new file mode 100644 index 0000000..e69de29 diff --git a/project_template/app/styles/variables.scss b/project_template/app/styles/variables.scss deleted file mode 100644 index 3bd24b8..0000000 --- a/project_template/app/styles/variables.scss +++ /dev/null @@ -1,13 +0,0 @@ -$color-primary: #004c9c; -$color-primary-text: #fff; - -$color-header-bg: $color-primary; -$color-header-text: #fff; - -$color-form-input-bg: #fff; -$color-form-input-border: #ccc; -$color-form-input-border-focus: #b5b5b5; - -$color-link: #000; -$color-page-background: #fff; -$color-listview-li-border: #e8e8e8; From 6f242082bd91a46bcc02161d78d3cd7f6f582936 Mon Sep 17 00:00:00 2001 From: Roel Nieskens Date: Mon, 23 Jan 2017 09:17:44 +0100 Subject: [PATCH 68/79] Fix tests and SCSS linter errors --- project_template/.scss-lint.yml | 2 + .../app/styles/components/_content.scss | 3 +- .../app/styles/components/_header.scss | 2 +- .../styles/components/_navigation-list.scss | 2 +- .../app/styles/elements/_base.scss | 2 +- .../app/styles/generic/_box-sizing.scss | 13 +++-- .../app/styles/generic/_reset.scss | 12 +++-- .../app/styles/settings/_colors.scss | 13 +++-- .../app/styles/settings/_general.scss | 2 +- .../app/styles/settings/_typography.scss | 2 +- project_template/config/iconfont.scss.js | 51 ++++++++++--------- .../spec/views/index_page_spec.coffee | 2 +- 12 files changed, 56 insertions(+), 50 deletions(-) diff --git a/project_template/.scss-lint.yml b/project_template/.scss-lint.yml index 84f8f29..926a2cd 100644 --- a/project_template/.scss-lint.yml +++ b/project_template/.scss-lint.yml @@ -15,3 +15,5 @@ linters: PropertySpelling: extra_properties: - text-size-adjust + Indentation: + width: 4 \ No newline at end of file diff --git a/project_template/app/styles/components/_content.scss b/project_template/app/styles/components/_content.scss index 6d359fc..1250f1c 100644 --- a/project_template/app/styles/components/_content.scss +++ b/project_template/app/styles/components/_content.scss @@ -10,5 +10,4 @@ overflow-y: auto; flex: 1; padding: $page-padding; - -} \ No newline at end of file +} diff --git a/project_template/app/styles/components/_header.scss b/project_template/app/styles/components/_header.scss index 390bb61..f8d31ac 100644 --- a/project_template/app/styles/components/_header.scss +++ b/project_template/app/styles/components/_header.scss @@ -47,4 +47,4 @@ .app-header-back { @include icon(back); -} \ No newline at end of file +} diff --git a/project_template/app/styles/components/_navigation-list.scss b/project_template/app/styles/components/_navigation-list.scss index 817448e..78c35b1 100644 --- a/project_template/app/styles/components/_navigation-list.scss +++ b/project_template/app/styles/components/_navigation-list.scss @@ -30,4 +30,4 @@ color: $navigation-list-chevron; } } -} \ No newline at end of file +} diff --git a/project_template/app/styles/elements/_base.scss b/project_template/app/styles/elements/_base.scss index 46ff691..75d6568 100644 --- a/project_template/app/styles/elements/_base.scss +++ b/project_template/app/styles/elements/_base.scss @@ -49,4 +49,4 @@ strong { p { margin-bottom: 1em; -} \ No newline at end of file +} diff --git a/project_template/app/styles/generic/_box-sizing.scss b/project_template/app/styles/generic/_box-sizing.scss index 3e0af98..b222bf8 100644 --- a/project_template/app/styles/generic/_box-sizing.scss +++ b/project_template/app/styles/generic/_box-sizing.scss @@ -1,7 +1,10 @@ -/* apply a natural box layout model to all elements, but allowing components to change */ +// apply a natural box layout model to all elements, but allowing components to change html { - box-sizing: border-box; + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; } -*, *:before, *:after { - box-sizing: inherit; -} \ No newline at end of file diff --git a/project_template/app/styles/generic/_reset.scss b/project_template/app/styles/generic/_reset.scss index af94440..c315d57 100644 --- a/project_template/app/styles/generic/_reset.scss +++ b/project_template/app/styles/generic/_reset.scss @@ -1,4 +1,6 @@ -/* http://meyerweb.com/eric/tools/css/reset/ +// scss-lint:disable all + +/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ @@ -12,8 +14,8 @@ b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; @@ -24,7 +26,7 @@ time, mark, audio, video { vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } @@ -45,4 +47,4 @@ q:before, q:after { table { border-collapse: collapse; border-spacing: 0; -} \ No newline at end of file +} diff --git a/project_template/app/styles/settings/_colors.scss b/project_template/app/styles/settings/_colors.scss index 349340b..5a32096 100644 --- a/project_template/app/styles/settings/_colors.scss +++ b/project_template/app/styles/settings/_colors.scss @@ -4,14 +4,13 @@ // Base brand colors // /////////////////////////////////////////////////////////// -$brand-black: black; -$brand-white: white; +$brand-black: #000; +$brand-white: #fff; $brand-grey-medium: #777; $brand-grey-dark: #333; -$brand-red: red; -$brand-green: green; -$brand-orange: orange; -$brand-blue: deepskyblue; +$brand-red: #f00; +$brand-green: #0f0; +$brand-blue: #00f; /////////////////////////////////////////////////////////// // @@ -46,4 +45,4 @@ $header-text: $text-color; // /////////////////////////////////////////////////////////// $navigation-list-border: $brand-grey-medium; -$navigation-list-chevron: $brand-blue; \ No newline at end of file +$navigation-list-chevron: $brand-blue; diff --git a/project_template/app/styles/settings/_general.scss b/project_template/app/styles/settings/_general.scss index 8abc711..9d002db 100644 --- a/project_template/app/styles/settings/_general.scss +++ b/project_template/app/styles/settings/_general.scss @@ -12,4 +12,4 @@ $header-height: 3rem; $header-ios-offset: 16px; $header-button-width: $header-height; -$navigation-list-height: 3rem; \ No newline at end of file +$navigation-list-height: 3rem; diff --git a/project_template/app/styles/settings/_typography.scss b/project_template/app/styles/settings/_typography.scss index 45d73f3..ff14dd0 100644 --- a/project_template/app/styles/settings/_typography.scss +++ b/project_template/app/styles/settings/_typography.scss @@ -3,7 +3,7 @@ // Typography related values // /////////////////////////////////////////////////////////// -$default-font-stack: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif; +$default-font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; $font-size-huge: 2.75rem; $font-size-large: 1.75rem; diff --git a/project_template/config/iconfont.scss.js b/project_template/config/iconfont.scss.js index bc3fd92..64ea4ea 100644 --- a/project_template/config/iconfont.scss.js +++ b/project_template/config/iconfont.scss.js @@ -1,44 +1,45 @@ @font-face { - font-family: '<%= fontName %>'; - src: url('<%= fontPath + fontName %>.eot'); - src: url('<%= fontPath + fontName %>.eot?#iefix') format('eot'), + font-family: '<%= fontName %>'; + src: url('<%= fontPath + fontName %>.eot'); + src: url('<%= fontPath + fontName %>.eot?#iefix') format('eot'), url('<%= fontPath + fontName %>.woff') format('woff'), url('<%= fontPath + fontName %>.ttf') format('truetype'), url('<%= fontPath + fontName %>.svg#<%= fontName %>') format('svg'); } %icon { - font-family: '<%= fontName %>'; - font-smoothing: antialiased; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-style: normal; - font-variant: normal; - font-weight: normal; - text-decoration: none; - text-transform: none; - text-rendering: auto; + font-family: '<%= fontName %>'; + font-smoothing: antialiased; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-style: normal; + font-variant: normal; + font-weight: normal; + text-decoration: none; + text-transform: none; + text-rendering: auto; + user-select: none; // Avoid text being selectable } @function icon-char($filename) { - $char: ''; -<% _.each(glyphs, function(glyph) { %> - @if $filename == <%= glyph.fileName %> { - $char: '\<%= glyph.codePoint %>'; - }<% }); %> - - @return $char; + $char: ''; + <% _.each(glyphs, function(glyph) { %> + @if $filename == <%= glyph.fileName %> { + $char: '\<%= glyph.codePoint %>'; + } + <% }); %> + @return $char; } @mixin icon($filename, $insert: before) { - &:#{$insert} { - @extend %icon; - content: icon-char($filename); - } + &:#{$insert} { + @extend %icon; + content: icon-char($filename); + } } <% _.each(glyphs, function(glyph) { %>%icon-<%= glyph.fileName %> { - @include icon(<%= glyph.fileName %>); + @include icon(<%= glyph.fileName %>); } <% }); %> diff --git a/project_template/spec/views/index_page_spec.coffee b/project_template/spec/views/index_page_spec.coffee index d1ab056..4741750 100644 --- a/project_template/spec/views/index_page_spec.coffee +++ b/project_template/spec/views/index_page_spec.coffee @@ -7,4 +7,4 @@ describe 'IndexPage', -> it 'shows a message', -> DOM.append @view.render().el - expect(@view.$el.find('p.welcome').text().trim()).to.eq('Welcome to your Maji app!') + expect(@view.$el.find('p:first-child').text().trim()).to.eq('Welcome to your Maji app!') From faf595cd0e8a0f2a663164c1d503caafa441bda9 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Wed, 25 Jan 2017 07:58:29 +0100 Subject: [PATCH 69/79] Fix typo --- project_template/app/config/template_helpers.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_template/app/config/template_helpers.coffee b/project_template/app/config/template_helpers.coffee index b1be7e2..376df64 100644 --- a/project_template/app/config/template_helpers.coffee +++ b/project_template/app/config/template_helpers.coffee @@ -5,7 +5,7 @@ Maji = require('maji') # These helpers will be exposed in templates under the 'h' namespace. # # E.g a 'pluralize' helper could be called from a template as -# h.pluaralize 'something' +# h.pluralize 'something' class AppTemplateHelpers Maji.TemplateHelpers.register(new AppTemplateHelpers()) From 696bd401cd38be3090d776cc473b9bac9e2f8295 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Wed, 1 Feb 2017 12:14:27 +0100 Subject: [PATCH 70/79] Inject livereload.js into head This is safer than injecting it in the body, as the entire body might be replaced by client side content. The head is unlikely to be emptied ever so injecting it there seems reasonable. --- src/maji-dev-server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/maji-dev-server.js b/src/maji-dev-server.js index 19fa730..1b23880 100755 --- a/src/maji-dev-server.js +++ b/src/maji-dev-server.js @@ -47,7 +47,11 @@ if(livereload) { .use(tinylr.middleware({ app: server, dashboard: true })) .use(require('connect-livereload')({ src: '/livereload.js?snipver=1', - include: [ '/', '.*\.html'] + include: [ '/', '.*\.html'], + rules: [{ + match: /<\/head>(?![\s\S]*<\/head>)/i, + fn: function(w, s) { return s + w; } + }] })); } From 7e3e7fc930a23eb67e724984320f536ddf20184b Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Thu, 2 Feb 2017 08:43:45 +0100 Subject: [PATCH 71/79] Set default SplashScreenDelay to 0 0 is a more sensible value. Depending on the time it takes to render your initial screen you might want to increase this value, or hide the splashscreen from Javascript after you've rendered your initial screen. No need to always wait 1500 msec. --- project_template/cordova/config.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project_template/cordova/config.xml b/project_template/cordova/config.xml index b398ddf..5dfeaf0 100644 --- a/project_template/cordova/config.xml +++ b/project_template/cordova/config.xml @@ -17,9 +17,8 @@ - - + From d67c57f8654b5045947582a2dde0602e2f4804c3 Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Thu, 2 Feb 2017 13:23:22 +0100 Subject: [PATCH 72/79] Describe the changes needed to be made to application.coffee --- docs/upgrade_guide.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index 5171f8e..8cdb3d1 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -11,9 +11,28 @@ The `Maji.bus` (using `backbone.wreqr`) is also removed in favor of using `backb Marionette has an [upgrade guide available online][marionette-upgrade], to find pointers not covered in this guide. -The best way to start is to checkout the updated example app. - -1. Update `app/application.coffee` You can probably look up the example app version +The best way to start is to checkout the updated example app and compare it to your app. + +1. Update `app/application.coffee`: + 1. At the top of the file, replace + ```coffee + attachFastClick = require('fastclick') + ``` + + with + + ```coffee + FastClick = require('fastclick) + ``` + + 1. In the method `setTimeout`, replace both instances of + ```coffee + app.mainRegion.$el + ``` + with + ```coffee + $(app.getRegion().el) + ``` 2. Update `app/app.coffee`: From 03fe0eab0bba9bf5b00a3367fb41e29a2a99774d Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Thu, 2 Feb 2017 13:24:13 +0100 Subject: [PATCH 73/79] Fix the formatting of the third section --- docs/upgrade_guide.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index 8cdb3d1..1573a16 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -87,7 +87,6 @@ The best way to start is to checkout the updated example app and compare it to y app.getRegion().goBack(where, opts) ) ``` - ``` 3. The best approachs is to disable all modules, and enable/update them one by one, starting with the main module 'app' file: @@ -116,7 +115,7 @@ The best way to start is to checkout the updated example app and compare it to y To: -```coffee + ```coffee # Notice creation of module is gone; # New name for router From 79bed6605737f246d4b7f0df6460f84da5949743 Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Thu, 2 Feb 2017 13:24:21 +0100 Subject: [PATCH 74/79] Remove extra whitespace --- docs/upgrade_guide.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index 1573a16..2c891c9 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -87,11 +87,11 @@ The best way to start is to checkout the updated example app and compare it to y app.getRegion().goBack(where, opts) ) ``` - + 3. The best approachs is to disable all modules, and enable/update them one by one, starting with the main module 'app' file: - Change: - + Change: + ```coffee MyModuleApp = app.module('my_module') MyModuleApp.startWithParent = false @@ -99,40 +99,40 @@ The best way to start is to checkout the updated example app and compare it to y class MyModuleApp.Router extends Marionette.AppRouter appRoutes: 'my_route/:resource_id' : 'openMyRoute' - + API = openMyRoute: (resourceId) -> app.mainRegion.show new EditorPage( model: model ) - + MyModuleApp.addInitializer -> new EditorApp.Router controller: API - + module.exports = EditorApp - ``` + ``` To: ```coffee # Notice creation of module is gone; # New name for router - + class MyModuleRouter extends Marionette.AppRouter routes: # previously appRoutes 'my_route/:resource_id' : 'openMyRoute' - + openMyRoute: (resourceId) -> # Method in class now app.showView new EditorPage( # Use showView model: model ) - + app.on 'start', -> new MyModuleRouter # No passing of controller - - # No Module exports -``` + + # No Module exports + ``` 4. Update all your views: @@ -162,7 +162,7 @@ In Maji 2.0, Maji no longer automatically adds Cordova platforms when you invoke All projects created with Maji 2.0 are configured like this out of the box. -## Maji bus +## Maji bus Update all uses of the `Maji.bus` to Backbone.Radio ([see documentation of radio here][backbone-radio]) From 4a020f138d6b331ac8b20b318c7fb650b3c21124 Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Thu, 2 Feb 2017 13:28:05 +0100 Subject: [PATCH 75/79] Mention the correct event --- docs/upgrade_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index 2c891c9..e7cde0a 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -59,7 +59,7 @@ The best way to start is to checkout the updated example app and compare it to y Change the kickoff of the backbone history: ```coffee - app.on 'start', (options) -> + app.on 'initialize:after', (options) -> Backbone.history.start() ``` From bdb2082d1bf4504315922be6b1e2831b77e24fca Mon Sep 17 00:00:00 2001 From: Etienne van Delden Date: Thu, 2 Feb 2017 14:38:27 +0100 Subject: [PATCH 76/79] Clarify that parts of the marionette upgrade are in this guide --- docs/upgrade_guide.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/upgrade_guide.md b/docs/upgrade_guide.md index e7cde0a..02021d1 100644 --- a/docs/upgrade_guide.md +++ b/docs/upgrade_guide.md @@ -9,7 +9,7 @@ In Maji 2.0.0 the Marionette dependency has been updated to 3.1.0. This means that your app has to update from Marionette 2.4.4 to 3.1.0. The `Maji.bus` (using `backbone.wreqr`) is also removed in favor of using `backbone.radio`. -Marionette has an [upgrade guide available online][marionette-upgrade], to find pointers not covered in this guide. +Marionette has an [upgrade guide available online][marionette-upgrade], which you can use to find more help upgrading after using this guide. The best way to start is to checkout the updated example app and compare it to your app. @@ -70,8 +70,8 @@ The best way to start is to checkout the updated example app and compare it to y # Bind the start event in after the inclusion of # the modules, so that the routers are initialized # before the kick-off of the backbone history - app.on 'start', (options) -> - Backbone.history.start() + app.on 'start', (options) -> + Backbone.history.start() ``` Update the implementation of the bus functions to: From 045d19cec239cfba8527a5f5bfa28158c2e3ec26 Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Fri, 3 Feb 2017 10:54:36 +0100 Subject: [PATCH 77/79] Fix header color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maji Blue™ and white text --- project_template/app/styles/settings/_colors.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/project_template/app/styles/settings/_colors.scss b/project_template/app/styles/settings/_colors.scss index 5a32096..54a4bd6 100644 --- a/project_template/app/styles/settings/_colors.scss +++ b/project_template/app/styles/settings/_colors.scss @@ -10,7 +10,7 @@ $brand-grey-medium: #777; $brand-grey-dark: #333; $brand-red: #f00; $brand-green: #0f0; -$brand-blue: #00f; +$brand-blue: #009cff; /////////////////////////////////////////////////////////// // @@ -18,6 +18,7 @@ $brand-blue: #00f; // /////////////////////////////////////////////////////////// $text-color: $brand-black; +$text-color-light: $brand-white; $page-background: $brand-white; /////////////////////////////////////////////////////////// @@ -37,7 +38,7 @@ $secondary-button-text: $brand-white; // /////////////////////////////////////////////////////////// $header-background: $brand-blue; -$header-text: $text-color; +$header-text: $text-color-light; /////////////////////////////////////////////////////////// // From e2858fcfb946051458bc4367d2b5a39da9d0abba Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Fri, 3 Feb 2017 13:46:20 +0100 Subject: [PATCH 78/79] Upgrade webpack to 2.2 stable --- project_template/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_template/package.json b/project_template/package.json index baa5a3f..035fe2c 100644 --- a/project_template/package.json +++ b/project_template/package.json @@ -46,7 +46,7 @@ "sinon": "~1.17.2", "sinon-chai": "2.8.0", "vinyl-fs": "~2.4.2", - "webpack": "^2.2.0-rc.3", + "webpack": "^2.2.1", "yaml-loader": "^0.4.0" } } From d462294ffebac3ffb279e88a125e4ba3da88de4c Mon Sep 17 00:00:00 2001 From: Pascal Widdershoven Date: Mon, 6 Feb 2017 14:14:51 +0100 Subject: [PATCH 79/79] Fix CI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 049901f..e8783b2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Maji Mobile](img/maji-mobile-logo.png) -[![Build Status](https://ci.kabisa.nl/buildStatus/icon?job=maji)](https://ci.kabisa.nl/job/maji/) +[![Build Status](https://ci.kabisa.nl/buildStatus/icon?job=maji/2-0-stable) Maji Mobile is a mobile platform development solution, that allows you to quickly create mobile applications for any platform, using web technologies. It allows any (Web-)developer to quickly start developing mobile applications for any mobile platform.