An introduction to Ember.js and surrounding technologies
@jeffreybiles
www.emberscreencasts.com
`import Ember from 'ember'`
`import BattleEvent from '../../models/battle-event'`
BattleModalController = Ember.Controller.extend
needs: ['application', 'battle-queue']
player: Ember.computed.alias('session.currentUser')
battleQueue: Ember.computed.alias("controllers.battle-queue")
enemyLost: false
playerLost: false
minigame: false
selectMonster: false
message: 'battle starting'
messageShowing: false
showingStats: Ember.computed 'minigame', 'selectMonster', ->
!@get('minigame') and !@get('selectMonster')
handleEnemyMonsterDefeat:->
queue = @get('battleQueue')
if @get('enemy.defeated')
queue.addPriorityMessage("You have defeated #{@get('enemy.name')}.") unless @get("enemy.randomBattleArea")
queue.addEvent(@eventify(this, 'exitBattle', {playerVictory: true}))
else
queue.addPriorityEvent(@eventify(@get('enemy'), 'switchToNextMonster'))
expMessage = @get('playerMonster').gainExperience({defeated: @get('enemyMonster')})
@handlePlayerMonsterGainingALevel() if @get('playerMonster.readyToLevel')
queue.addPriorityMessage(expMessage)
queue.addPriorityMessage("You defeated #{@get('enemyMonster.name')}.")
handlePlayerMonsterGainingALevel: ->
beforeStats = @get('playerMonster').statsHash()
@get('playerMonster').levelUp()
afterStats = @get('playerMonster').statsHash()
monsterName = @get('playerMonster.name')
@get('battleQueue').addPriorityMessage("<table class='level-up-table margin-auto'>
<tr>
<td>Health</td>
<td><span class='icon-heart'></span></td>
<td class='before-stats'>#{beforeStats['health']}</td>
<td>→</td>
<td class='after-stats'>#{afterStats['health']}</td>
</tr>
<tr>
<td>Strength</td>
<td>⚔</td>
<td class='before-stats'>#{beforeStats['strength']}</td>
<td>→</td>
<td class='after-stats'>#{afterStats['strength']}</td>
</tr>
<tr>
<td>Defense</td>
<td>⛨</td>
<td class='before-stats'>#{beforeStats['defense']}</td>
<td>→</td>
<td class='after-stats'>#{afterStats['defense']}</td>
</tr>
</table>")
@get('battleQueue').addPriorityMessage("#{monsterName} gained a level!!!! +1<span class='icon-muscle'></span><br>#{monsterName} is now level #{@get('playerMonster.level')}", 'soundfx/victory')
handlePlayerMonsterDefeat: ->
queue = @get('battleQueue')
if @get('player.defeated')
queue.addPriorityMessage("You have been defeated.")
queue.addEvent(@eventify(this, 'exitBattle', {playerVictory: false}))
@get("preloader").play('soundfx/loss')
@transitionToRoute('levels.try-again')
else
switchToMonsterSelect = @eventify(this, 'toggleMonsterSelect')
queue.addPriorityEvent(switchToMonsterSelect)
queue.addPriorityMessage("#{@get('playerMonster.name')} knocked out.")
battleOver: Ember.computed.or 'enemy.defeated', 'player.defeated'
exitBattle: ({playerVictory}) ->
# analyticsStats = {
# challengeId: @get('challengeLink.challenge.id')
# challengeName: @get('challengeLink.challenge.name')
# subchallengeId: @get('challengeLink.orderedItem.id')
# index: @get('challengeLink.currentIndex')
# currentMonsterId: @get("playerMonster.id")
# currentMonsterLevel: @get("playerMonster.level")
# currentMonsterHealth: @get("playerMonster.currentHp")
# enemyMonsterLevel: @get("enemyMonster.level")
# enemyMonsterTypeId: @get("enemyMonster.monsterType.id")
# enemyMonsterTypeName: @get("enemyMonster.monsterType.name")
# }
if playerVictory
@get('analytics').track('gameplay', 'progress', 'progress in challenge')
if @get('enemy.actingChallengeOrdering.next')
@get('enemy.challenge').nextItem()
else
# $.extend(analyticsStats, {category: 'gameplay', action: 'complete', label: 'complete challenge'})
@get('analytics').track('gameplay', 'complete', 'complete challenge')
@get("preloader").play('soundfx/victory')
@get('enemy.challenge.battleRecord').updateWithResults({playerVictory: true})
@transitionToRoute('level', @get("territory"))
else
@get('analytics').track('gameplay', 'exit', 'defeat or run')
@get('enemy.monsters').forEach (item) -> item.heal()
@get('player.monsters').forEach (item) -> item.heal()
@get("player.monsters").forEach (item) -> item.save()
@get('battleQueue').clear() #this is to clear the 'handleEnemyMonsterDefeat' events that the healing made happen
@queueEnded()
@send('closeModal')
territory: Ember.computed.alias('enemy.challenge.territory')
resultText: null
playerMonster: Ember.computed.alias('player.currentMonster.content')
enemyMonster: Ember.computed.alias('enemy.currentMonster')
playerOptions: Ember.computed.alias('player.userOption')
attack: ({attacker, defender, skill, isPlayer, isSuccessful}) ->
return new Ember.RSVP.Promise (resolve, reject) =>
if attacker.get('unconscious') or defender.get('unconscious')
reject()
else
attack = @store.createRecord('attack', {attacker: attacker, defender: defender, skill: skill, isSuccessful: isSuccessful})
attack.commence().then =>
elementName = attack.get('skill.element.name')
if isSuccessful
@get('preloader').image(elementName, '.attack-animation', !isPlayer)
@get('battleQueue').displayMessage("#{attack.get('attacker.name')} uses #{attack.get('skill.name')}")
@get('preloader').play("soundfx/#{elementName.toLowerCase()}-attack")
Ember.run.later(_this, ->
attack.doDamage()
multiplier = attack.get('elementalMultiplier')
if multiplier > 1
messageModifier = "very effective <span class='icon-up'></span>"
else if multiplier < 1
messageModifier = "weak <span class='icon-down'></span>"
else
messageModifier = "successful ⬌"
message = "#{attacker.get('name')}'s #{elementName} attack is #{messageModifier} against #{defender.get('element.name')}"
defendingMonster = if isPlayer then 'enemy-monster' else 'player-monster'
$(".#{defendingMonster}.monster").effect('shake', distance: 10)
@handleEnemyMonsterDefeat() if @get("enemyMonster.unconscious")
@handlePlayerMonsterDefeat() if @get("playerMonster.unconscious")
@get('preloader').play("soundfx/hit")
attack.record(this) if isPlayer
resolve("#{message} <br>#{attack.get('defender.name')} lost #{attack.get('damage')} health<br>-#{attack.get('damage')} <span class='icon-heart'></span>")
, 1700)
, => #rejection means a miss
@get('preloader').play("soundfx/miss")
attack.record(this) if isPlayer
resolve("Attack missed.")
eventify: (context, functionName, options={}) ->
promiseAction = =>
new Ember.RSVP.Promise (resolve, reject) =>
fn = context[functionName]
resolve(fn.bind(context)(options))
return BattleEvent.create(action: promiseAction, options: options)
toggleMonsterSelect: ->
@set('selectMonster', !@get('selectMonster'))
"I choose you!"
queueEnded: ->
@set('messageShowing', false)
actions:
exitBattle: ->
@exitBattle(playerVictory: false)
@transitionToRoute('level', @get('territory'))
beginMinigame: (skill) ->
@set('deployedSkill', skill)
@set('minigame', true)
useSkill: (playerIsCorrect) ->
@set('minigame', false)
@set('messageShowing', true)
queue = @get('battleQueue')
playerAttack = @eventify this, 'attack',
attacker: @get('playerMonster')
defender: @get('enemyMonster')
skill: @get('deployedSkill')
isPlayer: true
isSuccessful: playerIsCorrect
queue.addEvent(playerAttack)
enemyAttack = @eventify this, 'attack',
attacker: @get('enemyMonster')
defender: @get('playerMonster')
isPlayer: false
isSuccessful: true
queue.addEvent(enemyAttack)
Ember.run.later =>
queue.start()
toggleMonsterSelect: ->
if !@get('selectMonster') and (@get('messageShowing') or @get('minigame'))
alert("Please wait until the end of your turn to change monsters.")
else
if !@get('playerOptions.tutorialChangeCurrentMonster')
@set('playerOptions.tutorialChangeCurrentMonster', true)
@get('playerOptions').save()
@toggleMonsterSelect()
volumeToggle: ->
@get("preloader").toggleVolume()
highlightCurrentSkill: ( ->
$('.current-skill-button').toggleClass("highlighted")
$('.shiny').toggleClass("highlighted")
setTimeout((@highlightCurrentSkill).bind(this), 1500)
).on 'init'
`export default BattleModalController`
for one technology
for many technologies
If I have seen further it is by
standing on the shoulders of giants.
--Isaac Newton
If I have made awesome web apps it is by
Embering on the shoulders of Giants
--Not Isaac Newton
NAND gates
Microchips
Assembly Language
C
Javascript
jQuery
Ember
C language
Ember
javascript
jQuery
Angular
3d engines
openGL
CAD
3d game engine
until you reach the level of complexity directly below that which you wish to build
NAND gates
Microchips
Assembly Language
C
Javascript
jQuery
Ember
You might not
need jQuery
You might not
need Assembly Language
C language
jQuery
Ember
javascript
jQuery
Angular
3d engines
3d game engine
openGL
CAD
Custom Web Framework
npm install -g ember-cli
ember new giants-app
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
options: {
separator: ';'
},
dist: {
src: ['src/**/*.js'],
dest: 'dist/<%= pkg.name %>.js'
}
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
files: {
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
}
},
qunit: {
files: ['test/**/*.html']
},
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
options: {
// options here to override JSHint defaults
globals: {
jQuery: true,
console: true,
module: true,
document: true
}
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'qunit']
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('test', ['jshint', 'qunit']);
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
};
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var app = new EmberApp();
//custom imports
app.import("bower_components/jquery.cookie/jquery.cookie.js");
app.import("bower_components/moment/moment.js");
app.import('bower_components/bootstrap/dist/js/bootstrap.js');
app.import('bower_components/pickadate/lib/legacy.js');
app.import('bower_components/pickadate/lib/picker.js');
app.import('bower_components/pickadate/lib/picker.date.js');
app.import('bower_components/pickadate/lib/picker.time.js');
module.exports = app.toTree();
(ES6 modules)
ember generate component giant-tree
//app/components/giant-tree.js
import Ember from 'ember';
export default Ember.Component.extend({
});
//app/templates/components/giant-tree.hbs
{{yield}}
//tests/unit/components/giant-tree-test.js
import {
moduleForComponent,
test
} from 'ember-qunit';
moduleForComponent('giant-tree', {
// Specify the other units that are required for this test
// needs: ['component:foo', 'helper:bar']
});
test('it renders', function(assert) {
assert.expect(2);
// Creates the component instance
var component = this.subject();
assert.equal(component._state, 'preRender');
// Renders the component to the page
this.render();
assert.equal(component._state, 'inDOM');
});
ember test //run once
ember test --server //run on filechange
ember g acceptance-test standing-on-giant
//tests/acceptance/standing-on-giant-test.js
import Ember from 'ember';
import {
module,
test
} from 'qunit';
import startApp from 'enterprise/tests/helpers/start-app';
var application;
module('Acceptance: StandingOnGiant', {
beforeEach: function() {
application = startApp();
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('visiting /standing-on-giant', function(assert) {
visit('/standing-on-giant');
andThen(function() {
assert.equal(currentURL(), '/standing-on-giant');
});
});
ember install ember-cli-mocha
npm install -g ember-cli
ember new giants-app
with Classes, Inheritance, and Mixins
//models/giant.js
export default DS.Model.extend({
name: DS.attr('string'),
standingOnShouldersOf: DS.hasMany('giant'),
})
//old jQuery way
var giant = {
name: 'Hodor',
standingOnShouldersOf: []
}
jQuery.ajax({
url: '/api/giants',
type: 'post',
data: {giant: giant},
dataType: 'json'
})
//Ember Data way
var giant = this.store.createRecord('giant', {
name: 'Hodor'
});
giant.save();
//in command line
ember install liquid-fire
//app/transitions.js
export default function(){
this.transition(
this.fromRoute('giants.index'),
this.toRoute('giants.show'),
this.use('toLeft'),
this.reverse('toRight')
);
...
};
ember install ember-cli-fastboot
a bunch of stuff that Ember takes care of for you
a few new APIs that Ember introduces