Sales web changes

<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content='Ember.js is an opinionated framework for building kickass web applications. Its strong opinions and heavy reliance of “convention over configuration” can give developers coming to the framework a hard time finding out how "Ember wants them to do things”. This book helps to overcome that initial frustration and sets you on your way to master Ember.js. It does this by guiding you through the steps of building a real application.'>
    <meta name="author" content="Balint Erdi">

    <meta name="twitter:card" content="summary" />
    <meta name="twitter:site" content="@rock-and-roll-with-emberjs" />
    <meta name="twitter:title" content="The most up-to-date book to learn Ember.js" />
    <meta name="twitter:description" content="Build ambitious apps with confidence. Rock and Roll with Ember.js guides you through the building of an application and teaches you the fundamentals of Ember in the process." />
    <meta name="twitter:image" content="https://www.dropbox.com/s/ahyvwmvhhydt12r/Rock%26Roll_252_200.jpg?dl=0" />
    <meta name="twitter:url" content="http://rockandrollwithemberjs.com" />

    <link rel="shortcut icon" href="images/favicon/favicon.ico" type="image/x-icon">
    <link rel="icon" href="images/favicon/favicon.ico" type="image/x-icon">

    <title>Cut your learning curve and build ambitious apps with Ember.js.</title>

    <link href="css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="css/lib/nivo-lightbox.css" type="text/css" />
    <link rel="stylesheet" href="css/lib/nivo-themes/default/default.css" type="text/css" />
    <link rel="stylesheet" type="text/css" href="css/styles.css" />
    <link rel="stylesheet" type="text/css" href="css/custom.css" />
    <link rel="stylesheet" type="text/css" href="css/scheme/brown.css" />
    <link rel="stylesheet" type="text/css" href="css/convertkit.css" />

    <link href='http://fonts.googleapis.com/css?family=Allura' rel='stylesheet' type='text/css'>

The most up-to-date book to learn Ember.js

                    Trusted by:

Build ambitious apps with confidence

Ember.js is a kick-ass framework for building
                       web applications. It's the only tool you need
                      (on the client-side) to craft your idea into a working
                      wonder. However, its strong opinions and heavy reliance of
                      “convention over configuration” can give developers who
                      are new to the framework a hard time figuring out how
                      Ember wants them to do things.

This book helps to overcome that initial
                      frustration, and help you grok Ember a lot
                      faster, by pinpointing the core concepts and
                      explaining them in detail. Once you understand them you
                      are on your way to taming and mastering Ember.js.
                      So don't fear the learning curve.

This is a really great book. I think it will be very helpful to many people. I’m definitely going to recommend it to beginners.

                                  — Taras Mankowski
                                  EmberSherpa

The book guides you through the steps of building a real application so that you can see how the concepts are applied in practice. You'll learn about the building blocks of Ember one by one, in a way that crushes the steep learning curve. Knowledge is dripped in slowly, chapter by chapter, to prevent you from drowning in a sea of novel information. By improving an existing and working application, you will learn more quickly and new knowledge will sink in deeper.

Already convinced? Skip to the packages.

Want to read a sample chapter first?

Thanks! Now check your email.

                        <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/2637/subscribe" data-remote="true">
                          <input type="hidden" value="{"embed_style":"inline","embed_trigger":"scroll_percentage","scroll_percentage":"70","delay_seconds":"10","display_position":"br","display_devices":"all","days_no_show":"15","converted_behavior":"show"}" id="ck_form_options">
                          <input type="hidden" name="id" value="2637" id="landing_page_id">

There was an error submitting your subscription. Please try again.

                            First Name
                            <input type="text" name="first_name" class="ck_first_name" id="ck_firstNameField">

                            Email Address
                            <input type="email" name="email" class="ck_email_address" id="ck_emailField" required>

                          <button type="submit" class="subscribe_button ck_subscribe_button btn fields" id='ck_subscribe_button'>
                            Send me the chapter
                          </button>
                          We won't send you spam. Unsubscribe at any time.
                        </form>

Once upon a time...

                  It all started in September 2013. Having just returned from the first European
                  Ember.js conference, my enthusiasm went through the roof. I wanted to spread the
                  word, for people to realize how beautiful Ember is, for them to know
                  that Ember.js is not that hard to learn. It's just a matter of having the core
                  concepts internalized.

                  So without proper equipment (and after a few episodes, with a ~$80 mic)  I started producing screencasts, a series in
                  which a simple application is developed step-by-step. I sent the first few to my
                  subscribers and kept producing them. And people seemed to have liked them.

Great episode! Pace and depth was perfect. Thanks for such a great webcast series!

                            — Curtis Wallen

I really appreciate the effort you want to put up. Just wanted to thank you for this and let you know that your video series rock!

                            — Antonio Antillon

                  Once the screencast series was finished, I switched to blog posts and lengthier
                  articles, and in 2014 I committed to posting something of
                  value each week. Then, after a long labor of (mostly) love,
                  I published the 1st edition of the book in Feburary
                  2015, that used the then stable Ember version, 1.10.

                  I was not done, though. As Ember made progress in a
                  neck breaking pace, I kept the book up-to-date with the
                  actual stable version of the framework, and adding material
                  that covered the new features.

Buy once, get all updates for free

The first edition of this book followed Ember's journey to
                its 2.0 release. As Ember gracefully shedded old syntaxes, the
                book's content was updated not to use those syntaxes, either.
                When a new feature appeared, I added a section about it, trying
                to find a practical example for it in the application.

Since Ember follows a 6-week release cycle, that meant
                frequent book updates, precisely 8 "minor book releases" in a
                little less than 7 months. These updates came free for all of my
                customers, no matter which package they chose. I aim to follow a
                similar path and update policy with Ember 2.x. The framework
                makes progress really fast but I'll keep in sync with the latest
                stable version of Ember and keep the content fresh. And
                free until at least the next major version.

I really like it! It is super difficult to find the right balance between pacing and detail
and put it all together in an understandable form but you pulled it off.

                         — Cory Forsyth
                         201 Created

Just wanted to say thanks for the ongoing updates of the book. Even after finishing the book, it’s becoming a great reference for the changes Ember is going through (even more so than the official docs).

                         — Gabriel Rotbart

Table of Contents

Preface

My love affair with Ember.js

Acknowledgements

Introduction to Ember.js

Why Ember.js

Backstage - Dial M for Model

About this book

Is this book for you?

Ambitious goal for an ambitious framework

The application we are going to build

Errata

Ember CLI

Setting up Ember CLI

Creating our application

Templates and data bindings

The first template

Showing a list of songs in the application template

Backstage - Handlebar's fail-softness

Backstage - ES2015 modules

Backstage - Class and instance properties

Adding libraries to the build

Importing needed assets manually

Using an Ember add-on

Auto-updating templates

Routing

What do we want to achieve?

New styles

Routes set up data

Backstage - A word about generated test files

Moving around with link-to

Using the model property

Showing a list of bands

Showing a list of songs

Nested routes

Defining the nested routes

Full route names

Backstage - Resource routes

Stepping through a router transition

Backstage - Computed properties

Nested templates

{{outlet}} is the default template for routes

Actions

Extracting model classes

The class-level mutable attribute property

Capturing the event

Turning event into intent

Components

The idea behind components

Component specification

Defining the component's properties

Using the component

Displaying the rating with stars

Backstage - The default component template

Backstage - On the utility of explicit dependency definitions

Speaking to the outside world through actions

Closure actions

Controllers

Preventing the creation of bands without a name

Smooth UI flows

Going to the band page after it is created

Nudging the user to create the first song

Showing the text field when the call to action has been acted on

Creating new bands and songs by pressing return

Backstage - Where to handle actions?

Advanced routing

Route hooks

Route actions

Redirection

Redirecting before the model is known

Redirecting after the model is known

Backstage - Skipping model resolution

Leaving routes and arriving at them

Setting descriptive page titles

Warning about losing data

Talking to a backend - with Ember Data

The concept behind Ember Data

The API

Model classes

Transforming the app to use Ember Data

Loading bands

Fetching a single band

Loading songs for a band

Creating a band

Updating a band's description

Updating song ratings

Backstage - Promises

Testing

Clarifying the vocabulary

Acceptance tests

To mock or not to mock?

Unit tests

Integration tests

Adding an acceptance test

Analyzing an Ember acceptance test

Making the first test pass

Safeguarding critical user scenarios

Creating a band

Creating a song

Backstage – Fake synchronous tests

Writing your own test helpers

Registering a synchronous helper

Registering an async helper

Refactoring stubs

Integration tests in Ember

Unit tests in Ember

Sorting and searching with query parameters

