Más contenido relacionado La actualidad más candente (20) Similar a Building a Single Page Application using Ember.js ... for fun and profit (20) Building a Single Page Application using Ember.js ... for fun and profit17. show rebates
it needs to
allow registration
allow account mgmt
provide cash out
explain how it works
show where it works
…
build a shopping list
be location aware
track all the things
52. – Ruby on Rails Guides
“conventions will speed up development, keep
your code concise and readable and - most
important - these conventions allow you an easy
navigation inside your application.”
https://en.wikibooks.org/wiki/Ruby_on_Rails/Getting_Started/Convention_Over_Configuration* emphasis mine
53. structure of an ember app
app
├── components
├── controllers
├── helpers
├── models
├── routes
├── styles
└── templates
└── components
tests
├── helpers
├── integration
│ └── components
└── unit
59. knowledge in the head
vs.
knowledge in the world
adapted from concepts in Don Norman’s
The Design of Everyday Things
61. *Icons made Freepik from www.flaticon.com is licensed by CC BY 3.0
used by
few
used by
many
more knowledge
in the head
62. *Icons made Freepik from www.flaticon.com is licensed by CC BY 3.0
used by
few
used by
many
more knowledge
in the head
more knowledge
in the world
63. *Icons made Freepik from www.flaticon.com is licensed by CC BY 3.0
used by
few
used by
many
more knowledge
in the head
more knowledge
in the world
64. *Icons made Freepik from www.flaticon.com is licensed by CC BY 3.0
used by
few
used by
many
more knowledge
in the head
more knowledge
in the world
80. ✓Array comprehensions
✓Arrow functions
✓Async functions
✓Async generator functions
✓Classes
✓Class properties
✓Computed property names
✓Constants
✓Decorators
✓Default parameters
✓Destructuring
✓Exponentiation operator
✓For-of
✓Function bind
✓Generators
✓Generator comprehensions
✓Let scoping
✓Modules
✓Module export extensions
✓Object rest/spread
✓Property method assignment
✓Property name shorthand
✓Rest parameters
✓React
✓Spread
✓Template literals
✓Type annotations
✓Unicode regex
111. structure of our app
app
├── components
├── controllers
├── helpers
├── models
├── routes
├── styles
└── templates
└── components
tests
├── helpers
├── integration
│ └── components
└── unit
116. I18n
1 <!-- ./app/templates/index.hbs -->
2 <div class="home-feature">
3 <div class="feature-content">
4 <h1>{{t 'index.headline'}}</h1>
5 <h3>{{t 'index.subTitle'}}</h3>
6 </div>
7 </div>
1 // ./app/locales/en/translations.js
2 export default {
3 'index': {
4 'headline': 'Frozen Bananas!',
5 'subTitle': 'Coming Soon'
6 }
7 };
118. I18n
1 <!-- ./app/templates/index.hbs -->
2 <div class="home-feature">
3 <div class="feature-content">
4 <h1>{{t 'index.headline'}}</h1>
5 <h3>{{t 'index.subTitle'}}</h3>
6 </div>
7 </div>
1 // ./app/locales/en/translations.js
2 export default {
3 'index': {
4 'headline': 'Frozen Bananas!',
5 'subTitle': 'Coming Soon'
6 }
7 };
121. ./tests/acceptance/index-test.js
1 // ...
2
3 test('visiting /index', function(assert) {
4 assert.expect(3);
5 visit('/');
6
7 andThen(function() {
8 assert.equal(currentURL(), '/');
9 let headline = find('.home-feature h1');
10 assert.equal(headline.length, 1);
11 assert.equal(headline.text(), 'Frozen Bananas!');
12 });
13 }); Promise explainer Here?
123. ./tests/acceptance/index-test.js
1 // ...
2
3 test('visiting /index', function(assert) {
4 assert.expect(3);
5 visit('/');
6
7 andThen(function() {
8 assert.equal(currentURL(), '/');
9 let headline = find('.home-feature h1');
10 assert.equal(headline.length, 1);
11 assert.equal(headline.text(), 'Frozen Bananas!');
12 });
13 });
125. √ ok 1 PhantomJS 2.0 - Acceptance | index: visiting /index
129. ./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 this.route('about');
10 });
11
12 export default Router;
131. ./app/templates/about.hbs
1 <div class="container">
2 {{#md-card
3 title=(t 'about.title')
4 class="teal"
5 titleClass="white-text"
6 bodyClass="white-text"
7 id="address-card"}}
8 {{#md-card-content class="white-text"}}
9 <p>{{t 'about.number'}}</p>
10 <p>{{t 'about.street'}}</p>
11 {{/md-card-content}}
12 {{/md-card}}
13 </div>
133. ./app/templates/about.hbs
1 <div class="container">
2 {{#md-card
3 title=(t 'about.title')
4 class="teal"
5 titleClass="white-text"
6 bodyClass="white-text"
7 id="address-card"}}
8 {{#md-card-content class="white-text"}}
9 <p>{{t 'about.number'}}</p>
10 <p>{{t 'about.street'}}</p>
11 {{/md-card-content}}
12 {{/md-card}}
13 </div>
135. ./app/templates/index.hbs
1 <div class="home-feature">
2 <div class="feature-content">
3 <h1>{{t 'index.headline'}}</h1>
4 <h3>{{t 'index.subTitle'}}</h3>
5 <div class="about-link">
6 {{#link-to 'about'}}
7 <img src="/images/banana_grabber.png">
8 {{/link-to}}
9 </div>
10 </div>
11 </div>
140. √ ok 1 PhantomJS 2.0 -
Acceptance | about: visiting /about from index
143. ./app/models/company-address.js
1 import DS from 'ember-data';
2
3 export default DS.Model.extend({
4 name: DS.attr('string'),
5 street1: DS.attr('string'),
6 street2: DS.attr('string'),
7 city: DS.attr('string'),
8 state: DS.attr('string'),
9 zip: DS.attr('string') //string not number
10 });
155. ./app/templates/about.hbs
1 <div class="container">
2 {{#md-card
3 title=model.lastObject.name
4 class="teal"
5 titleClass="white-text"
6 bodyClass="white-text"
7 id="address-card"}}
8 {{#md-card-content class="white-text"}}
9 <p>{{model.lastObject.street1}}</p>
10 <p>{{model.lastObject.street2}}</p>
11 <p>
12 {{model.lastObject.city}}, {{model.lastObject.state}}
13 {{model.lastObject.zip}}
14 </p>
15 {{/md-card-content}}
16 {{/md-card}}
17 </div>
Demo, show error page
179. ./app/templates/index.hbs
1 <div class="home-feature">
2 <div class="feature-content row">
3 <div class="col m6">
4 <h1>{{t 'index.headline'}}</h1>
5 <h3>{{t 'index.subTitle'}}</h3>
6 <div class="about-link">
7 {{#link-to 'about'}}
8 <img src="/images/banana_grabber.png">
9 {{/link-to}}
10 </div>
11 </div>
12 <div class="subscribe-container col m6">
13 {{subscribe-form model=model}}
14 </div>
15 </div>
16 </div>
184. ./app/routes/index.js
1 import Ember from 'ember';
2
3 export default Ember.Route.extend({
4 model() {
5 return this.get('store').createRecord('email-subscription');
6 }
7 });
186. ./app/templates/components/subscribe-form.hbs
1 {{#md-card title=title}}
2 {{#if saved}}
3 {{#md-card-content}}
4 {{t 'subscribe.successMessage' email=model.email}}
5 {{/md-card-content}}
6 {{else}}
7 {{#md-card-content}}
8 {{md-input value=model.email label=(t 'subscribe.email.label')
9 type='email' validate=true}}
10 {{md-check checked=model.marketing
11 name=(t 'subscribe.marketing.label')}}
12 {{#if saving}}
13 {{md-loader}}
14 {{/if}}
15 {{/md-card-content}}
16 {{#md-card-action}}
17 <button {{action 'subscribe'}}>
18 {{t 'subscribe.submit'}}
19 </button>
20 {{/md-card-action}}
21 {{/if}}
22 {{/md-card}}
188. ./app/templates/components/subscribe-form.hbs
1 {{#md-card title=title}}
2 {{#if saved}}
3 {{#md-card-content}}
4 {{t 'subscribe.successMessage' email=model.email}}
5 {{/md-card-content}}
6 {{else}}
7 {{#md-card-content}}
8 {{md-input value=model.email label=(t 'subscribe.email.label')
9 type='email' validate=true}}
10 {{md-check checked=model.marketing
11 name=(t 'subscribe.marketing.label')}}
12 {{#if saving}}
13 {{md-loader}}
14 {{/if}}
15 {{/md-card-content}}
16 {{#md-card-action}}
17 <button {{action 'subscribe'}}>
18 {{t 'subscribe.submit'}}
19 </button>
20 {{/md-card-action}}
21 {{/if}}
22 {{/md-card}}
190. ./app/templates/components/subscribe-form.hbs
1 {{#md-card title=title}}
2 {{#if saved}}
3 {{#md-card-content}}
4 {{t 'subscribe.successMessage' email=model.email}}
5 {{/md-card-content}}
6 {{else}}
7 {{#md-card-content}}
8 {{md-input value=model.email label=(t 'subscribe.email.label')
9 type='email' validate=true}}
10 {{md-check checked=model.marketing
11 name=(t 'subscribe.marketing.label')}}
12 {{#if saving}}
13 {{md-loader}}
14 {{/if}}
15 {{/md-card-content}}
16 {{#md-card-action}}
17 <button {{action 'subscribe'}}>
18 {{t 'subscribe.submit'}}
19 </button>
20 {{/md-card-action}}
21 {{/if}}
22 {{/md-card}}
192. ./app/components/subscribe-form.js
1 import Ember from 'ember';
2
3 export default Ember.Component.extend({
4 i18n: Ember.inject.service('i18n'),
5 title: Ember.computed('saved', function() {
6 if (this.get('saved')) {
7 return this.get('i18n').t('subscribe.successHeader');
8 } else {
9 return this.get('i18n').t('subscribe.header');
10 }
11 }),
12 saved: false,
13 actions: {
14 subscribe() {
15 this.set('saving', true);
16 this.get('model').save().then(() => {
17 this.set('saving', false);
18 this.set('saved', true);
19 }, () => {
20 //error handling here
21 });
22 }
23 }
24 });
194. ./app/components/subscribe-form.js
1 import Ember from 'ember';
2
3 export default Ember.Component.extend({
4 i18n: Ember.inject.service('i18n'),
5 title: Ember.computed('saved', function() {
6 if (this.get('saved')) {
7 return this.get('i18n').t('subscribe.successHeader');
8 } else {
9 return this.get('i18n').t('subscribe.header');
10 }
11 }),
12 saved: false,
13 actions: {
14 subscribe() {
15 this.set('saving', true);
16 this.get('model').save().then(() => {
17 this.set('saving', false);
18 this.set('saved', true);
19 }, () => {
20 //error handling here
21 });
22 }
23 }
24 });
196. ./app/mirage/config.js
1 export default function() {
2 this.timing = 2000;
3
4 this.get('/companyAddresses', 'company-addresses');
5
6 this.post('/emailSubscriptions',
7 'email-subscriptions');
8 }
199. ./tests/integration/components/subscribe-form-test.js
1 test('it renders', function(assert) {
2 assert.expect(3);
3
4 this.render(hbs`{{subscribe-form}}`);
5
6 assert.equal(
7 this.$('.card-title').text().trim(),
8 'Subscribe for updates on the stand.'
9 );
10
11 assert.equal(
12 this.$('.input-field label').text().trim(),
13 'Please enter your email address.'
14 );
15
16 assert.equal(
17 this.$('.materialize-checkbox label').text().trim(),
18 'Yes I would like to receive marketing material from Bluth sponsors.
19 '
20 );
21
22 });
200. ./tests/acceptance/index-test.hbs
1 test('subscribe for updates', function(assert) {
2 visit('/');
3
4 fillIn(
5 '.subscribe-container .input-field input',
6 'veep@whitehouse.gov'
7 );
8 click('.card-action button');
9 andThen(() => {
10 // stays on index
11 assert.equal(currentURL(), '/');
12
13 assert.equal(
14 find('.subscribe-container .card-title').text().trim(),
15 'Thanks for signing up.'
16 );
17 assert.equal(
18 find('.card p').text().trim(),
19 'We'll send all of our updates to veep@whitehouse.gov.'
20 );
21 });
22
23 });