TomsterMail

The latest build of Ember.

by alexspeller

HTML

<script src="http://ember.alexspeller.com/handlebars-1.0.0.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.1.0/moment.min.js"></script>
<script src="http://ember.alexspeller.com/ember-latest.js"></script>
  <script type="text/x-handlebars" data-template-name="application">
    <div class='url'>URL: {{target.url}}</div>
    <aside>

        <ul>
            <li><h2>Mailboxes</h2></li>
            {{#each}}
                <li>
                    {{#linkTo 'mailbox' this}}
                        <span class="count">
                            {{messages.length}}
                        </span>
                        {{name}}
                    {{/linkTo}}
                </li>
            {{/each}}
        </ul>
    </aside>

    <section class='main'>
        {{outlet}}
    </section>
</script>

<script type="text/x-handlebars" data-template-name="index">
    <section class="index">
        <h1>TomsterMail</h1>
        <span class='tomster'></span>
    </section>
</script>


<script type="text/x-handlebars" data-template-name="mailbox">
    <div class='filterbar'>
      {{view App.FilterView valueBinding=filter}}
    </div>
    <table>
        <tr>
          {{#linkTo "mailbox" this sort="date" tagName="th"}}Date{{/linkTo}}
          {{#linkTo "mailbox" this sort="subject" tagName="th"}}Subject{{/linkTo}}
          {{#linkTo "mailbox" this sort="from" tagName="th"}}From{{/linkTo}}
          {{#linkTo "mailbox" this sort="to" tagName="th"}}To{{/linkTo}}
        </tr>
        {{#each arrangedContent}}
            {{#linkTo 'mail' this tagName=tr}}
                <td>{{date date}}</td>
                <td>{{view.isActive}}{{subject}}</td>
                <td>{{from}}</td>
                <td>{{to}}</td>
            {{/linkTo}}
        {{/each}}
    </table>

    {{outlet}}

</script>

<script type="text/x-handlebars" data-template-name="mailbox/index">
    <section class="mailbox-index">
        Select an email
   ...

CSS

body {
  font-family: sans-serif;
  color: #333;
}

h1 {
  font-size: 2em;
  margin-bottom: 1em;
}

h2 {
  margin-bottom: 1em;
}

h4 {
  font-size: 1.5em;
  margin-bottom: 1em;
  font-weight: bold;
}

.loading {
  position: absolute;
  top: 50%;
  bottom: 50%;
  left: 50%;
  right: 50%;

  width: 100px;
  height: 50px;

  color: white;
  background-color: rgba(0,0,0,0.5);

  margin-left: -50px;
  margin-top: -25px;

  border-radius: 10px;
  text-align: center;
  line-height: 50px;

}

aside {
  float: left;
  background-color: #9cb5d1;
  width: 180px;
  height: 430px;
  border: 1px solid #ccc;
  border-right-color: #333;
}

aside h2 {
  text-transform: uppercase;
  font-size: 0.7em;
  font-weight: bold;
  letter-spacing: 0.2em;
  text-shadow: 1px 1px 0px #eee;
  padding: 15px;
  margin-bottom: 0;
}

aside a {
  color: #333;
  text-decoration: none;
  display: block;
  padding: 15px;
}

aside a.active {
  background: linear-gradient(to bottom, #7db9e8 0%,#267dc9 99%);
  color: white;
  text-shadow: 1px 1px 0px #666;
}

span.count {
  float: right;
  background-color: #505f71;
  color: white;
  padding: 0.5em 1em;
  border-radius: 40%;
  font-size: 0.6em;
  position: relative;
  top: -3px;
  font-weight: bold;
}

section.main {
  height: 430px;
  border: 1px solid #ccc;
  margin-left: 180px;
}

section .index {
  padding-top: 5em;
  text-align: center;
  height: 100%;
}

table {
  width: 99%;
  border-bottom: 1px solid #999;
  border-collapse: collapse;
}

td, th {
  padding: 0.5em;
}

th {
  font-weight: bold;
  font-size: 0.8em;
}

th.active:after {
  content: " ▲";
}
th.active.desc:after {
  content: " ▼";
}

tr {
  border-bottom: 1px solid #ccc;
  cursor: pointer;
}

td a {
  text-decoration: none;
  color: #333;
}

tr.active {
  background: linear-gradient(to bottom, #7db9e8 0%,#267dc9 99%);
  color: white;
  text-shadow: 1px 1px 0px #666;
}

section .mailbox-index {
  padding-top: 5em;
  text-align: center;
  color: #eee;
  font-style: italic;
}

section...

CoffeeScript

Ember.FEATURES['query-params'] = true

window.App = Ember.Application.create
  LOG_TRANSITIONS: true

#############
# Route Map #
#############

App.Router.map ->
  @resource 'mailbox', path: '/:id', queryParams: ['sort', 'filter'], ->
    @resource 'mail', path: '/:index'

#####################
# Route definitions #
#####################

App.ApplicationRoute = Em.Route.extend
  model: ->
    Mailbox.find()

App.MailboxRoute = Em.Route.extend
  model: (params) ->
    Mailbox.find params.id

  serialize: (mailbox) ->
    id: mailbox.id

  setupController: (controller, context, queryParams) ->
    @_super controller, context
    controller.set 'sort', queryParams.sort
    controller.set 'filter', queryParams.filter


App.MailRoute = Em.Route.extend
  model: (params) ->
    @modelFor('mailbox').messages[params.index]

  serialize: (mail) ->
    index: @modelFor('mailbox').messages.indexOf(mail)

# Just defining a route called LoadingRoute is enough to make ember
# enter this route when a promise is returned from any routes 'model'
# hook. The 'loading' template will be rendered in the application
# outlet with the LoadingView view #by default, although this can
# be overriden with the renderTemplate hook if desired
App.LoadingRoute = Em.Route.extend()

###############
# Controllers #
###############

App.MailboxController = Em.ObjectController.extend
  arrangedContent: (->
    if Em.isEmpty @get('filter')
      filtered = @get('messages') || []
    else
      filtered = (@get('messages') || []).filter (message) =>
        new RegExp(RegExp.quote(@get('filter').toLowerCase())).test(message.subject.toLowerCase())

    Em.ArrayProxy.create content: (filtered).sort (a, b) =>
      if a[@get('sort')] < b[@get('sort')] then -1
      else if a[@get('sort')] > b[@get('sort')] then 1
      else 0
  ).property 'messages.[]', 'sort', 'filter'

  filterDidChange: (->
    Em.run.debounce @, 'transitionFilter', 500
  ).observes 'filter'

  transitionFilter: ->
    string =...