Query parameters

Query parameters in Ember

Sorting the list of songs

Sorting an array of items

Backstage - Computed property macros

Changing the sorting criteria

Filtering songs by a search term

Adding a dash of query parameters

Using links instead of buttons

Taking a look at what we made

Loading and error routes

Loading routes

Backstage - Rendering the loading template is an overridable default

Error routes

Helpers

About helpers

Capitalizing each word of a name

Using it in templates, or elsewhere

Use dashed names for helpers

Animations

Setting up liquid-fire

liquid-fire architecture

Transition map

Transition functions

Template helpers

Putting it all together

Debugging transitions

Animate moving between routes

Adding a custom animation

Animate a value change

Making an Ember addon

What is an Ember addon?

Developing the addon

Integrating our addon into our app

Adding a block form

Customizing the star-rating component's look and behavior

Publishing the addon

Using a published addon from the app

ES2015 - Writing modern JavaScript

Modernizing the app

Modules

Template strings

Short-hand method definitions

Replacing var (let & const)

Arrow functions

Destructuring

Other useful ES2015 features

Backstage - ember-watson

Computed property macros

Deployment

FrontPage

Heroku

ember-cli-deploy

Afterword

Your journey with Ember.js

About me

Hello there, I'm Balint Erdi.

                  I have a long history of building web applications and have mostly been a back-end
                  guy.

                  On a grey, chilly February day in 2013 that I’ll never forget (maybe it was
                  January?) I got acquainted with Ember.js. Ember.js made that day bright and the
                  ones that came after that. After learning the ropes I shifted gears and have been
                  proselytizing for Ember since last summer.

                  I gave a beginner’s workshop at Eurucamp, and held presentations at Arrrrcamp, The Geek Gathering and EmberFest. I started an Ember.js mailing list, made an introductory
                  screencast series and have been sending Ember content to my dear subscribers on a
                  weekly basis. I have also written guest articles for Safari Books Online, Firebase
                  and Toptal.

                  I guess you could say I’m pretty passionate about Ember.

                  Oh, and I love rock & roll, obviously.

Bonus: Exclusive hack.hands() deal

                hack.hands()
                is your SOS button for live programming support, available 24/7. hack.hands() instantly connects developers with a network of expert programmers at the click of a button. Never get stuck, learn more and keep coding!
                If you buy any package, you will receive $25 to spend on their platform that you can use to get your Ember project unstuck.

Even though I have worked with Ember for the past 6 months, the book helped clarify a lot of things, especially new features and their deprecated counterparts. Such a great book, and looking forward to the second one!

                         — David Tang

There is almost nowhere to go to get a good grounding in Ember. In months of searching this is the best resource that exists... so much so I'm going to try to get a few copies sorted out for my team at work. Thanks Balint!

                         — Andy Davison

Stairway to Heaven Package

                  The path to Ember.js bliss, this package contains all you need to take your Ember.js skills to the next level. It contains:

This was a truly great series of screencasts. Very, very well done. Simple enough while not being simplistic.

                      — Claude Précourt

Nice work with these Ember tutorials. You're doing a really nice thing for the community.

                      — Steven Kane

The book

                    18 chapters

This is what you absolutely need to get up & running with Ember.js. Comes in pdf, mobi and epub formats for your reading pleasure.

Access to the code

                    All of the source code

 in the application so that you can check how the app looks & works at each step. Or tweak it to your liking.

Screencasts

                    4 screencasts

They run around 10 minutes each and cover the following,
                    advanced topics: Pods in Ember CLI, Animations,
                    Authentication & Authorization, and ES2015 &
                      computed macros.

Rock & More

                    Includes

$25 credit on the hack.hands() platform

60-day refund guarantee

                    Buy now for $89

Light my Fire Package

                  Take out the screencasts and you have this smaller package that will still ignite your Ember rocket. It contains:

The book

                    18 chapters

This is what you absolutely need to get up & running with Ember.js. Comes in pdf, mobi and epub formats for your reading pleasure.

Access to the code

                    All of the source code

 in the application so that you can check how the app looks & works at each step. Or tweak it to your liking.

Rock & More

                    Includes

$25 credit on the hack.hands() platform

60-day refund guarantee

                    Buy now for $59

Smoke on the Water Package

                  Basic but still solid, as the name suggests. This package inclues the book and the hack.hands() deal. It contains:

The book

                    18 chapters

This is what you absolutely need to get up & running with Ember.js. Comes in pdf, mobi and epub formats for your reading pleasure.

Rock & More

                    Includes

$25 credit on the hack.hands() platform

60-day refund guarantee

                    Buy now for $39

I’m REALLY enjoying the book and find it to be very thorough and informative. You’ve done a great job of answering questions I had about various concepts that were not detailed clearly elsewhere.

                         — Bill White
                         Data Visualizations at www.bildwhite.com

It’s been an invaluable resource in learning Ember and one I would recommend to any developer wanting to get started with the framework.

                         — Jonathan Rowley

Hmm, I’m not sure...

Is the book ready for Ember 2?

                    Yes, it is! The app in the book runs Ember (and Ember Data)
                      2 and does not use any deprecated stuff.

Is this book for me?

                    The book starts from the very basics (installing
                    ember-cli, writing your first template, etc.) so if you
                    completely get Ember, this book is probably not for you. If
                    you are just starting out (or about to) or want to
                    understand fundamental concepts in Ember, the book is
                    definitely for you.

What if I don't like the book?

                    I offer a 60-day moneyback guarantee. No strings attached,
                    just send your purchase receipt (or email address) to
                    rarwe-book@balinterdi.com or answer to the welcome email
                    that you received when you purchased.

Everything is changing so fast; won't the content be outdated by next week?

                    As Ember.js moves towards 3.0, I will keep updating the
                    content of the book so that it stays void of deprecation
                    warnings and does not promote practices that are no longer
                    considered idiomatic. For the 1st edition of the book,
                    I released an update once about every four weeks,
                    more often than Ember stable versions came out.
                    Expect the same for the this book edition.

I heard Ember is so hard. Will this book help me understand it?

                    I think there are a handful of key concepts in Ember.js
                    that you need to understand to master it (probably the main
                    one among these is routing). Once you understand these,
                    Ember is not that hard. The intention of the book is
                    to introduce concepts by way of building an actual
                    application, so you see them applied in practice right away.
                    That also helps the learning process. I also encourage you
                    to work through the book as you read it, since that makes
                    things sink in even deeper. You have access to the code
                    repository if you have purchased the middle- or
                    high-tier package. If I failed to explain Ember for you, please
                    send me your purchase receipt to rarwe-book@balinterdi.com
                    within 60 days of purchase and I’ll issue a refund.

Is the book DRM protected?

                    No, it’s not. If you would like to share it with
                    colleagues (or a class), please contact me at rarwe-book@balinterdi.com.

What if I buy the book-only package and later realize I want to switch to a higher package?

No problem! Just send me your purchase receipt to rarwe-book@balinterdi.com, and I will upgrade you for the difference between the package prices.

Choose a Package

Smoke on the Water

                          $39

 The book with 18 chapters

 Free book updates until 3.0

 $25 credit for hack.hands()

 60-days money back guarantee

 Rock & Roll!

                        Buy Now

Light my Fire

                          $59

 The book with 18 chapters

 Free book updates until 3.0

 Access to the code

 $25 credit for hack.hands()

 60-days money back guarantee

 Rock & Roll!

                        Buy Now

Stairway to Heaven

                          $89

 The book with 18 chapters

 Free book updates until 3.0

 Access to the code

 4 Screencasts

 $25 credit for hack.hands()

 60-days money back guarantee

 Rock & Roll!

                        Buy Now

This book is a fabulous, approachable, foray into Emberjs that makes the learning curve much less scary. I just wish it had been available sooner for students in my previous classes.

                          — Matt Hale
                          Assistant Professor at the University of Nebraska

I’ve gone through the first four chapters and I am already loving it.

                          — Sergio Arbeo
                          EmberMadrid

                        Copyright © 2015
                            Balint Erdi

                        Powered by

                                Bootstrap

