├── app ├── .gitkeep ├── templates │ └── components │ │ ├── ps-pane-link.hbs │ │ ├── ps-pane-menu.hbs │ │ ├── ps-pane.hbs │ │ ├── ps-draggable-pane.hbs │ │ └── ps-panel.hbs └── components │ ├── ps-draggable-pane.js │ ├── ps-pane-link.js │ ├── ps-pane-menu.js │ ├── ps-pane.js │ └── ps-panel.js ├── addon ├── .gitkeep ├── utils │ ├── animate.js │ ├── init-pane-controllers.js │ ├── init-active-panes.js │ └── find-component-by-name.js ├── controllers │ ├── panel.js │ └── pane.js └── mixins │ ├── component-registry.js │ ├── child-component.js │ └── draggable-panel.js ├── vendor ├── .gitkeep └── ember-cli-panels.css ├── tests ├── unit │ └── .gitkeep ├── dummy │ ├── public │ │ ├── .gitkeep │ │ ├── robots.txt │ │ └── crossdomain.xml │ ├── app │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── user.js │ │ │ └── contact.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── views │ │ │ └── .gitkeep │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── panes │ │ │ │ ├── contacts.js │ │ │ │ └── users.js │ │ │ └── application.js │ │ ├── templates │ │ │ ├── components │ │ │ │ └── .gitkeep │ │ │ ├── panes │ │ │ │ ├── contacts.hbs │ │ │ │ └── users.hbs │ │ │ └── application.hbs │ │ ├── adapters │ │ │ └── application.js │ │ ├── router.js │ │ ├── styles │ │ │ └── app.css │ │ ├── app.js │ │ └── index.html │ └── config │ │ └── environment.js ├── helpers │ ├── resolver.js │ └── start-app.js ├── test-helper.js ├── index.html └── .jshintrc ├── server ├── .jshintrc ├── index.js └── mocks │ ├── users.js │ └── contacts.js ├── .bowerrc ├── config └── environment.js ├── .npmignore ├── testem.json ├── .ember-cli ├── .gitignore ├── index.js ├── .travis.yml ├── blueprints └── ember-cli-panels │ └── index.js ├── bower.json ├── .editorconfig ├── .jshintrc ├── Brocfile.js ├── LICENSE.md ├── package.json └── README.md /app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/templates/components/ps-pane-link.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} 2 | -------------------------------------------------------------------------------- /app/templates/components/ps-pane-menu.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} 2 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /app/templates/components/ps-pane.hbs: -------------------------------------------------------------------------------- 1 | {{partial pane.partialName}} 2 | {{yield}} 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /app/templates/components/ps-draggable-pane.hbs: -------------------------------------------------------------------------------- 1 | {{partial pane.partialName}} 2 | {{yield}} 3 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/dummy/app/models/user.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Model.extend({ 4 | name: DS.attr('string') 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/models/contact.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Model.extend({ 4 | name: DS.attr('string') 5 | }); 6 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.RESTAdapter.extend({ 4 | namespace: 'api' 5 | }); 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | .bowerrc 4 | .editorconfig 5 | .ember-cli 6 | .travis.yml 7 | .npmignore 8 | **/.gitkeep 9 | bower.json 10 | Brocfile.js 11 | testem.js 12 | server/ 13 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /addon/utils/animate.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function animate($el, opts, ms) { 4 | if (ms !== 0 && !ms) { 5 | ms = 375; 6 | } 7 | 8 | return Ember.$.Velocity($el, opts, ms); 9 | } 10 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/panes/contacts.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Pane from 'ember-cli-panels/controllers/pane'; 3 | 4 | export default Pane.extend({ 5 | modelHook: function() { 6 | return this.store.find('contact'); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | }); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /addon/utils/init-pane-controllers.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function initPaneControllers(paneNames) { 4 | return Ember.computed(function() { 5 | return paneNames.map(function(paneName) { 6 | return this.container.lookup('controller:' + paneName); 7 | }, this); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | 6 | module.exports = { 7 | name: 'ember-cli-panels', 8 | isDevelopingAddon: function() { 9 | return true; 10 | }, 11 | 12 | included: function(app) { 13 | app.import(path.join('vendor', 'ember-cli-panels.css')); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 20px; 3 | } 4 | 5 | p { 6 | margin-bottom: 5px; 7 | } 8 | 9 | .ps-panel { 10 | position: relative; 11 | height: 150px; 12 | } 13 | 14 | .ps-pane { 15 | position: absolute; 16 | } 17 | 18 | .panel-menu div { 19 | float: left; 20 | padding-right: 10px; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/panes/contacts.hbs: -------------------------------------------------------------------------------- 1 |

2 | Contacts 3 |

4 | 5 | {{#if pane.loading}} 6 | Loading... 7 | {{/if}} 8 | 9 | 20 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/panes/users.hbs: -------------------------------------------------------------------------------- 1 |

2 | Users 3 |

4 | 5 | 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - "npm config set spin false" 14 | - "npm install -g npm@^2" 15 | 16 | install: 17 | - npm install -g bower 18 | - npm install 19 | - bower install 20 | 21 | script: 22 | - npm test 23 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/panes/users.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Pane from 'ember-cli-panels/controllers/pane'; 3 | 4 | export default Pane.extend({ 5 | modelHook: function() { 6 | return this.store.find('user'); 7 | }, 8 | 9 | actions: { 10 | remove: function(user) { 11 | this.get('model').removeObject(user); 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /addon/controllers/panel.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { animate } from 'liquid-fire'; 3 | 4 | export default Ember.Controller.extend({ 5 | queryParams: ['pane'], 6 | 7 | showAnimation: function() { 8 | return animate(this, {translateX: 0, opacity: 1}, {}, 'show ps-pane'); 9 | }, 10 | 11 | hideAnimation: function() { 12 | return animate(this, {translateX: -100, opacity: 0}, {}, 'hide ps-pane'); 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /addon/mixins/component-registry.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Mixin.create({ 4 | // class can override this to give it a different name 5 | childComponentsName: 'children', 6 | 7 | __registerChild: function(child) { 8 | this.get(this.get('childComponentsName')).unshiftObject(child); 9 | }, 10 | 11 | __unregisterChild: function(child) { 12 | this.get(this.get('childComponentsName')).removeObject(child); 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /blueprints/ember-cli-panels/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | normalizeEntityName: function() { }, 3 | 4 | afterInstall: function() { 5 | var blueprint = this; 6 | 7 | return blueprint.addPackageToProject('liquid-fire', '~0.16.0').then(function() { 8 | return blueprint.addBowerPackageToProject([ 9 | { name: 'hammer.js', target: '^2.0.4' }, 10 | { name: 'jquery-scrollstop', target: '~1.1.0' } 11 | ]); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/templates/components/ps-panel.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#each paneCtrl in paneControllers}} 3 | {{#if draggable}} 4 | {{ps-draggable-pane 5 | pane=paneCtrl 6 | showAnimation=showAnimation 7 | hideAnimation=hideAnimation}} 8 | {{else}} 9 | {{ps-pane 10 | pane=paneCtrl 11 | showAnimation=showAnimation 12 | hideAnimation=hideAnimation}} 13 | {{/if}} 14 | {{/each}} 15 |
16 | 17 | {{yield}} 18 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | Ember.MODEL_FACTORY_INJECTIONS = true; 7 | 8 | var App = Ember.Application.extend({ 9 | modulePrefix: config.modulePrefix, 10 | podModulePrefix: config.podModulePrefix, 11 | Resolver: Resolver 12 | }); 13 | 14 | loadInitializers(App, config.modulePrefix); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | 8 | document.write('
'); 9 | 10 | QUnit.config.urlConfig.push({ id: 'nocontainer', label: 'Hide container'}); 11 | var containerVisibility = QUnit.urlParams.nocontainer ? 'hidden' : 'visible'; 12 | document.getElementById('ember-testing-container').style.visibility = containerVisibility; 13 | -------------------------------------------------------------------------------- /app/components/ps-draggable-pane.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import PsPane from './ps-pane'; 3 | 4 | export default PsPane.extend({ 5 | _hideAnimation: function() { 6 | return true; 7 | }, 8 | 9 | _showAnimation: function() { 10 | this.get('panel').updateContainerWidth(this.width()); 11 | 12 | return true; 13 | }, 14 | 15 | width: function() { 16 | return this.$().outerWidth(); 17 | }, 18 | 19 | updateWidth: function(width) { 20 | return this.$().width(width); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to ember-cli-panels

2 | 3 | {{ps-panel 4 | currentPaneName=pane 5 | paneControllers=paneControllers 6 | showAnimation=showAnimation 7 | hideAnimation=hideAnimation}} 8 | 9 |
10 |
11 | 12 | Users 13 | 14 |
15 |
16 | 17 | Contacts 18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /addon/mixins/child-component.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | // The parent must mixin ParentRegistrar 4 | export default Ember.Mixin.create({ 5 | parentName: 'parent', // class can override this to give it a different name 6 | 7 | __registerWithParent: Ember.on('didInsertElement', function() { 8 | this.get(this.get('parentName')).__registerChild(this); 9 | }), 10 | 11 | __deregisterWithParent: Ember.on('willDestroyElement', function() { 12 | this.get(this.get('parentName')).__unregisterChild(this); 13 | }), 14 | }); 15 | -------------------------------------------------------------------------------- /addon/utils/init-active-panes.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function initActivePanes(currentPane, paneNames) { 4 | return Ember.on('init', function() { 5 | paneNames.forEach(function(paneName) { 6 | var split = paneName.split('/'); 7 | var name = split[split.length - 1]; 8 | 9 | var propName = Ember.String.camelize(name + 'Active'); 10 | 11 | Ember.defineProperty(this, propName, 12 | Ember.computed.equal(currentPane, paneName)); 13 | }, this); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import Router from '../../router'; 4 | import config from '../../config/environment'; 5 | 6 | export default function startApp(attrs) { 7 | var application; 8 | 9 | var attributes = Ember.merge({}, config.APP); 10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 11 | 12 | Ember.run(function() { 13 | application = Application.create(attributes); 14 | application.setupForTesting(); 15 | application.injectTestHelpers(); 16 | }); 17 | 18 | return application; 19 | } 20 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-panels", 3 | "dependencies": { 4 | "jquery": "^1.11.1", 5 | "ember": "1.10.0", 6 | "ember-data": "1.0.0-beta.16", 7 | "ember-resolver": "~0.1.14", 8 | "loader.js": "ember-cli/loader.js#3.2.0", 9 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 10 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 11 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.0.2", 12 | "ember-qunit": "0.2.8", 13 | "ember-qunit-notifications": "0.0.7", 14 | "qunit": "~1.17.1", 15 | "hammer.js": "~2.0.4", 16 | "jquery-scrollstop": "~1.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.css] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.html] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.md] 33 | trim_trailing_whitespace = false 34 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /addon/controllers/pane.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | modelHook: function() { 5 | return null; 6 | }, 7 | 8 | partialName: Ember.computed.alias('name'), 9 | 10 | name: Ember.computed(function() { 11 | return this.toString().split(':')[1]; 12 | }), 13 | 14 | setupModel: Ember.on('init', function() { 15 | var pane = this; 16 | this.set('loading', true); 17 | 18 | Ember.RSVP.resolve(this.modelHook()).then(function(model) { 19 | if (model) { 20 | pane.set('model', model); 21 | } 22 | }, function(err) { 23 | console.log('modelHook error:', err); 24 | }).finally(function() { 25 | pane.set('loading', false); 26 | }); 27 | }) 28 | }); 29 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | // To use it create some files under `routes/` 2 | // e.g. `server/routes/ember-hamsters.js` 3 | // 4 | // module.exports = function(app) { 5 | // app.get('/ember-hamsters', function(req, res) { 6 | // res.send('hello'); 7 | // }); 8 | // }; 9 | 10 | module.exports = function(app) { 11 | var globSync = require('glob').sync; 12 | var mocks = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require); 13 | var proxies = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require); 14 | 15 | // Log proxy requests 16 | var morgan = require('morgan'); 17 | app.use(morgan('dev')); 18 | 19 | mocks.forEach(function(route) { route(app); }); 20 | proxies.forEach(function(route) { route(app); }); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import initPaneControllers from 'ember-cli-panels/utils/init-pane-controllers'; 3 | import { animate } from 'liquid-fire'; 4 | 5 | export default Ember.Controller.extend({ 6 | queryParams: ['pane'], 7 | pane: 'panes/users', 8 | 9 | paneControllers: initPaneControllers(['panes/users', 'panes/contacts']), 10 | 11 | showAnimation: function() { 12 | return animate(this, {translateX: 0, opacity: 1}, {}, 'show ps-pane'); 13 | }, 14 | 15 | hideAnimation: function() { 16 | return animate(this, {translateX: -100, opacity: 0}, {}, 'hide ps-pane'); 17 | }, 18 | 19 | actions: { 20 | switchPane: function(name) { 21 | this.set('pane', name); 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /vendor/ember-cli-panels.css: -------------------------------------------------------------------------------- 1 | .ps-panel { 2 | position: relative; 3 | width: 100%; 4 | overflow-x: hidden; 5 | } 6 | 7 | .ps-pane { 8 | overflow-x: hidden; 9 | overflow-y: scroll; 10 | -webkit-overflow-scrolling: touch; 11 | height: 100%; 12 | margin: 0; 13 | padding: 0; 14 | float: left; 15 | display: block; 16 | } 17 | 18 | /* http://cantina.co/thought_leadership/ios-5-native-scrolling-grins-and-gothcas/ */ 19 | .ps-pane > * { 20 | -webkit-transform: translate3d(0,0,0); 21 | } 22 | 23 | .ps-pane-menu { 24 | height: 3rem; 25 | overflow: hidden; 26 | text-align: justify; 27 | white-space: nowrap; 28 | } 29 | 30 | .ps-pane-link { 31 | line-height: 1rem; 32 | text-decoration: none; 33 | font-size: 1.0rem; 34 | padding: 1rem 2rem; 35 | display: inline-block; 36 | } 37 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* global require, module */ 3 | 4 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 5 | 6 | var app = new EmberAddon(); 7 | 8 | // Use `app.import` to add additional libraries to the generated 9 | // output files. 10 | // 11 | // If you need to use different assets in different 12 | // environments, specify an object as the first parameter. That 13 | // object's keys should be the environment name and the values 14 | // should be the asset to use in that environment. 15 | // 16 | // If the library that you are including contains AMD or ES6 17 | // modules that you would like to import into your application 18 | // please specify an object with the list of modules as keys 19 | // along with the exports of each module as its value. 20 | 21 | module.exports = app.toTree(); 22 | -------------------------------------------------------------------------------- /addon/utils/find-component-by-name.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function(componentName, errMsg) { 4 | if (!errMsg) { 5 | errMsg = ''; 6 | } 7 | 8 | return Ember.computed(function() { 9 | function tryParentContext(parent) { 10 | var parentComponentName = getComponentName(parent); 11 | 12 | if (parentComponentName === componentName) { 13 | return parent; 14 | } 15 | 16 | var parentsParent = parent.get('parentView'); 17 | 18 | if (parentsParent) { 19 | return tryParentContext(parentsParent); 20 | } 21 | 22 | throw new Ember.Error('Could not find parent component named ' + componentName + ' to return. ' + errMsg); 23 | } 24 | 25 | return tryParentContext(this.get('parentView')); 26 | }); 27 | } 28 | 29 | function getComponentName(component) { 30 | var name = component.toString(); 31 | 32 | if (name.indexOf('@component') > -1) { 33 | return name.split(':')[1]; 34 | } 35 | 36 | return false; 37 | } 38 | -------------------------------------------------------------------------------- /server/mocks/users.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | var express = require('express'); 3 | var usersRouter = express.Router(); 4 | 5 | usersRouter.get('/', function(req, res) { 6 | res.send({ 7 | "users": [ 8 | { 9 | id: 0, 10 | name: "Jake Craige" 11 | }, { 12 | id: 1, 13 | name: "Matthew Hager" 14 | }, { 15 | id: 2, 16 | name: "Dalia Rihani" 17 | } 18 | ] 19 | }); 20 | }); 21 | 22 | usersRouter.post('/', function(req, res) { 23 | res.status(201).end(); 24 | }); 25 | 26 | usersRouter.get('/:id', function(req, res) { 27 | res.send({ 28 | "users": { 29 | "id": req.params.id 30 | } 31 | }); 32 | }); 33 | 34 | usersRouter.put('/:id', function(req, res) { 35 | res.send({ 36 | "users": { 37 | "id": req.params.id 38 | } 39 | }); 40 | }); 41 | 42 | usersRouter.delete('/:id', function(req, res) { 43 | res.status(204).end(); 44 | }); 45 | 46 | app.use('/api/users', usersRouter); 47 | }; 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /server/mocks/contacts.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | var express = require('express'); 3 | var contactsRouter = express.Router(); 4 | 5 | contactsRouter.get('/', function(req, res) { 6 | // Simulate delay 7 | setTimeout(function() { 8 | res.send({ 9 | "contacts": [ 10 | { 11 | id: 0, 12 | name: "Bart Kowalski" 13 | }, { 14 | id: 1, 15 | name: "Lauren Schott" 16 | }, { 17 | id: 2, 18 | name: "John Goodman" 19 | } 20 | ] 21 | }); 22 | }, 3000); 23 | }); 24 | 25 | contactsRouter.post('/', function(req, res) { 26 | res.status(201).end(); 27 | }); 28 | 29 | contactsRouter.get('/:id', function(req, res) { 30 | res.send({ 31 | "contacts": { 32 | "id": req.params.id 33 | } 34 | }); 35 | }); 36 | 37 | contactsRouter.put('/:id', function(req, res) { 38 | res.send({ 39 | "contacts": { 40 | "id": req.params.id 41 | } 42 | }); 43 | }); 44 | 45 | contactsRouter.delete('/:id', function(req, res) { 46 | res.status(204).end(); 47 | }); 48 | 49 | app.use('/api/contacts', contactsRouter); 50 | }; 51 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.baseURL = '/'; 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-panels", 3 | "version": "0.0.6", 4 | "directories": { 5 | "doc": "doc", 6 | "test": "tests" 7 | }, 8 | "scripts": { 9 | "start": "ember server", 10 | "build": "ember build", 11 | "test": "ember test" 12 | }, 13 | "homepage": "https://github.com/poetic/ember-cli-panels", 14 | "repository": "https://github.com/poetic/ember-cli-panels.git", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "Jake Craige ", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.0.2", 22 | "ember-cli": "0.2.1", 23 | "ember-cli-app-version": "0.3.3", 24 | "ember-cli-content-security-policy": "0.4.0", 25 | "ember-cli-dependency-checker": "0.0.8", 26 | "ember-cli-htmlbars": "0.7.4", 27 | "ember-cli-ic-ajax": "0.1.1", 28 | "ember-cli-inject-live-reload": "^1.3.0", 29 | "ember-cli-qunit": "0.3.9", 30 | "ember-cli-uglify": "1.0.1", 31 | "ember-data": "1.0.0-beta.16", 32 | "ember-export-application-global": "^1.0.2", 33 | "ember-cli-github-pages": "0.0.2", 34 | "liquid-fire": "^0.15.2" 35 | }, 36 | "description": "An ember addon to support immediate switching between templates.", 37 | "keywords": [ 38 | "ember-addon" 39 | ], 40 | "dependencies": { 41 | "ember-cli-babel": "^4.0.0" 42 | }, 43 | "ember-addon": { 44 | "configPath": "tests/dummy/config" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 32 | 33 | {{content-for 'head-footer'}} 34 | {{content-for 'test-head-footer'}} 35 | 36 | 37 | 38 | {{content-for 'body'}} 39 | {{content-for 'test-body'}} 40 | 41 | 42 | 43 | 44 | 45 | 46 | {{content-for 'body-footer'}} 47 | {{content-for 'test-body-footer'}} 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "QUnit", 10 | "define", 11 | "console", 12 | "equal", 13 | "notEqual", 14 | "notStrictEqual", 15 | "test", 16 | "asyncTest", 17 | "testBoth", 18 | "testWithDefault", 19 | "raises", 20 | "throws", 21 | "deepEqual", 22 | "start", 23 | "stop", 24 | "ok", 25 | "strictEqual", 26 | "module", 27 | "moduleFor", 28 | "moduleForComponent", 29 | "moduleForModel", 30 | "process", 31 | "expect", 32 | "visit", 33 | "exists", 34 | "fillIn", 35 | "click", 36 | "keyEvent", 37 | "triggerEvent", 38 | "find", 39 | "findWithAssert", 40 | "wait", 41 | "DS", 42 | "isolatedContainer", 43 | "startApp", 44 | "andThen", 45 | "currentURL", 46 | "currentPath", 47 | "currentRouteName" 48 | ], 49 | "node": false, 50 | "browser": false, 51 | "boss": true, 52 | "curly": false, 53 | "debug": false, 54 | "devel": false, 55 | "eqeqeq": true, 56 | "evil": true, 57 | "forin": false, 58 | "immed": false, 59 | "laxbreak": false, 60 | "newcap": true, 61 | "noarg": true, 62 | "noempty": false, 63 | "nonew": false, 64 | "nomen": false, 65 | "onevar": false, 66 | "plusplus": false, 67 | "regexp": false, 68 | "undef": true, 69 | "sub": true, 70 | "strict": false, 71 | "white": false, 72 | "eqnull": true, 73 | "esnext": true 74 | } 75 | -------------------------------------------------------------------------------- /app/components/ps-pane-link.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { animate } from 'liquid-fire'; 3 | import ChildComponent from 'ember-cli-panels/mixins/child-component'; 4 | import findComponentByName from 'ember-cli-panels/utils/find-component-by-name'; 5 | 6 | export default Ember.Component.extend(ChildComponent, { 7 | parentName: 'menu', // for ChildComponent 8 | menu: findComponentByName('ps-pane-menu'), 9 | currentPane: Ember.computed.alias('menu.currentPane'), 10 | panePrefix: Ember.computed.alias('menu.panePrefix'), 11 | 12 | tagName: 'a', 13 | classNames: 'ps-pane-link', 14 | 15 | attributeBindings: ['href'], 16 | href: '#', 17 | 18 | classNameBindings: ['active'], 19 | active: Ember.computed('currentPane', 'prefixedTo', function() { 20 | return Ember.isEqual(this.get('currentPane'), this.get('prefixedTo')); 21 | }), 22 | 23 | prefixedTo: Ember.computed('panePrefix', 'to', function() { 24 | if (this.get('panePrefix')) { 25 | return this.get('panePrefix') + '/' + this.get('to'); 26 | } 27 | 28 | return this.get('to'); 29 | }), 30 | 31 | click: function(e) { 32 | e.preventDefault(); 33 | 34 | this.get('menu').sendAction('action', this.get('prefixedTo')); 35 | this.get('menu').switchPane(this.get('prefixedTo')); 36 | }, 37 | 38 | scrollIntoCenter: function(menu, duration) { 39 | return animate(this, 'scroll', { 40 | container: menu.$(), 41 | axis: 'x', 42 | offset: this.calculateOffset(menu.$(), this.$()), 43 | duration: duration 44 | }); 45 | }, 46 | 47 | calculateOffset: function($menu, $el) { 48 | var menuWidth = $menu.outerWidth(); 49 | var elWidth = $el.outerWidth(); 50 | 51 | return elWidth / 2 - menuWidth / 2; 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /app/components/ps-pane-menu.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ComponentRegistry from 'ember-cli-panels/mixins/component-registry'; 3 | 4 | export default Ember.Component.extend(ComponentRegistry, { 5 | classNames: 'ps-pane-menu', 6 | 7 | currentPane: null, 8 | panePrefix: null, 9 | childComponentsName: 'linkComponents', // for ComponentRegistry 10 | linkComponents: Ember.A([]), 11 | animating: false, 12 | isFirstRender: true, 13 | 14 | switchPane: function(newPane) { 15 | if (this.get('animating')) { 16 | return; 17 | } 18 | 19 | // hackyhack fixes flashing bug from clicking to next pane too quick in menu 20 | // I think the run loop / ember ends up merging all the changes into one 21 | // when I use the timeout? I figured it would do this anyways but it doesn't 22 | // seem to. 23 | Ember.run.later(this, function() { 24 | this.set('currentPane', newPane); 25 | }, 100); 26 | }, 27 | 28 | activeLink: Ember.computed('linkComponents.@each.active', function() { 29 | return this.get('linkComponents').findBy('active', true); 30 | }), 31 | 32 | initialScrollToCenter: Ember.on('didInsertElement', function() { 33 | return this.scrollToCenter(); 34 | }), 35 | 36 | scrollToCenter: Ember.observer('currentPane', function() { 37 | var menu = this; 38 | var activeLink = this.get('activeLink'); 39 | 40 | if (!activeLink) { 41 | return; 42 | } 43 | 44 | var ms = 200; 45 | if (menu.get('isFirstRender')) { 46 | menu.set('isFirstRender', false); 47 | ms = 0; 48 | } 49 | 50 | menu.set('animating', true); 51 | activeLink.scrollIntoCenter(menu, ms).then(function() { 52 | menu.set('animating', false); 53 | }); 54 | }) 55 | }); 56 | -------------------------------------------------------------------------------- /app/components/ps-pane.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ChildComponent from 'ember-cli-panels/mixins/child-component'; 3 | import findComponentByName from 'ember-cli-panels/utils/find-component-by-name'; 4 | 5 | export default Ember.Component.extend(ChildComponent, { 6 | name: Ember.computed.alias('pane.name'), 7 | scrolling: false, 8 | 9 | classNames: 'ps-pane', 10 | 11 | parentName: 'panel', // for ChildComponent 12 | panel: findComponentByName('ps-panel'), 13 | animating: Ember.computed.alias('panel.animating'), 14 | 15 | addScrollHandler: Ember.on('didInsertElement', function() { 16 | this.$().on('scrollstart', this.scrollstart.bind(this)); 17 | this.$().on('scrollstop', this.scrollstop.bind(this)); 18 | }), 19 | 20 | setupHeight: Ember.on('didInsertElement', function(){ 21 | // cannot target panes using 'this.$()' because they have different 22 | // offsets when initially inserted into the DOM 23 | var offset = $('.ps-pane').offset().top 24 | var height = $(window).height() 25 | var paneHeight = height - offset 26 | $('.ps-pane').height(paneHeight); 27 | }), 28 | 29 | removeScrollHandler: Ember.on('willDestroyElement', function() { 30 | this.$().off('scrollstart', this.scrollstart.bind(this)); 31 | this.$().off('scrollstop', this.scrollstop.bind(this)); 32 | }), 33 | 34 | scrollstart: function() { 35 | this.set('scrolling', true); 36 | }, 37 | 38 | scrollstop: function() { 39 | this.set('scrolling', false); 40 | }, 41 | 42 | _hideAnimation: function() { 43 | if (this.get('hideAnimation')) { 44 | return this.get('hideAnimation')(this); 45 | } 46 | 47 | return this.$().hide(); 48 | }, 49 | 50 | _showAnimation: function() { 51 | if (this.get('showAnimation')) { 52 | return this.get('showAnimation')(this); 53 | } 54 | 55 | return this.$().show(); 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /addon/mixins/draggable-panel.js: -------------------------------------------------------------------------------- 1 | /* global Hammer */ 2 | 3 | import Ember from 'ember'; 4 | 5 | // Can only be mixed into ps-panel 6 | export default Ember.Mixin.create({ 7 | setupPanHammer: Ember.on('didInsertElement', function() { 8 | if (!this.get('draggable')) { 9 | return; 10 | } 11 | 12 | var panel = this; 13 | 14 | var hammer = new Hammer(this.$()[0], { 15 | touchAction: 'pan-y' 16 | }); 17 | 18 | this.set('hammer', hammer); 19 | 20 | hammer.get('pan').set({ 21 | direction: Hammer.DIRECTION_HORIZONTAL 22 | }); 23 | 24 | hammer.on('panmove', function(event) { 25 | return panel.panmove(event); 26 | }); 27 | 28 | hammer.on('panend', function(event) { 29 | return panel.panend(event); 30 | }); 31 | }), 32 | 33 | threshold: Ember.computed('paneWidth', function() { 34 | return this.get('elWidth') * 0.30; 35 | }), 36 | 37 | disallowPan: Ember.computed.or('isPaneScrolling', 'animating'), 38 | 39 | disallowPanChanged: Ember.observer('disallowPan', 'hammer', function() { 40 | var hammer = this.get('hammer'); 41 | if (!hammer) { 42 | return; 43 | } 44 | 45 | if (this.get('disallowPan')) { 46 | hammer.get('pan').set({enable: false}); 47 | } else { 48 | hammer.get('pan').set({enable: true}); 49 | } 50 | }), 51 | 52 | panmove: function(event) { 53 | var containerXOffset = this.get('containerXOffset'); 54 | var offset = containerXOffset + event.deltaX; 55 | 56 | var xSwipe = Math.abs(event.deltaX) >= Math.abs(event.deltaY); 57 | 58 | // animate only if pan is a horizontal swipe 59 | if (xSwipe) { 60 | Ember.$.Velocity(this.get('$container'), { 61 | translateX: offset 62 | }, { duration: 0 }); 63 | } 64 | }, 65 | 66 | panend: function(event) { 67 | return this.panChooseAnimation(event); 68 | }, 69 | 70 | panChooseAnimation: function(event) { 71 | var deltaX = event.deltaX; 72 | var deltaY = event.deltaY; 73 | var xSwipe = Math.abs(deltaX) >= Math.abs(deltaY); 74 | 75 | var threshold = this.get('threshold'); 76 | var prevPane = this.get('prevPane'); 77 | var nextPane = this.get('nextPane'); 78 | 79 | if (prevPane && (deltaX > threshold) && xSwipe) { 80 | return this.animateToPane(prevPane); 81 | 82 | } else if ((deltaX > 0) && xSwipe) { 83 | return this.animateToCurrentPane(); 84 | 85 | } else if ((nextPane && Math.abs(deltaX) > threshold) && xSwipe) { 86 | return this.animateToPane(nextPane); 87 | 88 | } else { 89 | return this.animateToCurrentPane(); 90 | } 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-cli-panels 2 | 3 | Disclaimer: This is a WIP undergoing heavy development. Breaking changes may be ahead. 4 | 5 | ## Tip 6 | 7 | Make sure to have a meta tag like this in your app if you want to prevent 8 | zooming and weird scrolling issues. 9 | 10 | ```html 11 | 12 | ``` 13 | 14 | ## Installation 15 | 16 | `npm install ember-cli-panels --save-dev` 17 | 18 | ## Getting Started 19 | 20 | This package exposes a `ps-panel` component that wraps dynamically created `ps-pane` components. 21 | 22 | The `ps-panel` is defined in your template like so: 23 | 24 | ```javascript 25 | // app/templates/index.hbs 26 | 27 | {{ps-panel 28 | draggable=true 29 | currentPaneName=pane 30 | paneControllers=paneControllers 31 | showAnimation=showAnimation 32 | hideAnimation=hideAnimation 33 | }} 34 | ``` 35 | 36 | Where `currentPaneName` is a pane you would like to initially present when rendering the template, `paneControllers` is a list of controllers that are associated with each pane, and `showAnimation`, `hideAnimation`, are [liquid-fire](https://github.com/ef4/liquid-fire) animations that handle the transitions. 37 | 38 | `ps-panel` components are of two kinds, static and draggable. Draggable allows for horizontal swipe-to-navigate functionality between panes, and static (by removing `draggable=true`) will allow for immediate pane switching based on links or actions. 39 | 40 | There is some setup required in the controller for the template. Specifically, a `pane` query param needs to be registered and initialized with the pane that is shown on rendering. This is the `pane` property passed into the `ps-pane` above. 41 | 42 | Optionally, in the controller for this template you can extend from `PanelController`. 43 | 44 | ```javascript 45 | // app/controllers/index.js 46 | 47 | import PanelController from 'ember-cli-panels/controllers/panel'; 48 | import initPaneControllers from 'ember-cli-panels/utils/init-pane-controllers'; 49 | 50 | export default PanelController.extend({ 51 | // define this property to declare the initially rendered pane 52 | pane: 'panes/main-nav/upcoming', 53 | 54 | // order here reflects left/right order of panes in template 55 | paneControllers: initPaneControllers([ 56 | 'panes/main-nav/rsvp', 'panes/main-nav/upcoming', 'panes/main-nav/past-events' 57 | ]), 58 | }); 59 | ``` 60 | 61 | This will set up the `pane` query parameter and provide default `showAnimation` and `hideAnimation` functions. These can be overridden if need be by importing the `animate` function from liquid-fire. 62 | 63 | A pane is defined by a file in the `templates/` folder and a matching file in the `controllers/` folder. So the above pane property will look for a template in `app/templates/panes/main-nav/upcoming` and a controller in `app/controllers/panes/main-nav/upcoming`. 64 | 65 | ## Running 66 | 67 | * `ember server` 68 | * Visit your app at http://localhost:4200. 69 | 70 | ## Running Tests 71 | 72 | * `ember test` 73 | * `ember test --server` 74 | 75 | ## Building 76 | 77 | * `ember build` 78 | 79 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/). 80 | -------------------------------------------------------------------------------- /app/components/ps-panel.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import ComponentRegistry from 'ember-cli-panels/mixins/component-registry'; 3 | import DraggablePanelMixin from 'ember-cli-panels/mixins/draggable-panel'; 4 | import animate from 'ember-cli-panels/utils/animate'; 5 | 6 | export default Ember.Component.extend(ComponentRegistry, DraggablePanelMixin, { 7 | animating: false, 8 | 9 | classNames: 'ps-panel', 10 | 11 | childComponentsName: 'paneComponents', // for ComponentRegistry 12 | paneControllers: Ember.A([]), // public [1,2,3] 13 | paneComponents: Ember.A([]), 14 | currentPaneName: null, // public 15 | currentPane: null, 16 | 17 | $container: null, 18 | $panel: null, 19 | elWidth: 0, 20 | containerWidth: 0, 21 | containerXOffset: 0, 22 | isFirstRender: true, 23 | 24 | updateVisiblePane: Ember.observer('currentPaneName', 'paneComponents.[]', 'paneControllers.[]', function() { 25 | // Guard to make sure all paneControllers are rendered before running any animations. 26 | if (this.get('paneComponents.length') !== this.get('paneControllers.length')) { 27 | return; 28 | } 29 | 30 | if (this.get('animating')) { 31 | return; 32 | } 33 | 34 | var hasShownPane = false; 35 | var currentPaneName = this.get('currentPaneName'); 36 | var component = this; 37 | 38 | var animations = this.get('paneComponents').map(function(pane, index) { 39 | if (pane.get('name') === currentPaneName) { 40 | hasShownPane = true; 41 | this.set('currentPane', pane); 42 | 43 | return Ember.RSVP.resolve(pane._showAnimation()).then(function() { 44 | var ms; 45 | 46 | if (component.get('isFirstRender')) { 47 | component.set('isFirstRender', false) 48 | ms = 0; 49 | } 50 | 51 | return component.animateToPaneAtIndex(index, ms); 52 | }); 53 | 54 | } else { 55 | return Ember.RSVP.resolve(pane._hideAnimation()); 56 | } 57 | }, this); 58 | 59 | if (!hasShownPane) { 60 | throw new Ember.Error('Could not find pane with name "' + currentPaneName + '" to show.'); 61 | } 62 | 63 | return Ember.RSVP.all(animations).finally(function() { 64 | component.send('stopAnimating'); 65 | }); 66 | }), 67 | 68 | paneIndex: Ember.computed('paneComponents.[]', 'currentPane', function() { 69 | return this.get('paneComponents').indexOf(this.get('currentPane')); 70 | }), 71 | 72 | prevPane: Ember.computed('paneComponents.[]', 'paneIndex', function() { 73 | return this.get('paneComponents')[this.get('paneIndex') - 1] 74 | }), 75 | 76 | nextPane: Ember.computed('paneComponents.[]', 'paneIndex', function() { 77 | return this.get('paneComponents')[this.get('paneIndex') + 1] 78 | }), 79 | 80 | scrollingPanes: Ember.computed.filterBy('paneComponents', 'scrolling', true), 81 | isPaneScrolling: Ember.computed('scrollingPanes.[]', function() { 82 | return this.get('scrollingPanes.length') > 0; 83 | }), 84 | 85 | setupJqCache: Ember.on('didInsertElement', function() { 86 | var $panel = this.$(); 87 | this.set('$panel', $panel); 88 | this.set('$container', $panel.find('.ps-panel-container')); 89 | }), 90 | 91 | containerWidthChanged: Ember.observer('containerWidth', '$container', 92 | 'elWidth', 'paneComponents.[]', function() { 93 | var $container = this.get('$container'); 94 | if ($container) { 95 | $container.width(this.get('containerWidth')); 96 | } 97 | 98 | this.get('paneComponents').forEach(function(component) { 99 | if (this.get('elWidth')) { 100 | component.updateWidth(this.get('elWidth')) 101 | } 102 | }, this); 103 | }), 104 | 105 | animateToPaneAtIndex: function(index, ms) { 106 | if (this.get('animating')) { 107 | return; 108 | } 109 | 110 | if (ms !== 0 && !ms) { 111 | ms = 325; 112 | } 113 | 114 | var component = this; 115 | 116 | component.send('startAnimating'); 117 | 118 | var containerXOffset = component.xOffsetForIndex(index); 119 | this.set('containerXOffset', containerXOffset); 120 | 121 | var pane = this.get('paneComponents')[index]; 122 | 123 | return animate(component.get('$container'), { 124 | translateX: containerXOffset 125 | }, ms).then(function() { 126 | component.setProperties({ 127 | currentPane: pane, 128 | currentPaneName: pane.get('name') 129 | }); 130 | component.send('stopAnimating'); 131 | }); 132 | }, 133 | 134 | animateToPane: function(pane) { 135 | var index = this.get('paneComponents').indexOf(pane); 136 | 137 | return this.animateToPaneAtIndex(index, 200); 138 | }, 139 | 140 | updateContainerWidth: function(singlePaneWidth) { 141 | var newWidth = singlePaneWidth * this.get('paneControllers.length'); 142 | this.set('elWidth', singlePaneWidth); 143 | this.set('containerWidth', newWidth); 144 | }, 145 | 146 | animateToCurrentPane: function() { 147 | return this.animateToPaneAtIndex(this.get('paneIndex')); 148 | }, 149 | 150 | xOffsetForIndex: function(index) { 151 | var elWidth = this.get('elWidth'); 152 | var offset = -(index * elWidth); 153 | 154 | return offset; 155 | }, 156 | 157 | actions: { 158 | startAnimating: function() { 159 | this.set('animating', true); 160 | }, 161 | 162 | stopAnimating: function() { 163 | this.set('animating', false); 164 | } 165 | } 166 | }); 167 | --------------------------------------------------------------------------------