EmberJS: How to update model attributes - ember.js

I've got a list of messages that are provided by a Rails backend. What I need is when the "toggle_visibility" action button is pressed, it would toggle the "publicly_viewable" property. This means, making a corresponding REST call (to effect the database) and changing the state of the corresponding cached message. Here is where I'm at so far.
Here's what I've got so far, that manages to end up on the debug console:
# app.js
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: 'http://localhost:3000'
})
});
App.Message = DS.Model.extend({
body: DS.attr('string'),
mobile_number: DS.attr('string'),
publicly_viewable: DS.attr('boolean'),
created_at: DS.attr('date')
});
App.Router.map(function() {
this.resource('messages');
});
App.MessagesRoute = Ember.Route.extend({
model: function() { return App.Message.find() }
});
App.MessagesController = Ember.ArrayController.extend({
toggle_visibility: function(){
debugger;
}
});
# index.html
{{#each model}}
<button class="close" {{action toggle_visibility this}}><i class="icon-eye-close"></i></button>
<p class="message_body lead">{{body}}</p>
<small class="source_number">from {{mobile_number}}, received {{date created_at}}</small>
{{/each}}
I've been spending the past few hours reading through the Ember Guides and while I've gotten an idea on what the different classes there are, I still can't visualize clearly how to go about it. Particularly, I'm not sure if this should be a route concern or a controller, and I know that if ever it was a controller responsibility, I know that it should be on an ObjectController but I've been having trouble making it work.

You can use ArrayController#itemController and define a controller for the individual record in your ModelArray. Then you have to specify in the Array Controller the Object Controller responsible for a single object, which you have to reference as well in Handlebars. You can do something like this:
JS:
App.MessageController = Ember.ObjectController.extend({
visibilityClass: function() {
var visibility = this.get('model.publiclyViewable');
return 'toggle-visibility mdi-action-visibility%#'.fmt(
visibility ? '':'-off'
);
}.property('model.publiclyViewable'),
actions: {
toggleVisibility: function() {
var model = this.get('model');
model.toggleProperty('publiclyViewable');
model.save();
}
}
});
Handlebars:
<script type="text/x-handlebars" data-template-name="messages">
<!--
At this point the {{each}} helper will know how to lookup for
the controller simply by it's name
-->
{{#each model itemController="message"}}
<div class="panel panel-primary">
<div class="panel-heading">
<div class="pull-left">
<h3 class="panel-title">{{title}}</h3>
</div>
<div class="pull-right">
<a {{action 'toggleVisibility'}}>
<i class={{visibilityClass}} style="color: #FFF"></i>
</a>
</div>
</div>
<div class="panel-body">
{{body}}
</div>
<div class="panel-footer">
<i class="mdi-communication-quick-contacts-dialer"></i> {{mobileNumber}}
<i class="mdi-notification-event-note"></i> {{createdAt}}
</div>
</div>
{{/each}}
</script>
(see fiddle)
Note: Updated to Ember 1.11.x-beta and changed the code a little bit

Related

Ember.js models not being rendered in nested route

I have an Ember app setup, and when visiting /playlist/1, it renders the templates as expected. However, the model data isn't being displayed. I have a playlist.hbs file with an outlet, and a playlist folder with index.hbs inside of that folder with html and handlebars to display data. I have both App.PlaylistIndexController & App.PlaylistIndexRoute defined.
App.Router.map(function() {
this.resource('account', {path: '/account/:accountId'}, function() {
this.route('login');
});
this.resource('playlist', { path: '/playlist/:playlist_id'}, function() {
this.route('edit');
});
});
FWIW, everything was working properly prior to adding the nested route (with my controller and route defined as App.PlaylistController and App.PlaylistRoute respectively)
"playlist":{"id":1,"name":"playlistname"}
Any ideas how to get the playlist data to display properly?
UPDATE:
App.PlaylistIndexRoute = App.AuthenticatedRoute.extend({
setupController: function(controller, model) {
this._super(controller, model);
var online = this.get('store').find('account');
this.controllerFor('playlistViewers').set('model', online);
},
});
<div id="main">
<div id="primary">
<section id="playlist">
<header class="playlist-header">
<h2>Playlist</h2>
<h1>{{name}}</h1>
</header><!--.playlist-header-->
<div class="playlist-content">
<ul>
{{#each song in songs}}
<li {{action 'play' song}} class="show-for-mobile">
<button {{bind-attr class="song.isPlaying:icon-volume-up:icon-play song.isStreaming:icon-adjust"}} ></button>
<div class="song-meta">
<span class="song-name">{{song.name}}</span>
<span class="song-artist">{{song.artist}}</span>
</div><!--.song-meta-->
</li>
{{/each}}
</ul>
</div><!--.playlist-content-->
</section><!--#playlist-->
</div><!--#primary-->
{{partial "sidebar"}}
That's my current playlist/index.hbs file, but even {{name}} displays nothing
Your PlaylistIndexRoute needs to have a model. This can be the model that was loaded by the playlist resource.
App.PlaylistIndexRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.modelFor('playlist');
}
});

Change a view property from an unrelated controller

I have the following view:
App.MessageTrayView = Bootstrap.AlertMessage.extend({
message: 'This is a message.',
});
Displayed in this template:
<script type="text/x-handlebars" data-template-name="nodes">
<article>
<form class="form-horizontal">
<fieldset>
{{view App.MessageTrayView id="message-tray-view"}}
<div id="legend" class="">
<legend class="">Nodes <span class="badge">{{controllers.nodesIndex.length}} records</span>
<div class="pull-right">
<a {{action destroyAllRecords}}><i class="icon-remove-circle"></i><a/>
{{#linkTo "nodes.new" class="btn btn-primary"}}Add Node{{/linkTo}}
</div>
</legend>
</div>
{{outlet}}
</fieldset>
</form>
</article>
</script>
And this unrelated controller:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
App.MessageTrayView.set('message', 'All nodes have been deleted');
},
});
I want to change the message displayed as soon as the destroyAllRecords is triggered. This is not working (the error message in the console is telling me that I am doing something * very* wrong). How can I change the message property, so that the changes are directly visible on the page?
You can see the code live here
One quick way of doing this could be to define a property on the App namespace:
App = Ember.Application.create({
messageTrayContent: ''
});
then bind to it in your view using the suffix Binding after your property name:
App.MessageTrayView = Bootstrap.AlertMessage.extend({
messageBinding: 'App.messageTrayContent'
});
Now doing:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
App.set('messageTrayContent', 'All nodes have been deleted');
},
});
should work.
Hope it helps.