<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content='Ember.js is an opinionated framework for building kickass web applications. Its strong opinions and heavy reliance of “convention over configuration” can give developers coming to the framework a hard time finding out how "Ember wants them to do things”. This book helps to overcome that initial frustration and sets you on your way to master Ember.js. It does this by guiding you through the steps of building a real application.'>
    <meta name="author" content="Balint Erdi">

    <meta name="twitter:card" content="summary" />
    <meta name="twitter:site" content="@rock-and-roll-with-emberjs" />
    <meta name="twitter:title" content="The most up-to-date book to learn Ember.js" />
    <meta name="twitter:description" content="Build ambitious apps with confidence. Rock and Roll with Ember.js guides you through the building of an application and teaches you the fundamentals of Ember in the process." />
    <meta name="twitter:image" content="https://www.dropbox.com/s/ahyvwmvhhydt12r/Rock%26Roll_252_200.jpg?dl=0" />
    <meta name="twitter:url" content="http://rockandrollwithemberjs.com" />

    <link rel="shortcut icon" href="images/favicon/favicon.ico" type="image/x-icon">
    <link rel="icon" href="images/favicon/favicon.png" type="image/png">
    <link rel="icon" sizes="32x32" href="images/favicon/favicon-32.png" type="image/png">
    <link rel="icon" sizes="64x64" href="images/favicon/favicon-64.png" type="image/png">
    <link rel="icon" sizes="96x96" href="images/favicon/favicon-96.png" type="image/png">
    <link rel="icon" sizes="196x196" href="images/favicon/favicon-196.png" type="image/png">
    <link rel="apple-touch-icon" sizes="152x152" href="images/favicon/apple-touch-icon.png">
    <link rel="apple-touch-icon" sizes="60x60" href="images/favicon/apple-touch-icon-60x60.png">
    <link rel="apple-touch-icon" sizes="76x76" href="images/favicon/apple-touch-icon-76x76.png">
    <link rel="apple-touch-icon" sizes="114x114" href="images/favicon/apple-touch-icon-114x114.png">
    <link rel="apple-touch-icon" sizes="120x120" href="images/favicon/apple-touch-icon-120x120.png">
    <link rel="apple-touch-icon" sizes="144x144" href="images/favicon/apple-touch-icon-144x144.png">
    <meta name="msapplication-TileImage" content="favicon-144.png">
    <meta name="msapplication-TileColor" content="#FFFFFF">

    <title>Cut your learning curve and build ambitious apps with Ember.js.</title>

    <link href="css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="css/lib/nivo-lightbox.css" type="text/css" />
    <link rel="stylesheet" href="css/lib/nivo-themes/default/default.css" type="text/css" />
    <link rel="stylesheet" type="text/css" href="css/styles.css" />
    <link rel="stylesheet" type="text/css" href="css/custom.css" />
    <link rel="stylesheet" type="text/css" href="css/scheme/brown.css" />
    <link rel="stylesheet" type="text/css" href="css/convertkit.css" />

    <link href='http://fonts.googleapis.com/css?family=Allura' rel='stylesheet' type='text/css'>

The most up-to-date book to learn Ember.js

                    Trusted by:

Build ambitious apps with confidence

Ember.js is a kick-ass framework for building
                       web applications. It's the only tool you need
                      (on the client-side) to craft your idea into a working
                      wonder. However, its strong opinions and heavy reliance of
                      “convention over configuration” can give developers who
                      are new to the framework a hard time figuring out how
                      Ember wants them to do things.

This book helps to overcome that initial
                      frustration, and help you grok Ember a lot
                      faster, by pinpointing the core concepts and
                      explaining them in detail. Once you understand them you
                      are on your way to taming and mastering Ember.js.
                      So don't fear the learning curve.

This is a really great book. I think it will be very helpful to many people. I’m definitely going to recommend it to beginners.

                                  — Taras Mankowski
                                  EmberSherpa

The book guides you through the steps of building a real application so that you can see how the concepts are applied in practice. You'll learn about the building blocks of Ember one by one, in a way that crushes the steep learning curve. Knowledge is dripped in slowly, chapter by chapter, to prevent you from drowning in a sea of novel information. By improving an existing and working application, you will learn more quickly and new knowledge will sink in deeper.

Already convinced? Skip to the packages.

Want to read a sample chapter first?

Thanks! Now check your email.

                        <form id="ck_subscribe_form" class="ck_subscribe_form" action="https://app.convertkit.com/landing_pages/2637/subscribe" data-remote="true">
                          <input type="hidden" value="{"embed_style":"inline","embed_trigger":"scroll_percentage","scroll_percentage":"70","delay_seconds":"10","display_position":"br","display_devices":"all","days_no_show":"15","converted_behavior":"show"}" id="ck_form_options">
                          <input type="hidden" name="id" value="2637" id="landing_page_id">

There was an error submitting your subscription. Please try again.

                            First Name
                            <input type="text" name="first_name" class="ck_first_name" id="ck_firstNameField">

                            Email Address
                            <input type="email" name="email" class="ck_email_address" id="ck_emailField" required>

                          <button type="submit" class="subscribe_button ck_subscribe_button btn fields" id='ck_subscribe_button'>
                            Send me the chapter
                          </button>
                          We won't send you spam. Unsubscribe at any time.
                        </form>

Once upon a time...

                  It all started in September 2013. Having just returned from the first European
                  Ember.js conference, my enthusiasm went through the roof. I wanted to spread the
                  word, for people to realize how beautiful Ember is, for them to know
                  that Ember.js is not that hard to learn. It's just a matter of having the core
                  concepts internalized.

                  So without proper equipment (and after a few episodes, with a ~$80 mic)  I started producing screencasts, a series in
                  which a simple application is developed step-by-step. I sent the first few to my
                  subscribers and kept producing them. And people seemed to have liked them.

Great episode! Pace and depth was perfect. Thanks for such a great webcast series!

                            — Curtis Wallen

I really appreciate the effort you want to put up. Just wanted to thank you for this and let you know that your video series rock!

                            — Antonio Antillon

                  Once the screencast series was finished, I switched to blog posts and lengthier
                  articles, and in 2014 I committed to posting something of
                  value each week. Then, after a long labor of (mostly) love,
                  I published the 1st edition of the book in Feburary
                  2015, that used the then stable Ember version, 1.10.

                  I was not done, though. As Ember made progress in a
                  neck breaking pace, I kept the book up-to-date with the
                  actual stable version of the framework, and adding material
                  that covered the new features.

Buy once, get all updates for free

The first edition of this book followed Ember's journey to
                its 2.0 release. As Ember gracefully shedded old syntaxes, the
                book's content was updated not to use those syntaxes, either.
                When a new feature appeared, I added a section about it, trying
                to find a practical example for it in the application.

Since Ember follows a 6-week release cycle, that meant
                frequent book updates, precisely 8 "minor book releases" in a
                little less than 7 months. These updates came free for all of my
                customers, no matter which package they chose. I aim to follow a
                similar path and update policy with Ember 2.x. The framework
                makes progress really fast but I'll keep in sync with the latest
                stable version of Ember and keep the content fresh. And
                free until at least the next major version.

I really like it! It is super difficult to find the right balance between pacing and detail
and put it all together in an understandable form but you pulled it off.

                         — Cory Forsyth
                         201 Created

Just wanted to say thanks for the ongoing updates of the book. Even after finishing the book, it’s becoming a great reference for the changes Ember is going through (even more so than the official docs).

                         — Gabriel Rotbart

Table of Contents

Preface

My love affair with Ember.js

Acknowledgements

Introduction to Ember.js

Why Ember.js

Backstage - Dial M for Model

About this book

Is this book for you?

Ambitious goal for an ambitious framework

The application we are going to build

Errata

Ember CLI

Setting up Ember CLI

Creating our application

Templates and data bindings

The first template

Showing a list of songs in the application template

Backstage - Handlebar's fail-softness

Backstage - ES2015 modules

Backstage - Class and instance properties

Adding libraries to the build

Importing needed assets manually

Using an Ember add-on

Auto-updating templates

Routing

What do we want to achieve?

New styles

Routes set up data

Backstage - A word about generated test files

Moving around with link-to

Using the model property

Showing a list of bands

Showing a list of songs

Nested routes

Defining the nested routes

Full route names

Backstage - Resource routes

Stepping through a router transition

Backstage - Computed properties

Nested templates

{{outlet}} is the default template for routes

Actions

Extracting model classes

The class-level mutable attribute property

Capturing the event

Turning event into intent

Components

The idea behind components

Component specification

Defining the component's properties

Using the component

Displaying the rating with stars

Backstage - The default component template

Backstage - On the utility of explicit dependency definitions

Speaking to the outside world through actions

Closure actions

Controllers

Preventing the creation of bands without a name

Smooth UI flows

Going to the band page after it is created

Nudging the user to create the first song

Showing the text field when the call to action has been acted on

Creating new bands and songs by pressing return

Backstage - Where to handle actions?

Advanced routing

Route hooks

Route actions

Redirection

Redirecting before the model is known

Redirecting after the model is known

Backstage - Skipping model resolution

Leaving routes and arriving at them

Setting descriptive page titles

