Ember Vocab, Terms, and More!
Ember Application
Wrapper around all of our logic.
Gets things started out.
Says where on the page to put the Ember app (usually take over the entire body
element).
Usually you won't need to modify this
Router - app/router.js
This is what lets Ember know what URLs the application responds to and what routes to load based on the current URL. It also manages transitioning between routes and waiting for data to load.
We manage and set this up by declaring routes within the Router.map
callback in app/router.js
.
Also allows configuration for Router.location
which defines how to read the URL.
Usually this is for hash or no hash to support things like Github Pages.
Route
Defines how to transition to a particular page or part of the application.
Most of the work for routes is accomplished with route method hooks:
model
- Loads data (usually from a fetch request or Ember Data), waits for it to complete (or fail) and loads that into the corresponding template as the
model
variable.- If the model function returns a failed promise, Ember handles the error by going to an error route
- Loads data (usually from a fetch request or Ember Data), waits for it to complete (or fail) and loads that into the corresponding template as the
beforeModel
- Allows you to do work and checks before loading data with the
model
hook
- Allows you to do work and checks before loading data with the
afterModel
- After loading data with
model
, gives a final place to check that we shouldn't redirect before loading the template
- After loading data with
Routes can be nested to allow multiple routes to be grouped together and to share a common data source. For instance take the following route structure:
post - /posts
|--post.index
|--post.edit - /:id/edit
|--post.detail - /:id/more
|--post.new
`
When starting the app, first the post
route and model
hook are started.
In our example, this method loads this.store.findAll('post')
to get all post records from the API.
Now our post
template could look like this:
{% raw %}
<div class="sidebar">
<ul class="post-list">
{{#each model as |post|}}
<li class="post-list__item">
{{#link-to "post.detail" post}}
{{post.name}}
{{/link-to}}
</li>
{{else}}
<li class="post-list__item--empty">
Please add a post!
</li>
{{/each}}
</ul>
</div>
<div class="main">
{{outlet}}
</div>
{% endraw %}
Now if we go to /posts
this will load the post.index
route which will be surrounded by the above template.
Any HBS from the post.index
template will be put in the {% raw %}{{outlet}}{% endraw %}
area.
Because of nesting, the sidebar will show up for ALL of the post.___
routes.
If a child route does not define its own model and does not have any dynamic segments, it will inherit the parent model
data.
This means that we do not have to rewrite this.store.findAll('post')
in our post.index
route.
But, we will have to likely fetch data in our post.detail
and post.edit
pages or else we'll just get an object with an id
parameter.
Templates
Ember uses HTMLBars which is like Handlebars that actually knows about the DOM and how HTML works. At it's core HTMLBars is just HTML that we can start adding bits of logic and templating to.
Variables can be used by surrounding it in {% raw %}{{}}{% endraw %}
.
So, if we wanted to get the firstName
property from the user
variable we would write {% raw %}{{ user.firstName }}{% endraw %}
(the spaces are just for readability).
NOTE The curly braces in HTMLBars are not plain Javascript! So you cannot run functions regularly! So
{{ (new Date()).getHours() }}
. Instead to use Javascript, we will have to use helpers or components to help format data into strings
Templating Helpers
each
-{{#each arr as |item|}} ... {{/each}}
if
- Inline Mode (Helpful for adding/removing classes)
{{if check "true-class" "false-class"}}
- Block Mode (Most common)
{{#if check}} ... {{else}} ... {{/if}}
(the else is optional)
- Inline Mode (Helpful for adding/removing classes)
unless
- Works the same asif
but happens when thecheck
is falsey
Special Helpers
outlet
- This is where nested templates will be renderedaction
- Used to listen for browser and user events- Put within the opening tag of an element or as an argument to component
<button {{action "save"}}>Save</button>
{{input onchange=(action "alertChange")}}
- Optional
on
attribute can specify what type of event to listen to (defaults to 'click')<form {{action "saveUser" on="submit"}}> ... </form>
- Arguments can be passed in to actions after the action name
<form {{action "saveUser" user firstName lastName on="submit"}}> ... </form>
- This would run the
saveUser
method like this:saveUser(user, firstName, lastName)
- Put within the opening tag of an element or as an argument to component
Simple Form
Ember Component that helps to capture submit events on forms and groups all changes from multiple inputs as a single object.
Basic Example:
Let's create an edit form for post.edit
assuming that the current post model is loaded in the template as model
:
{% raw %}{{#simple-form startingValues=model onsubmit=(action "savePost" model) as |formValues|}}
{{input value=formValues.title}}
<button>Save</button>
{{/simple-form}}{% endraw %}
startingValues
- What should the form be filled in with to start?onsubmit
- What action should be run when the form is submitted.- Here we pass in the model that we want to save
- Block Parameters - The
simple-form
componentyield
s an object of values that will be sent to our action after themodel
that we passed in
Handling the onsubmit
action:
In our controller we can handle the onsubmit action. Since we added an argument of our model, we will get that as our first argument in our action handler. Then, we will get the object of changes within our form. Finally, we will get a function to reset the form (although this is often not needed since we'll usually redirect after saving).
import Ember from 'ember';
export default Ember.Controller.extend({
checkValidInput(input) {
return input.title === '' || input.name === '';
},
actions: {
savePost(post, form, reset) {
if (this.checkValidInput(form)) {
alert('Try that again');
reset();
} else {
// Fill in the post model with all the values from the `form` object
post.setProperties(form);
// Save the post model to the server
post.save().then(() => {
this.transitionToRoute('post.index');
});
}
}
}
});
Controller
How to manage data and AFTER the route has loaded the current page.
Usually, this is where we handle user actions in the actions
object.
NOTE Any property on the controller will be directly available in the template. This is why
this.set
will immediately update what the user sees in the browser.
- Ember Data (DS)
An easy (or easier) way to manage large sets of related data stored somewhere (usually on a server).
Store
- Local inventory
- Ask the store for different pieces of data and it figures out where to find them from
- Available as
this.store
in Controllers and Routes - Important Functions:
createRecord(modelName, startingProperties)
- Create new a local copy of the specified model withstartingProperties
(does not save yet)- Returns the newly created model
modelName
is required and should match the module name withinapp/models
startingProperties
is optional, but saves ALOT of time :)- Ex.
this.store.createRecord('post', {title: 'How to write JS apps'})
findAll(modelName)
- Find all the records for a particular model from the serverfindRecord(modelName, id)
- Find a record for a particular model based on itsid
from the server
Model (Definition) -
DS.Model.extend
- Located inapp/models
- Describes the attributes and Relationships that should be synced with the server
Model Generator Command
NOTE Rember the
-p
flag so this file ends up in theapp/models
directory and not in a random podLet's create a
post
model that belongs to auser
and has atitle
that is a string:ember g model post user:belongsTo:user title:string -p
// app/models/post.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user'),
title: DS.attr('string')
});
* Let's create a `user` model that has many posts and has first and last names:
- `ember g model user post:hasMany:post firstName:string lastName:string -p`
// app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
post: DS.hasMany('post'),
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
Model Relationships
- Says how different models are related to each other
- Ex. An author has written a lot of books so if we stored that we would say that:
book
belongsToauthor
author
hasManybook
- NOTE Ember does not have any
hasOne
relationship- Ex. A hooman has one doge
doge
belongsTohooman
hooman
belongsTodoge
- Ex. A hooman has one doge
- Ex. An author has written a lot of books so if we stored that we would say that:
- Says how different models are related to each other
Model (Instance) - Returned by
findAll
,createRecord
,findRecord
- Object to represent and manage a particular record in the API
- Important Functions:
set(propertyName, value)
- Change a single property on an Ember data modelsetProperties(valuesObject)
- Set all the values on the current model with values from thevaluesObject
post.setProperties({body: 'This is an awesome post', numOfComments: 5})
incrementProperty(propertyName, value = 1)
- Add 1 (or the value specified) to a propertytoggleProperty(propertyName)
- Change a property fromtrue
tofalse
or the opposite...save()
- Figures out how to sync this current record with the API- For instance, for records that are new (usually made by
createRecord
) make aPOST
request - For records that are being updated make a
PUT
orPATCH
request
- For instance, for records that are new (usually made by
destroyRecord()
- Delete a record locally and make aDELETE
request to the server
Adapter
- How to actually connect to the data source and what type of requests to make
- You usually won't write your own
- Some things that you WILL usually customize though
host
- What is the domain name of the API you are connecting to (http://myapi.com
)namespace
- Is there anything that comes between thehost
and the model names (usuallyapi
)
- Serializer
- Translator in and out so that API gets the format it wants, and the store gets the format it wants
- YOU WILL NOT WRITE THESE YOURSELF!!!
Component
yield
link-to
- Method Hooks
didReceiveAttrs
Helpers
Mirage
Addon that acts like a fake API useful for testing and when you don't have a working API up and running. Also useful when you want to try to see what your app looks like with strange data or error states.
- Scenario
- What fake data should be created when working in development mode
- NOTE Scenarios are NOT loaded in tests
server.create(modelName, properties)
- Adds a single record to the "database"server.createList(modelName, numberOfCopies, properties)
- Adds a multiple records to the "database"
- Factory
- How to fill in data when
create
orcreateList
is run, but a property wasn't specified - This usually where you will be using
faker
- Default values to help create fake records in the "database"
- How to fill in data when
- Model
- Defines that this resource exists in the Mirage "database"
- NOTE This is different than Ember Data models!!!
- Only needed to define relationships between different resources in Mirage
- NO REALLY THIS ISN'T CONNECTED TO
app/models
... DON'T MIX THEM!
- Serializer
- Translator between incoming requests to the "server" and the info sent back out
- You won't touch it
config.js
- What URLs should the "server" respond to?
- What host name and namespace is the server located on?
- Should requests be delayed by a certain amount of time to simulate network traffic?
- Tests
- Unit Test
- Really small test
- Testing a single function or class at a time
- How does this piece work in isolation
- Integration Test
- Tests a few pieces together or small user behavior
- Usually used to test Components
- Acceptance Test
- "As a user I accept that I can..."
- Might be faking some network requests
- Actually running the application
- Reset to a new app EVERY time
- Test Helpers
visit
click
fillIn
andThen
find
findWithAssert
- Unit Test
Ember CLI (Command Line Interface)
- Commands
ember new <project-name>
- Creates a new Ember projectember install
- Install addons by name and automatically setups any required files and updatespackage.json
ember s
(npm start
) - Starts a development server on port4200
, watches for changesember g
- Generates a piece of your appember g route
ember g template
ember g controller
ember g model
ember g helper
ember g adapter
.ember-cli
- Configuration for theember
commandusePods
- When set totrue
, all newly generated routes, controllers, and templates will be grouped together
Folder Structure
app
public
- Plain 'ol files. Images, fonts, extra CSS, etc- Pods vs Traditional
- Commands
Given the folowing routes:
post - /posts
|--post.index
|--post.edit - /:id/edit
|--post.detail - /:id/more
|--post.new
`
Traditional
app/
|--controllers/
| |--post/
| | |--edit.js
| | |--new.js
| |--post.js
|--routes/
| |--post/
| | |--detail.js
| | |--edit.js
| | |--index.js
| | |--new.js
| |--post.js
|--templates/
| |--post/
| | |--detail.hbs
| | |--edit.hbs
| | |--index.hbs
| | |--new.hbs
| |--post.hbs
Pods
app/
|--post/
| |--detail/
| | |--route.js
| | |--template.hbs
| |--edit/
| | |--controller.js
| | |--route.js
| | |--template.hbs
| |--index/
| | |--route.js
| | |--template.hbs
| |--new/
| | |--controller.js
| | |--route.js
| | |--template.hbs
| |--controller.js
| |--route.js
| |--template.hbs