Mark the current detail entry in a master list

I have a list of users which are displayed in a master view on the left side (Twitter Bootstrap CSS). Details of each user can be shown by clicking the show button. They will be displayed on the right side (detail).
How can I remove the show button for the currently displayed user? e.g. #/users/1 shouldn't render the show button for the first user.
index.html
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span4'>
<table class='table table-striped'>
{{#each model}}
<tr>
<td>{{lastName}}</td>
<td>{{#linkTo 'user' this}}<button class="btn" type="button">show</button>{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div class='span8'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h1>{{firstName}} {{lastName}}</h1>
</script>
app.js
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
})
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' })
})
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
})
App.User.FIXTURES = [{
id: 1,
firstName: "Bill",
lastName: "Clinton"
}, {
id: 2,
firstName: "Barack",
lastName: "Obama"
}]
Ember provides some support for doing what you want. By default it sets the "active" css class on the selected element. You can find more information about that here: http://emberjs.com/api/classes/Ember.LinkView.html (note that the {{#linkTo}} is just a helper based on the LinkView).
The simplest way to override this behavior, since instead of "active" you want to hide the button, would be to make use of the hide class that comes with Twitter Bootstrap. So your users template would look like:
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span4'>
<table class='table table-striped'>
{{#each model}}
<tr>
<td>{{lastName}}</td>
<td>{{#linkTo 'user' this activeClass="hide"}}<button class="btn" type="button">show</button>{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div class='span8'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h1>{{firstName}} {{lastName}}</h1>
</script>

Ember.js Router: Embedded resources

I'm creating an Ember application to display twitter feeds but I am having trouble with displaying individual tweets through embedded resources.
The code is as follows:
Templates
<script type="text/x-handlebars" data-template-name="tweets">
<div id="stream">
{{#each tweet in controller}}
<div class="tweet">
<p class="tweet_text">{{tweet.text}}</p>
<p> {{#linkTo "tweet" tweet}} {{tweet.id}} {{/linkTo}}</p>
</div>
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="tweet">
<div id="detail">
{{text}}
</div>
</script>
Router
window.App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function(){
this.resource('tweets',function(){
this.resource('tweet',{path: ':tweet_id'})
});
});
// (1) App.Router.map(function(){
// this.resource('tweets')
// this.resource('tweet',{path: ':tweet_id'})
// });
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('tweets');
}
});
App.TweetsRoute = Ember.Route.extend({
model: function(){
var me = [];
$.getJSON("http://search.twitter.com/search.json?q=emberjs&rpp=200&count=200&callback=?",
{},
function (data) {
$.each(data.results,function(k,tweet){
var tweet = App.Tweet.create({
created_at: tweet.created_at,
from_user: tweet.from_user,
profile_image_url: tweet.profile_image_url,
text: tweet.text,
id: tweet.id
});
me.pushObject( tweet );
});
});
return me;
}
});
Objects & Controllers
App.TweetsController = Ember.ArrayController.extend({});
App.Tweet = Ember.Object.extend({
created_at: "",
from_user: "",
profile_image_url: "",
text: "",
id: 0
})
As you can see, I have a commented our router (1) which works in finding the correct tweet, and rendering it in the tweet template. However, I would like this route to be nested so that I can implement it as a Master-Detail application.
Using the LOG_TRANSITIONS, I can see that the correct routes are initialised, but I cannot get the nested resource path to render.
Any ideas would be hugely appreciated, thanks in advance.
I got this working. For anyone stuck on something similar, this is how I did it:
Templates - Changed the {{#linkTo}} "tweet"... to {{#linkTo}} "tweets.tweet"... AND added an {{outlet}}
<script type="text/x-handlebars" data-template-name="tweets">
<div id="stream">
{{#each tweet in controller}}
<div class="tweet">
<p class="tweet_text">{{tweet.text}}</p>
<p> {{#linkTo "tweets.tweet" tweet}} {{tweet.id}} {{/linkTo}}</p>
</div>
{{/each}}
</div>
{{ outlet }}
</script>
Router - Changed 'this.resource' to 'this.route'
App.Router.map(function(){
this.resource('tweets',function(){
this.route('tweet',{path: ':tweet_id'})
});
});
Caveat
I think this is a workaround and that the nested resource was the correct approach in this context. I understand that a nested route should be "a verb" or action route. I would still be grateful if anyone knows the correct approach to the question but hope the above helps others where relevant.

Applying JQuery effect when ember-data has finished loading and element is rendered

I have a list of items that I want to make draggable. I am using ember-data to get the items from my API and then render them in a view with an ArrayController. I can successfully load the items and render them but I don't know where or when to put the JQuery draggable function.
I have tried using didInsertElement on my view but this is triggered when the view is rendered and not when the items are loaded. I have also tried to put an observer on my ArratController to run the code when the array length changes (i.e when an element is added to the array) None of these things worked.
Any ideas?
My JS code:
var REVISION = 9;
// Application namespace
var App = Ember.Application.create({
ApplicationView: Ember.View.extend({
templateName: 'application',
classNames: ['application-view']
}),
ApplicationController: Ember.Controller.extend(),
RewardsView: Em.View.extend({
templateName: 'rewards',
click: function(event) {
console.log(event);
//window.location.href = event
},
didInsertElement: function() {
this.$(".draggable").draggable();
}
}),
RewardsController: Em.ArrayController.extend({
rewardAdded: function() {
$(".draggable").draggable({
cursor: 'move', // sets the cursor apperance
revert: 'invalid', // makes the item to return if it isn't placed into droppable
revertDuration: 900, // duration while the item returns to its place
});
}.observes('length')
}),
ready: function(){
console.log("Created App namespace");
},
Router: Ember.Router.extend({
goToRewards: Ember.Route.transitionTo('root.rewards'),
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
}),
rewards: Ember.Route.extend({
route: '/rewards',
enter: function ( router ){
console.log("The rewards sub-state was entered.");
},
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('content','rewards', App.store.findAll(App.Rewards));
}
}),
})
})
});
App.Rewards = DS.Model.extend({
provider: DS.attr('string'),
name: DS.attr('string'),
description: DS.attr('string'),
discount: DS.attr('string'),
img: DS.attr('string'),
video: DS.attr('string'),
price: DS.attr('string'),
available_quantity: DS.attr('string'),
didLoad: function() {
console.log('model loaded', this);
}
});
App.store = DS.Store.create({
revision: REVISION,
adapter: DS.DjangoTastypieAdapter.extend({
serverDomain: "http://example.com",
namespace: "api/v1"
}),
});
// Start!
App.initialize();
My handlebars template:
{% handlebars "rewards" %}
<div class="row-fluid">
<div class="span6">
<div class="box paint color_7">
<div class="title">
<div class="row-fluid">
<h4> Available Rewards </h4>
</div>
</div>
<!-- End .title -->
<div class="content">
<div class="accordion" id="accordion2">
{{#each reward in controller}}
<div class="draggable accordion-group">
{{#with reward}}
{{#if isLoaded}}
<div class="accordion-heading">
<a class="accordion-toggle collapsed" data-toggle="collapse" data-parent="#accordion2" {{bindAttr href="reward.id"}}> {{name}} {{id}} </a>
</div>
<div {{bindAttr id="reward.id"}} class="accordion-body collapse" style="height: 0px; ">
<div class="accordion-inner"> {{description}} </div>
</div>
{{else}}
Loading...
{{/if}}
{{/with}}
</div>
{{/each}}
</div>
</div>
<!-- End .content -->
</div>
<!-- End .box -->
</div>
{% endhandlebars%}
use findQuery instead of findAll
router.get('applicationController').connectOutlet('content','rewards',App.store.findQuery(App.Rewards));
You get the property isLoaded for the content, now you can add observer on isLoaded property to run your required functionality as follows
startDraggableFunctionality: function(){
if(this.get('content.isLoaded'){
/*Your code goes here...*/
}
}.observes('content.isLoaded')
After Rendering + Data loaded
Inside the view add the following method as follows
View
//This method will be executed when the view has finished rendering
afterRender: function(){
this.get('controller').set("viewRendered", true);
}
Controller
viewRendered: false, //set it to false initially
startDraggableFunctionality: function(){
if(this.get('content.isLoaded') && this.get('viewRendered')){
/*Your code goes here...*/
}
}.observes('content.isLoaded', 'viewRendered')
This way if the view renders before loading content, isLoaded make sures that function will be executed only after data has been loaded & vice-versa