Warning about losing data

Talking to a backend - with Ember Data

The concept behind Ember Data

The API

Model classes

Transforming the app to use Ember Data

Loading bands

Fetching a single band

Loading songs for a band

Creating a band

Updating a band's description

Updating song ratings

Backstage - Promises

Testing

Clarifying the vocabulary

Acceptance tests

To mock or not to mock?

Unit tests

Integration tests

Adding an acceptance test

Analyzing an Ember acceptance test

Making the first test pass

Safeguarding critical user scenarios

Creating a band

Creating a song

Backstage – Fake synchronous tests

Writing your own test helpers

Registering a synchronous helper

Registering an async helper

Refactoring stubs

Integration tests in Ember

Unit tests in Ember

Sorting and searching with query parameters

Query parameters

Query parameters in Ember

Sorting the list of songs

Sorting an array of items

Backstage - Computed property macros

Changing the sorting criteria

Filtering songs by a search term

Adding a dash of query parameters

Using links instead of buttons

Taking a look at what we made

Loading and error routes

Loading routes

Backstage - Rendering the loading template is an overridable default

Error routes

Helpers

About helpers

Capitalizing each word of a name

Using it in templates, or elsewhere

Use dashed names for helpers

Animations

Setting up liquid-fire

liquid-fire architecture

Transition map

Transition functions

Template helpers

Putting it all together

Debugging transitions

Animate moving between routes

Adding a custom animation

Animate a value change

Making an Ember addon

What is an Ember addon?

Developing the addon

Integrating our addon into our app

Adding a block form

Customizing the star-rating component's look and behavior

Publishing the addon

Using a published addon from the app

ES2015 - Writing modern JavaScript

Modernizing the app

Modules

Template strings

Short-hand method definitions

Replacing var (let & const)

Arrow functions

Destructuring

Other useful ES2015 features

Backstage - ember-watson

Computed property macros

Deployment

FrontPage

Heroku

ember-cli-deploy

Afterword

Your journey with Ember.js

About me

Hello there, I'm Balint Erdi.

                  I have a long history of building web applications and have mostly been a back-end
                  guy.

                  On a grey, chilly February day in 2013 that I’ll never forget (maybe it was
                  January?) I got acquainted with Ember.js. Ember.js made that day bright and the
                  ones that came after that. After learning the ropes I shifted gears and have been
                  proselytizing for Ember since last summer.

                  I gave a beginner’s workshop at Eurucamp, and held presentations at Arrrrcamp, The Geek Gathering and EmberFest. I started an Ember.js mailing list, made an introductory
                  screencast series and have been sending Ember content to my dear subscribers on a
                  weekly basis. I have also written guest articles for Safari Books Online, Firebase
                  and Toptal.

                  I guess you could say I’m pretty passionate about Ember.

                  Oh, and I love rock & roll, obviously.

Bonus: Exclusive hack.hands() deal

                hack.hands()
                is your SOS button for live programming support, available 24/7. hack.hands() instantly connects developers with a network of expert programmers at the click of a button. Never get stuck, learn more and keep coding!
                If you buy any package, you will receive $25 to spend on their platform that you can use to get your Ember project unstuck.

Even though I have worked with Ember for the past 6 months, the book helped clarify a lot of things, especially new features and their deprecated counterparts. Such a great book, and looking forward to the second one!

                         — David Tang

There is almost nowhere to go to get a good grounding in Ember. In months of searching this is the best resource that exists... so much so I'm going to try to get a few copies sorted out for my team at work. Thanks Balint!

                         — Andy Davison

Stairway to Heaven Package

                  The path to Ember.js bliss, this package contains all you need to take your Ember.js skills to the next level. It contains:

This was a truly great series of screencasts. Very, very well done. Simple enough while not being simplistic.

                      — Claude Précourt

Nice work with these Ember tutorials. You're doing a really nice thing for the community.

                      — Steven Kane

The book

                    18 chapters

This is what you absolutely need to get up & running with Ember.js. Comes in pdf, mobi and epub formats for your reading pleasure.

Access to the code

                    All of the source code

 in the application so that you can check how the app looks & works at each step. Or tweak it to your liking.

Screencasts

                    4 screencasts

They run around 10 minutes each and cover the following,
                    advanced topics: Pods in Ember CLI, Animations,
                    Authentication & Authorization, and ES2015 &
                      computed macros.

Rock & More

                    Includes

$25 credit on the hack.hands() platform

60-day refund guarantee

                    Buy now for $89

Light my Fire Package

                  Take out the screencasts and you have this smaller package that will still ignite your Ember rocket. It contains:

The book

                    18 chapters

This is what you absolutely need to get up & running with Ember.js. Comes in pdf, mobi and epub formats for your reading pleasure.

Access to the code

                    All of the source code

 in the application so that you can check how the app looks & works at each step. Or tweak it to your liking.

Rock & More

                    Includes

$25 credit on the hack.hands() platform

60-day refund guarantee

                    Buy now for $59

Smoke on the Water Package

                  Basic but still solid, as the name suggests. This package inclues the book and the hack.hands() deal. It contains:

The book

                    18 chapters

This is what you absolutely need to get up & running with Ember.js. Comes in pdf, mobi and epub formats for your reading pleasure.

Rock & More

                    Includes

$25 credit on the hack.hands() platform

60-day refund guarantee

                    Buy now for $39

I’m REALLY enjoying the book and find it to be very thorough and informative. You’ve done a great job of answering questions I had about various concepts that were not detailed clearly elsewhere.

                         — Bill White
                         Data Visualizations at www.bildwhite.com

It’s been an invaluable resource in learning Ember and one I would recommend to any developer wanting to get started with the framework.

                         — Jonathan Rowley

Hmm, I’m not sure...

Is the book ready for Ember 2?

                    Yes, it is! The app in the book runs Ember (and Ember Data)
                      2 and does not use any deprecated stuff.

Is this book for me?

                    The book starts from the very basics (installing
                    ember-cli, writing your first template, etc.) so if you
                    completely get Ember, this book is probably not for you. If
                    you are just starting out (or about to) or want to
                    understand fundamental concepts in Ember, the book is
                    definitely for you.

What if I don't like the book?

                    I offer a 60-day moneyback guarantee. No strings attached,
                    just send your purchase receipt (or email address) to
                    rarwe-book@balinterdi.com or answer to the welcome email
                    that you received when you purchased.

Everything is changing so fast; won't the content be outdated by next week?

                    As Ember.js moves towards 3.0, I will keep updating the
                    content of the book so that it stays void of deprecation
                    warnings and does not promote practices that are no longer
                    considered idiomatic. For the 1st edition of the book,
                    I released an update once about every four weeks,
                    more often than Ember stable versions came out.
                    Expect the same for the this book edition.

I heard Ember is so hard. Will this book help me understand it?

                    I think there are a handful of key concepts in Ember.js
                    that you need to understand to master it (probably the main
                    one among these is routing). Once you understand these,
                    Ember is not that hard. The intention of the book is
                    to introduce concepts by way of building an actual
                    application, so you see them applied in practice right away.
                    That also helps the learning process. I also encourage you
                    to work through the book as you read it, since that makes
                    things sink in even deeper. You have access to the code
                    repository if you have purchased the middle- or
                    high-tier package. If I failed to explain Ember for you, please
                    send me your purchase receipt to rarwe-book@balinterdi.com
                    within 60 days of purchase and I’ll issue a refund.

Is the book DRM protected?

                    No, it’s not. If you would like to share it with
                    colleagues (or a class), please contact me at rarwe-book@balinterdi.com.

What if I buy the book-only package and later realize I want to switch to a higher package?

No problem! Just send me your purchase receipt to rarwe-book@balinterdi.com, and I will upgrade you for the difference between the package prices.

Choose a Package

Smoke on the Water

                          $39

 The book with 18 chapters

 Free book updates until 3.0

 $25 credit for hack.hands()

 60-days money back guarantee

 Rock & Roll!

                        Buy Now

Light my Fire

                          $59

 The book with 18 chapters

 Free book updates until 3.0

 Access to the code

 $25 credit for hack.hands()

 60-days money back guarantee

 Rock & Roll!

                        Buy Now

Stairway to Heaven

                          $89

 The book with 18 chapters

 Free book updates until 3.0

 Access to the code

 4 Screencasts

 $25 credit for hack.hands()

 60-days money back guarantee

 Rock & Roll!

                        Buy Now

This book is a fabulous, approachable, foray into Emberjs that makes the learning curve much less scary. I just wish it had been available sooner for students in my previous classes.

                          — Matt Hale
                          Assistant Professor at the University of Nebraska

I’ve gone through the first four chapters and I am already loving it.

                          — Sergio Arbeo
                          EmberMadrid

                        Copyright © 2015
                            Balint Erdi

                        Powered by

                                Bootstrap


Fixing regressed bug-fix for Issue#147

<?php declare(strict_types=1);
/*
 * This file is part of PHP Copy/Paste Detector (PHPCPD).
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\PHPCPD;

use const PHP_EOL;
use function count;
use function printf;
use SebastianBergmann\FileIterator\Facade;
use SebastianBergmann\PHPCPD\Detector\Detector;
use SebastianBergmann\PHPCPD\Detector\Strategy\AbstractStrategy;
use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy;
use SebastianBergmann\PHPCPD\Detector\Strategy\StrategyConfiguration;
use SebastianBergmann\PHPCPD\Detector\Strategy\SuffixTreeStrategy;
use SebastianBergmann\PHPCPD\Log\PMD;
use SebastianBergmann\PHPCPD\Log\Text;
use SebastianBergmann\Timer\ResourceUsageFormatter;
use SebastianBergmann\Timer\Timer;
use SebastianBergmann\Version;

final class Application
{
    private const VERSION = '7.0';

    public function run(array $argv): int
    {
        $this->printVersion();

        try {
            $arguments = (new ArgumentsBuilder)->build($argv);
        } catch (Exception $e) {
            print PHP_EOL . $e->getMessage() . PHP_EOL;

            return 1;
        }

        if ($arguments->version()) {
            return 0;
        }

        print PHP_EOL;

        if ($arguments->help()) {
            $this->help();

            return 0;
        }

        $files = (new Facade)->getFilesAsArray(
            $arguments->directories(),
            $arguments->suffixes(),
            '',
            $arguments->exclude()
        );

        if (empty($files)) {
            print 'No files found to scan' . PHP_EOL;

            return 1;
        }

        $config = new StrategyConfiguration($arguments);

        try {
            $strategy = $this->pickStrategy($arguments->algorithm(), $config);
        } catch (InvalidStrategyException $e) {
            print $e->getMessage() . PHP_EOL;

            return 1;
        }

        $timer = new Timer;
        $timer->start();

        $clones = (new Detector($strategy))->copyPasteDetection($files);

        (new Text)->printResult($clones, $arguments->verbose());

        if ($arguments->pmdCpdXmlLogfile()) {
            (new PMD($arguments->pmdCpdXmlLogfile()))->processClones($clones);
        }

        print (new ResourceUsageFormatter)->resourceUsage($timer->stop()) . PHP_EOL;

        return count($clones) > 0 ? 1 : 0;
    }

    private function printVersion(): void
    {
        printf(
            'phpcpd %s by Sebastian Bergmann.' . PHP_EOL,
            (new Version(self::VERSION, dirname(__DIR__)))->getVersion()
        );
    }

    /**
     * @throws InvalidStrategyException
     */
    private function pickStrategy(?string $algorithm, StrategyConfiguration $config): AbstractStrategy
    {
        return match ($algorithm) {
            null, 'rabin-karp' => new DefaultStrategy($config),
            'suffixtree' => new SuffixTreeStrategy($config),
            default      => throw new InvalidStrategyException('Unsupported algorithm: ' . $algorithm),
        };
    }

    private function help(): void
    {
        print <<<'EOT'
Usage:
  phpcpd [options] <directory>

Options for selecting files:

  --suffix <suffix> Include files with names ending in <suffix> in the analysis
                    (default: .php; can be given multiple times)
  --exclude <path>  Exclude files with <path> in their path from the analysis
                    (can be given multiple times)

Options for analysing files:

  --fuzzy             Fuzz variable names
  --min-lines <N>     Minimum number of identical lines (default: 5)
  --min-tokens <N>    Minimum number of identical tokens (default: 70)
  --algorithm <name>  Select which algorithm to use ('rabin-karp' (default) or 'suffixtree')
  --edit-distance <N> Distance in number of edits between two clones (only for suffixtree; default: 5)
  --head-equality <N> Minimum equality at start of clone (only for suffixtree; default 10)

Options for report generation:

  --log-pmd <file>  Write log in PMD-CPD XML format to <file>

EOT;
    }
}

<?php declare(strict_types=1);
/*
 * This file is part of PHP Copy/Paste Detector (PHPCPD).
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\PHPCPD;

use const PHP_EOL;
use function count;
use function printf;
use SebastianBergmann\FileIterator\Facade;
use SebastianBergmann\PHPCPD\Detector\Detector;
use SebastianBergmann\PHPCPD\Detector\Strategy\AbstractStrategy;
use SebastianBergmann\PHPCPD\Detector\Strategy\DefaultStrategy;
use SebastianBergmann\PHPCPD\Detector\Strategy\StrategyConfiguration;
use SebastianBergmann\PHPCPD\Detector\Strategy\SuffixTreeStrategy;
use SebastianBergmann\PHPCPD\Log\PMD;
use SebastianBergmann\PHPCPD\Log\Text;
use SebastianBergmann\Timer\ResourceUsageFormatter;
use SebastianBergmann\Timer\Timer;
use SebastianBergmann\Version;

final class Application
{
    private const VERSION = '7.0';

    public function run(array $argv): int
    {
        $this->printVersion();

        try {
            $arguments = (new ArgumentsBuilder)->build($argv);
        } catch (Exception $e) {
            print PHP_EOL . $e->getMessage() . PHP_EOL;

            return 1;
        }

        if ($arguments->version()) {
            return 0;
        }

        print PHP_EOL;

        if ($arguments->help()) {
            $this->help();

            return 0;
        }

        $files = (new Facade)->getFilesAsArray(
            $arguments->directories(),
            $arguments->suffixes(),
            '',
            $arguments->exclude()
        );

        if (empty($files)) {
            print 'No files found to scan' . PHP_EOL;

            return 0;
        }

        $config = new StrategyConfiguration($arguments);

        try {
            $strategy = $this->pickStrategy($arguments->algorithm(), $config);
        } catch (InvalidStrategyException $e) {
            print $e->getMessage() . PHP_EOL;

            return 1;
        }

        $timer = new Timer;
        $timer->start();

        $clones = (new Detector($strategy))->copyPasteDetection($files);

        (new Text)->printResult($clones, $arguments->verbose());

        if ($arguments->pmdCpdXmlLogfile()) {
            (new PMD($arguments->pmdCpdXmlLogfile()))->processClones($clones);
        }

        print (new ResourceUsageFormatter)->resourceUsage($timer->stop()) . PHP_EOL;

        return count($clones) > 0 ? 1 : 0;
    }

    private function printVersion(): void
    {
        printf(
            'phpcpd %s by Sebastian Bergmann.' . PHP_EOL,
            (new Version(self::VERSION, dirname(__DIR__)))->getVersion()
        );
    }

    /**
     * @throws InvalidStrategyException
     */
    private function pickStrategy(?string $algorithm, StrategyConfiguration $config): AbstractStrategy
    {
        return match ($algorithm) {
            null, 'rabin-karp' => new DefaultStrategy($config),
            'suffixtree' => new SuffixTreeStrategy($config),
            default      => throw new InvalidStrategyException('Unsupported algorithm: ' . $algorithm),
        };
    }

    private function help(): void
    {
        print <<<'EOT'
Usage:
  phpcpd [options] <directory>

Options for selecting files:

  --suffix <suffix> Include files with names ending in <suffix> in the analysis
                    (default: .php; can be given multiple times)
  --exclude <path>  Exclude files with <path> in their path from the analysis
                    (can be given multiple times)

Options for analysing files:

  --fuzzy             Fuzz variable names
  --min-lines <N>     Minimum number of identical lines (default: 5)
  --min-tokens <N>    Minimum number of identical tokens (default: 70)
  --algorithm <name>  Select which algorithm to use ('rabin-karp' (default) or 'suffixtree')
  --edit-distance <N> Distance in number of edits between two clones (only for suffixtree; default: 5)
  --head-equality <N> Minimum equality at start of clone (only for suffixtree; default 10)

Options for report generation:

  --log-pmd <file>  Write log in PMD-CPD XML format to <file>

EOT;
    }
}


fix display of #channame or www.link.com

html.normal {
    color: #4b4b4b;
    background-color: white;
    font: 12px/1.3em 'Lucida Grande' sans-serif;
    margin: 0;
    padding: 0;
}

body.normal .sender[type=myself]:hover:before {
    content: "• ";
    color: #bebebe;
}

body.normal a img {
    border: 0;
}

body.normal img {
    display: block;
    padding-top: 5px;
    padding-bottom: 5px;
    padding-left: 8px;
    max-width: 95%;
}

body.normal a:link, a:visited, a:hover, a:active {
    color: #184586;
}

body.normal .text span {
    display: block;
}

body.normal .line {
    padding: 0;
    margin: 0;
    background-color: #eee;
    line-height: 130%;
}

body.normal .event {
    padding: 0.2em;
    margin: 0;
    color: #363636;
    background: #e5edf7;
}

body.normal .event + .event {
    border-top: #fff 1px solid;
}

body.normal .text span.message {
    border-bottom: #efefef 1px solid;
}

body.normal .text + .event {
    margin-top: -1px;
}

body.normal .sender {
    padding: 0.4em 0;
    float: left;
    text-align: right;
    font-weight: bold;
    width: 130px;
    overflow: hidden;
}

body.normal .line .message {
    margin: 0 0 0 134px;
    padding: 0.4em;
    color: #303030;
    border-left: 1px solid #dcdcdc;
    background-color: #fff;
}

body.normal .event .message {
    margin: 0;
    padding: 0.4em;
    border: 0;
    color: #a6b9c0;
    background: none;
    font: bold 11px "Helvetica Neue", Arial, Helvetica, sans-serif;
}

body.normal .line .time {
    float: right;
    margin-right: 6px;
    margin-top: 5px;
    margin-left: 4px;
    color: #999;
    font: bold 11px "Helvetica Neue", Arial, Helvetica, sans-serif;
}

body.normal .line .place {
    display: block;
    color: #999;
    margin: 0;
    font-size: 12px;
}

body.normal .event .time {
    margin: 1px 3px;
    float: right;
    text-align: right;
    font-size: 11px;
    color: #a6b9c0;
}

body.normal .line[highlight=true] span.message {
    background-color: #ffc;
}

body.normal .member {
    text-decoration: none !important;
    color: inherit !important;
}

body.normal .member:hover {
    text-decoration: underline;
}

body.normal {

}

body.console {

}

body[type=channel] {

}

body[type=talk] {

}

body[type=console] {

}

body.normal hr {
    margin: 0;
    padding: 2px;
    border: none;
    background-color: #d33b54;
    height: 2px;
    text-align: center;
}

a {

}

/* lines */

.line[alternate=even] {

}

.line[alternate=odd] {

}

.line[type=action] .sender:before {
    content: "";
}

body.normal .line[type=action] .sender {
    font-style: italic;
}

body.normal .line[type=action] .message {
    background: #eee;
    border-left: none;
    margin: 0 0 0 130px;
}

body.normal .url {
    word-break: break-all;
}

body.normal .address {
    text-decoration: underline;
    word-break: break-all;
}

body.normal .highlight {
    color: #d33b54;
    font-weight: bold;
}

/* nickname for text messages */

body.normal .sender[type=myself] {
    color: #333;
}

body.normal .sender[type=normal] {
    color: #333;
}

body.normal .sender[type=normal][colornumber='0'] {

}

body.normal .sender[type=normal][colornumber='1'] {

}

body.normal .sender[type=normal][colornumber='2'] {

}

body.normal .sender[type=normal][colornumber='3'] {

}

body.normal .sender[type=normal][colornumber='4'] {

}

body.normal .sender[type=normal][colornumber='5'] {

}

body.normal .sender[type=normal][colornumber='6'] {

}

body.normal .sender[type=normal][colornumber='7'] {

}

body.normal .sender[type=normal][colornumber='8'] {

}

body.normal .sender[type=normal][colornumber='9'] {

}

body.normal .sender[type=normal][colornumber='10'] {

}

body.normal .sender[type=normal][colornumber='11'] {

}

body.normal .sender[type=normal][colornumber='12'] {

}

body.normal .sender[type=normal][colornumber='13'] {

}

body.normal .sender[type=normal][colornumber='14'] {

}

body.normal .sender[type=normal][colornumber='15'] {

}

/* message body */

body.normal .message[type=system] {
    color: #303030;
}

body.normal .message[type=error] {
    color: #d14025;
    font-weight: bold;
}

body.normal .message[type=reply] {
    color: #a6b9c0;
}

body.normal .message[type=error_reply] {
    color: #d33b54;
}

body.normal .message[type=dcc_send_send] {
    color: #a6b9c0;
}

body.normal .message[type=dcc_send_receive] {
    color: #153b7c;
}

body.normal .message[type=privmsg] {

}

body.normal .message[type=notice] {
    color: #888;
}

body.normal .message[type=action] {
    color: #303030;
    font-style: italic;
    font-weight: bold;
}

body.normal .message[type=join] {
    color: #a6b9c0;
}

body.normal .message[type=part] {
    color: #a6b9c0;
}

body.normal .message[type=kick] {
    color: #303030;
}

body.normal .message[type=quit] {
    color: #a6b9c0;
}

body.normal .message[type=kill] {
    color: #a6b9c0;
}

body.normal .message[type=nick] {
    color: #a6b9c0;
}

body.normal .message[type=mode] {
    color: #a6b9c0;
}

body.normal .message[type=topic] {
    color: #a6b9c0;
}

body.normal .message[type=invite] {
    color: #a6b9c0;
}

body.normal .message[type=wallops] {
    color: #a6b9c0;
}

body.normal .message[type=debug_send] {
    color: #aaa;
}

body.normal .message[type=debug_receive] {
    color: #444;
}

html.normal {
    color: #4b4b4b;
    background-color: white;
    font: 12px/1.3em 'Lucida Grande' sans-serif;
    margin: 0;
    padding: 0;
}

body.normal .sender[type=myself]:hover:before {
    content: "• ";
    color: #bebebe;
}

body.normal a img {
    border: 0;
}

body.normal img {
    display: block;
    padding-top: 5px;
    padding-bottom: 5px;
    padding-left: 8px;
    max-width: 95%;
}

body.normal a:link, a:visited, a:hover, a:active {
    color: #184586;
}

body.normal .text > span {
    display: block;
}

body.normal .line {
    padding: 0;
    margin: 0;
    background-color: #eee;
    line-height: 130%;
}

body.normal .event {
    padding: 0.2em;
    margin: 0;
    color: #363636;
    background: #e5edf7;
}

body.normal .event + .event {
    border-top: #fff 1px solid;
}

body.normal .text span.message {
    border-bottom: #efefef 1px solid;
}

body.normal .text + .event {
    margin-top: -1px;
}

body.normal .sender {
    padding: 0.4em 0;
    float: left;
    text-align: right;
    font-weight: bold;
    width: 130px;
    overflow: hidden;
}

body.normal .line .message {
    margin: 0 0 0 134px;
    padding: 0.4em;
    color: #303030;
    border-left: 1px solid #dcdcdc;
    background-color: #fff;
}

body.normal .event .message {
    margin: 0;
    padding: 0.4em;
    border: 0;
    color: #a6b9c0;
    background: none;
    font: bold 11px "Helvetica Neue", Arial, Helvetica, sans-serif;
}

body.normal .line .time {
    float: right;
    margin-right: 6px;
    margin-top: 5px;
    margin-left: 4px;
    color: #999;
    font: bold 11px "Helvetica Neue", Arial, Helvetica, sans-serif;
}

body.normal .line .place {
    display: block;
    color: #999;
    margin: 0;
    font-size: 12px;
}

body.normal .event .time {
    margin: 1px 3px;
    float: right;
    text-align: right;
    font-size: 11px;
    color: #a6b9c0;
}

body.normal .line[highlight=true] span.message {
    background-color: #ffc;
}

body.normal .member {
    text-decoration: none !important;
    color: inherit !important;
}

body.normal .member:hover {
    text-decoration: underline;
}

body.normal {

}

body.console {

}

body[type=channel] {

}

body[type=talk] {

}

body[type=console] {

}

body.normal hr {
    margin: 0;
    padding: 2px;
    border: none;
    background-color: #d33b54;
    height: 2px;
    text-align: center;
}

a {

}

/* lines */

.line[alternate=even] {

}

.line[alternate=odd] {

}

.line[type=action] .sender:before {
    content: "";
}

body.normal .line[type=action] .sender {
    font-style: italic;
}

body.normal .line[type=action] .message {
    background: #eee;
    border-left: none;
    margin: 0 0 0 130px;
}

body.normal .url {
    word-break: break-all;
}

body.normal .address {
    text-decoration: underline;
    word-break: break-all;
}

body.normal .highlight {
    color: #d33b54;
    font-weight: bold;
}

/* nickname for text messages */

body.normal .sender[type=myself] {
    color: #333;
}

body.normal .sender[type=normal] {
    color: #333;
}

body.normal .sender[type=normal][colornumber='0'] {

}

body.normal .sender[type=normal][colornumber='1'] {

}

body.normal .sender[type=normal][colornumber='2'] {

}

body.normal .sender[type=normal][colornumber='3'] {

}

body.normal .sender[type=normal][colornumber='4'] {

}

body.normal .sender[type=normal][colornumber='5'] {

}

body.normal .sender[type=normal][colornumber='6'] {

}

body.normal .sender[type=normal][colornumber='7'] {

}

body.normal .sender[type=normal][colornumber='8'] {

}

body.normal .sender[type=normal][colornumber='9'] {

}

body.normal .sender[type=normal][colornumber='10'] {

}

body.normal .sender[type=normal][colornumber='11'] {

}

body.normal .sender[type=normal][colornumber='12'] {

}

body.normal .sender[type=normal][colornumber='13'] {

}

body.normal .sender[type=normal][colornumber='14'] {

}

body.normal .sender[type=normal][colornumber='15'] {

}

/* message body */

body.normal .message[type=system] {
    color: #303030;
}

body.normal .message[type=error] {
    color: #d14025;
    font-weight: bold;
}

body.normal .message[type=reply] {
    color: #a6b9c0;
}

body.normal .message[type=error_reply] {
    color: #d33b54;
}

body.normal .message[type=dcc_send_send] {
    color: #a6b9c0;
}

body.normal .message[type=dcc_send_receive] {
    color: #153b7c;
}

body.normal .message[type=privmsg] {

}

body.normal .message[type=notice] {
    color: #888;
}

body.normal .message[type=action] {
    color: #303030;
    font-style: italic;
    font-weight: bold;
}

body.normal .message[type=join] {
    color: #a6b9c0;
}

body.normal .message[type=part] {
    color: #a6b9c0;
}

body.normal .message[type=kick] {
    color: #303030;
}

body.normal .message[type=quit] {
    color: #a6b9c0;
}

body.normal .message[type=kill] {
    color: #a6b9c0;
}

body.normal .message[type=nick] {
    color: #a6b9c0;
}

body.normal .message[type=mode] {
    color: #a6b9c0;
}

body.normal .message[type=topic] {
    color: #a6b9c0;
}

body.normal .message[type=invite] {
    color: #a6b9c0;
}

body.normal .message[type=wallops] {
    color: #a6b9c0;
}

body.normal .message[type=debug_send] {
    color: #aaa;
}

body.normal .message[type=debug_receive] {
    color: #444;
}


improve CLI

require 'image'
require 'readline'

class Editor
  def initialize(output)
    @output = output
  end
  def start
    @output.puts 'Welcome to Image Editor!'
    listen
  end

  def listen
    loop do
      cmd = Readline::readline '> ', true
      execute(cmd)
    end
  end

  def execute(cmd)
    case cmd
      when /^i (\d+)+ (\d+)+$/i
        @image = Image.new($1.to_i, $2.to_i)
      when /^c/i
        @image.clear
      when /^l (\d+) (\d+) ([a-z])$/i
        @image.colorize($1.to_i, $2.to_i,$3)
      when /^v (\d+) (\d+) (\d+) ([a-z])$/i
        @image.draw_vertical_segment($1.to_i, $2.to_i, $3.to_i, $4)
      when /^h (\d+) (\d+) (\d+) ([a-z])$/i
        @image.draw_horizontal_segment($1.to_i, $2.to_i, $3.to_i, $4)
      when /^f (\d+) (\d+) ([a-z])$/i
        @image.fill($1.to_i, $2.to_i, $3)
      when /^s$/i
        @output.puts @image.to_s
      when /^x$/i
        exit
      else
        puts 'Unknown command'
    end
  end

end

require 'image'
require 'readline'

# trap SIGINT and restore the state of the terminal
stty_save = `stty -g`.chomp
trap('INT') { system('stty', stty_save); exit }

class Editor
  def initialize(output)
    @output = output
  end
  def start
    @output.puts 'Welcome to Image Editor!'
    listen
  end

  def listen
    loop do
      cmd = Readline::readline '> ', true
      # exit if user press CTRL-D
      exit if cmd.nil?
      execute(cmd)
    end
  end

  def execute(cmd)
    case cmd
      when /^i (\d+)+ (\d+)+$/i
        @image = Image.new($1.to_i, $2.to_i)
      when /^c/i
        @image.clear
      when /^l (\d+) (\d+) ([a-z])$/i
        @image.colorize($1.to_i, $2.to_i,$3)
      when /^v (\d+) (\d+) (\d+) ([a-z])$/i
        @image.draw_vertical_segment($1.to_i, $2.to_i, $3.to_i, $4)
      when /^h (\d+) (\d+) (\d+) ([a-z])$/i
        @image.draw_horizontal_segment($1.to_i, $2.to_i, $3.to_i, $4)
      when /^f (\d+) (\d+) ([a-z])$/i
        @image.fill($1.to_i, $2.to_i, $3)
      when /^s$/i
        @output.puts @image.to_s
      when /^x$/i
        exit
      else
        puts 'Unknown command'
    end
  end

end


Path url_for to work on on Rails 4.2

require 'action_dispatch/routing/route_set'

module ActionDispatch
  module Routing
    class RouteSet #:nodoc:
      def url_for_with_subdomains(options, path_segments=nil)
        if SubdomainFu.needs_rewrite?(options[:subdomain], (options[:host] || @request.host_with_port)) || options[:only_path] == false
          options[:only_path] = false if SubdomainFu.override_only_path?
          options[:host] = SubdomainFu.rewrite_host_for_subdomains(options.delete(:subdomain), options[:host] || @request.host_with_port)
          # puts "options[:host]: #{options[:host].inspect}"
        else
          options.delete(:subdomain)
        end
        url_for_without_subdomains(options)
      end
      alias_method_chain :url_for, :subdomains
    end
  end
end

require 'action_dispatch/routing/route_set'

module ActionDispatch
  module Routing
    class RouteSet #:nodoc:
      # Known Issue: Monkey-patching `url_for` is error-prone.
      # For example, in rails 4.2, the method signature changed from
      #
      #     def url_for(options)
      #
      # to
      #
      #     def url_for(options, route_name = nil, url_strategy = UNKNOWN)
      #
      # Is there an alternative design using composition or class
      # inheritance?

      def url_for_with_subdomains(options, *args)
        if SubdomainFu.needs_rewrite?(options[:subdomain], (options[:host] || @request.host_with_port)) || options[:only_path] == false
          options[:only_path] = false if SubdomainFu.override_only_path?
          options[:host] = SubdomainFu.rewrite_host_for_subdomains(options.delete(:subdomain), options[:host] || @request.host_with_port)
          # puts "options[:host]: #{options[:host].inspect}"
        else
          options.delete(:subdomain)
        end
        url_for_without_subdomains(options, *args)
      end
      alias_method_chain :url_for, :subdomains
    end
  end
end


unbreak adding further options

require 'optparse'

module Nagios
  class Config

    attr_accessor :options

    def initialize
      @settings = {}
      @options = OptionParser.new do |options|
        options.on("-wWARNING",
                   "--warning=WARNING",
                   "Warning Threshold") do |x|
          @settings[:warning] = int_if_possible(x)
        end
        options.on("-cCRITICAL",
                   "--critical=CRITICAL",
                   "Critical Threshold") do |x|
          @settings[:critical] = int_if_possible(x)
        end
      end
    end

    def [](setting)
      @settings[setting]
    end

    def []=(field, value)
      @settings[field] = vaule
    end

    def parse!
      @options.parse!
    end

    private
      def int_if_possible(x)
        (x.to_i > 0 || x == '0') ? x.to_i : x
      end
  end
end

require 'optparse'

module Nagios
  class Config

    attr_accessor :options

    def initialize
      @settings = {}
      @options = OptionParser.new do |options|
        options.on("-wWARNING",
                   "--warning=WARNING",
                   "Warning Threshold") do |x|
          @settings[:warning] = int_if_possible(x)
        end
        options.on("-cCRITICAL",
                   "--critical=CRITICAL",
                   "Critical Threshold") do |x|
          @settings[:critical] = int_if_possible(x)
        end
      end
    end

    def [](setting)
      @settings[setting]
    end

    def []=(field, value)
      @settings[field] = value
    end

    def parse!
      @options.parse!
    end

    private
      def int_if_possible(x)
        (x.to_i > 0 || x == '0') ? x.to_i : x
      end
  end
end


Correct the URL for the github repo.

/*
 * YO check out the repository for updates
 * http://github.com/aconbere/event-emitter
 *
 */
var Evented = function () {
  this.listeners = {};
  this.listenersOnce = {};
};

Evented.prototype.withListeners = function (ev, callback) {
  if (!this.listeners[ev]) {
    this.listeners[ev] = [];
  }

  if (!this.listenersOnce[ev]) {
    this.listenersOnce[ev] = [];
  }

  if (callback) {
    callback(this.listeners[ev], this.listenersOnce[ev]);
  }
};

Evented.prototype.trigger = function (ev, data) {
  data = data || [];

  var that = this;
  this.withListeners(ev, function (listeners, listenersOnce) {
    for (var i = 0; i < listeners.length; i++) {
      listeners[i].apply(null, data);
    }

    for (var i = 0; i < listenersOnce.length; i++) {
      listenersOnce[i].apply(null, data);
    }
    // reset any of the once listeners for this event
    that.listenersOnce[ev] = [];
  });
};

Evented.prototype.bind = function (ev, callback) {
  this.withListeners(ev, function (listeners, _listenersOnce) {
    listeners.push(callback);
  });
};

Evented.prototype.bindOnce = function (ev, callback) {
  this.withListeners(ev, function (_listeners, listenersOnce) {
    listenersOnce.push(callback);
  });
};

Evented.prototype.unbindAll = function () {
  this.listeners = {};
};

if (typeof window === "undefined") {
  exports.Evented = Evented;
}

/*
 * YO check out the repository for updates
 * http://github.com/aconbere/evented
 *
 */
var Evented = function () {
  this.listeners = {};
  this.listenersOnce = {};
};

Evented.prototype.withListeners = function (ev, callback) {
  if (!this.listeners[ev]) {
    this.listeners[ev] = [];
  }

  if (!this.listenersOnce[ev]) {
    this.listenersOnce[ev] = [];
  }

  if (callback) {
    callback(this.listeners[ev], this.listenersOnce[ev]);
  }
};

Evented.prototype.trigger = function (ev, data) {
  data = data || [];

  var that = this;
  this.withListeners(ev, function (listeners, listenersOnce) {
    for (var i = 0; i < listeners.length; i++) {
      listeners[i].apply(null, data);
    }

    for (var i = 0; i < listenersOnce.length; i++) {
      listenersOnce[i].apply(null, data);
    }
    // reset any of the once listeners for this event
    that.listenersOnce[ev] = [];
  });
};

Evented.prototype.bind = function (ev, callback) {
  this.withListeners(ev, function (listeners, _listenersOnce) {
    listeners.push(callback);
  });
};

Evented.prototype.bindOnce = function (ev, callback) {
  this.withListeners(ev, function (_listeners, listenersOnce) {
    listenersOnce.push(callback);
  });
};

Evented.prototype.unbindAll = function () {
  this.listeners = {};
};

if (typeof window === "undefined") {
  exports.Evented = Evented;
}


Fixed 'open' to write binary data in Windows too

import zlib
import struct
import array
import random
import colorsys

def output_chunk(out, chunk_type, data):
    out.write(struct.pack("!I", len(data)))
    out.write(chunk_type)
    out.write(data)
    checksum = zlib.crc32(data, zlib.crc32(chunk_type))
    out.write(struct.pack("!i", checksum))

def get_data(width, height, rgb_func):
    fw = float(width)
    fh = float(height)
    compressor = zlib.compressobj()
    data = array.array("B")
    for y in range(height):
        data.append(0)
        fy = float(y)
        for x in range(width):
            fx = float(x)
            data.extend([min(255, max(0, int(v * 255))) for v in rgb_func(fx / fw, fy / fh)])
    compressed = compressor.compress(data.tostring())
    flushed = compressor.flush()
    return compressed + flushed

def write_png(filename, width, height, rgb_func):
    out = open(filename, "w")
    out.write(struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10))
    output_chunk(out, "IHDR", struct.pack("!2I5B", width, height, 8, 2, 0, 0, 0))
    output_chunk(out, "IDAT", get_data(width, height, rgb_func))
    output_chunk(out, "IEND", "")
    out.close()

def linear_gradient(start_value, stop_value, start_offset=0.0, stop_offset=1.0):
    return lambda offset: (start_value + ((offset - start_offset) / (stop_offset - start_offset) * (stop_value - start_value))) / 255.0

def LINEAR_Y(x, y):
    return y

def LINEAR_X(x, y):
    return x

def RADIAL(center_x, center_y):
    return lambda x, y: (x - center_x) ** 2 + (y - center_y) ** 2

def NO_NOISE(r, g, b):
    return r, g, b

def GAUSSIAN(sigma):
    def add_noise(r, g, b):
        d = random.gauss(0, sigma)
        return r + d, g + d, b + d
    return add_noise

def HSV(h, s, v):
    r, g, b = colorsys.hsv_to_rgb(h, s, v)
    return 255 * r, 255 * g, 255 * b

def hexstring_to_rgb(s):
    r = int(s[1:3], 16)
    g = int(s[3:5], 16)
    b = int(s[5:7], 16)
    return r, g, b

def gradient(value_func, noise_func, DATA):
    def gradient_function(x, y):
        initial_offset = 0.0
        v = value_func(x, y)
        for offset, start, end in DATA:
            if isinstance(start, str) and start.startswith("#"):
                start = hexstring_to_rgb(start)
            if isinstance(end, str) and end.startswith("#"):
                end = hexstring_to_rgb(end)
            if v < offset:
                r = linear_gradient(start[0], end[0], initial_offset, offset)(v)
                g = linear_gradient(start[1], end[1], initial_offset, offset)(v)
                b = linear_gradient(start[2], end[2], initial_offset, offset)(v)
                return noise_func(r, g, b)
            initial_offset = offset
        return noise_func(end[0] / 255.0, end[1] / 255.0, end[2] / 255.0)
    return gradient_function

import zlib
import struct
import array
import random
import colorsys

def output_chunk(out, chunk_type, data):
    out.write(struct.pack("!I", len(data)))
    out.write(chunk_type)
    out.write(data)
    checksum = zlib.crc32(data, zlib.crc32(chunk_type))
    out.write(struct.pack("!i", checksum))

def get_data(width, height, rgb_func):
    fw = float(width)
    fh = float(height)
    compressor = zlib.compressobj()
    data = array.array("B")
    for y in range(height):
        data.append(0)
        fy = float(y)
        for x in range(width):
            fx = float(x)
            data.extend([min(255, max(0, int(v * 255))) for v in rgb_func(fx / fw, fy / fh)])
    compressed = compressor.compress(data.tostring())
    flushed = compressor.flush()
    return compressed + flushed

def write_png(filename, width, height, rgb_func):
    out = open(filename, "wb")
    out.write(struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10))
    output_chunk(out, "IHDR", struct.pack("!2I5B", width, height, 8, 2, 0, 0, 0))
    output_chunk(out, "IDAT", get_data(width, height, rgb_func))
    output_chunk(out, "IEND", "")
    out.close()

def linear_gradient(start_value, stop_value, start_offset=0.0, stop_offset=1.0):
    return lambda offset: (start_value + ((offset - start_offset) / (stop_offset - start_offset) * (stop_value - start_value))) / 255.0

def LINEAR_Y(x, y):
    return y

def LINEAR_X(x, y):
    return x

def RADIAL(center_x, center_y):
    return lambda x, y: (x - center_x) ** 2 + (y - center_y) ** 2

def NO_NOISE(r, g, b):
    return r, g, b

def GAUSSIAN(sigma):
    def add_noise(r, g, b):
        d = random.gauss(0, sigma)
        return r + d, g + d, b + d
    return add_noise

def HSV(h, s, v):
    r, g, b = colorsys.hsv_to_rgb(h, s, v)
    return 255 * r, 255 * g, 255 * b

def hexstring_to_rgb(s):
    r = int(s[1:3], 16)
    g = int(s[3:5], 16)
    b = int(s[5:7], 16)
    return r, g, b

def gradient(value_func, noise_func, DATA):
    def gradient_function(x, y):
        initial_offset = 0.0
        v = value_func(x, y)
        for offset, start, end in DATA:
            if isinstance(start, str) and start.startswith("#"):
                start = hexstring_to_rgb(start)
            if isinstance(end, str) and end.startswith("#"):
                end = hexstring_to_rgb(end)
            if v < offset:
                r = linear_gradient(start[0], end[0], initial_offset, offset)(v)
                g = linear_gradient(start[1], end[1], initial_offset, offset)(v)
                b = linear_gradient(start[2], end[2], initial_offset, offset)(v)
                return noise_func(r, g, b)
            initial_offset = offset
        return noise_func(end[0] / 255.0, end[1] / 255.0, end[2] / 255.0)
    return gradient_function