Ember.js / Ember Data - Render hasMany key/value pair in template - ember.js

I have a Document model, which has attributes/properties defined to it using a hasMany relationship. The purpose is to freely be able to define content in different areas of the document like header, body, footer while also creating presentational attributes like color or image.
KF.Document = DS.Model.extend
title: DS.attr 'string'
documentAttributes: DS.hasMany 'documentAttribute'
KF.DocumentAttribute = DS.Model.extend
attrKey: DS.attr 'string'
attrValue: DS.attr 'string'
document: DS.belongsTo 'document'
Document.documentAttributes returns a DS.ManyArray so in order to render it I could do the following:
{{#each da in documentAttributes}}
<p>{{da.attrKey}} - {{da.attrValue}}</p> <!-- returns: "header - this is my header" -->
{{/each}}
The problem is that I want to access the keys directly (using a proxy?) so I can bind the data directly like so:
{{textarea value=documentAttributes.header cols="80" rows="6"}}
<img {{ bindAttr src="documentAttributes.imageSrc" }} >
{{textarea value=documentAttributes.footer cols="80" rows="6"}}
How should I approach this?

An approach could be to enhance an em view (for the brave maybe a component as well), or create a proxy, that receives a DocumentAttribute object and defines dynamically a property with name the value of the attrKey and return the value of the attrValue. You could achieve this with the following code ,
http://emberjs.jsbin.com/ehoxUVi/2/edit
js
App = Ember.Application.create();
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return createProxy(App.DocumentAttribute.create());
}
});
App.DocumentAttribute = Ember.Object.extend({
attrKey:"theKey",
attrValue:"theValue"
});
function createProxy(documentAttr){
App.DocumentAttributeProxy = Ember.ObjectProxy.extend({
createProp: function() {
_this = this;
var propName = this.get('attrKey');
if (!_this.get(propName)) {
return Ember.defineProperty(_this, propName, Ember.computed(function() {
return _this.get('attrValue');
}).property('attrKey'));
}
}.observes('content')
});
var proxy = App.DocumentAttributeProxy.create();
proxy.set('content',documentAttr);
return proxy;
}
HB
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{attrKey}}
<br/>
{{attrValue}}
<br/>
{{theKey}}
</script>

I couldn't get melc's solution to work with the DS.ManyArray returned by the relationship.
But his examples gave me some ideas and I did the following. Basically mapping the items through a "shortcut key" on the controller.
KF.DocumentsShowRoute = Ember.Route.extend
setupController: (controller, model) ->
controller.set('model', model)
# make an `Object` to store all the keys to avoid conflicts
controller.set('attrs', Ember.Object.create())
# loop through all `DocumentAttributes` in the `DS.ManyArray` returned by the relationship,
# get the `attrKey` from each item and make a shortcut to the item under `attrs` object
model.get('documentAttributes').forEach (item, index, enumerable) ->
key = item.get('attrKey')
controller.get('attrs').set(key, item)
The template, where header is the attrKey
{{input value=attrs.header.attrValue}}

Related

Ember js store and pass clicked {{#link-to}} property to Route model

I am new to Ember.js, and I am trying to learn it by rewriting an old app of mine. Unfortunately I got stuck fairly early and need some help, as couldn't find the answer on the Web.
I have the following code:
App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function () {
this.resource("users", function () {
this.resource("score", {path: "/:nickname"});
});
});
App.UsersRoute = Ember.Route.extend({
model: function () {
var url = "server url";
return new Ember.RSVP.Promise(function (resolve, reject) {
resolve($.getJSON(url));
});
}
});
App.ScoreRoute = Ember.Route.extend({
model: function () {
var url = "server url" + "?nickname=";
url += passedNicknameFromclickedLink;
return new Ember.RSVP.Promise(function (resolve, reject) {
resolve($.getJSON(url));
});
}
});
<script type="text/x-handlebars">
<nav>
{{#link-to "users"}}Users{{/link-to}}
</nav>
<main class="main-wrapper">{{outlet}}</main>
</script>
<script type="text/x-handlebars" data-template-name="users">
<div class="user-score">{{outlet}}</div>
<h2>Users</h2>
{{#each}}
<div>{{#link-to "score" nickname}}{{nickname}}{{/link-to}}</div>
<div>{{games}}</div>
<div>{{score}}</div>
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="score">
<h2>Score for {{nickname}}</h2>
{{#each categoryScores}}
<div class="scores">
<div>{{category}}</div>
<div>{{gamesPlayed}}</div>
<div>{{score}}</div>
</div>
{{/each}}
</script>
What I am trying to do is to take the {{nickname}} property clicked by the user in the {{#link-to}} in "users" template and pass it as "passedNicknameFromclickedLink" variable to App.ScoreRoute in order to make a server request. I was wondering what is the proper way to do this?
It looks like you've almost everything right, you just need 2 things. First, you can access the nickname dynamic segment in the model hook with the params parameter.
model: function(params) {
var passedNicknameFromclickedLink = params.nickname;
}
Secondly, because Ember assumes quite a few conventions with your route and models, you'll have to override the serialize method in the route as well. With the serialize method, what you want to do is start with your model (the JSON from the server) and give back the nickname that was used to find that model. I don't know exactly what your JSON looks like, but it might look something like this:
serialize: function(model) {
return encodeURI(model.nickname);
}

Two data sources and two outlets in ember.js

I'm trying to create something really simple with ember.js, and I'm getting badly lost between old examples, new examples, and extensive documentation.
I want to create an application with no functionality, that simply shows two sets of data in two columns. The real application uses JSON data from two different sources and is more complicated, but the problem reduces to getting the below to work:
<div id="appholder">
<script type="text/x-handlebars">
{{outlet left}}
{{outlet right}}
</script>
<script type="text/x-handlebars" data-template-name="left">
<div id="left">
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
</div>
</script>
<script type="text/x-handlebars" data-template-name="right">
<div id="right">
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
</div>
</script>
</div>
and in the javascript something like
App = Ember.Application.create({
rootElement: '#appholder'
});
App.LeftController = Ember.ArrayController.extend({
model: function() {
return ['left one', 'left two'];
}
});
App.RightController = Ember.ArrayController.extend({
model: function() {
return ['right one', 'right two'];
}
});
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('left', {
outlet: 'left',
controller: 'left',
});
this.render('right', {
outlet: 'right',
controller: 'right',
});
}
});
to output the hoped for result, a left column with list entries from one data source and a right column with two entries from another column.
Could somebody ideally provide a js fiddle with the above adapted to working code? Any part of it can change, to use {{render}} or {{view}} in the templates and whatever the js should be.
Thank you for any help
http://emberjs.jsbin.com/mifer/2/edit
Here is a working JSBin
First in order to use a function as a property, you must make it a computed property with the property() function:
App.LeftController = Ember.ArrayController.extend({
model: function() {
return ['left one', 'left two'];
}.property()
});
Secondly, the renderTemplate code is in the wrong place. You put it in the index route but in reality it should be in the application route. If you had {{outlet}} in your application template, the index template would have been rendered into it. Then, if you had those two named outlets inside the index template, what you had would have almost worked (you need to call this.render() or this._super() whenever you use renderTemplate if you want the route template to render.
But, you have two named outlets which you want to manually render into inside of your application template. Hence:
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function(){
this.render();
this.render('left', {outlet: 'left', into: 'application'});
this.render('right', {outlet: 'right', into: 'application'});
}
});
Now this next approach is how I tackle multiple models in my dashboard application. I am constantly needing to replace the sections of widgets so I use multiple named outlets. I've restructured your code so that the application template renders the index template into its single unnamed outlet.
Keys to this approach:
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.RSVP.hash({
left: ['left one', 'left two'],
right: ['right one', 'right two']
});
},
renderTemplate: function(controller, model){
this.render();
this.render('left', {outlet: 'left', into: 'index', controller: 'left', model: model.left});
this.render('right', {outlet: 'right', into: 'index', controller: 'right', model: model.right});
}
});
Whenever you need to return multiple models, and you want your route to block until all models are returned, use Ember.RSVP.hash. You return multiple promises, each as properties of your returned model. RenderTemplate takes two parameters, controller and model so you access your model in the renderTemplate to manually pass the model into the controller of the template you are rendering.
As a slight alternative to this approach, if you need to render multiple datasources on the page, but you do not ever need to dynamically replace the whole template backing one of the models (ie render once and done), you can use the {{render}} helper.
<script type="text/x-handlebars" data-template-name="index">
<p>Index Template</p>
{{render 'left' model.left}}
{{render 'right' model.right}}
</script>
The benefit of this code is that our route has simplified.
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.RSVP.hash({
left: ['left one', 'left two'],
right: ['right one', 'right two']
});
}
});
But, we have lost the ability to easily render something else here via action since we no longer have named outlets.

Ember -- TypeError: arrangedContent.addArrayObserver is not a function

I am trying to build a simple category browser with ember. I have two very simple views. When the user visits / they will see a list of all categories and when they click a category in that list they will be directed to #/10 where 10 is the id.
My problem is that when a user clicks on a category at the / route I am getting the following error
TypeError: arrangedContent.addArrayObserver is not a function
[Break On This Error]
didChange: 'arrangedContentArrayDidChange'
If I refresh the page at the #/10 route the proper api call is made to my backend /api/categories?parent=99. What could I be doing wrong that is throwing this error during the transition? A full example of my code is below.
Templates:
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="categories">
{{#each category in controller}}
<p>{{#linkTo 'category' category}}{{ category.name }}{{/linkTo}}</p>
{{/each}}
</script>
<!--this is an array instead of object -->
<script type="text/x-handlebars" data-template-name="category">
{{#each category in controller}}
<p>{{category.name}}</p>
{{/each}}
</script>
Javascript:
var App = Ember.Application.create();
App.Router.map(function(){
this.resource('categories', { path : '/' });
this.resource('category', { path : '/:category_id' });
});
App.CategoriesRoute = Ember.Route.extend({
model: function(){
return App.Category.find();
}
});
//this is causing the error possibly
App.CategoryRoute = Ember.Route.extend({
model: function(params){
return App.Category.find({parent: params.category_id});
}
});
App.CategoryController = Ember.ArrayController.extend();
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.RESTAdapter'
});
DS.RESTAdapter.configure("plurals", {
category: "categories"
});
App.Category = DS.Model.extend({
name: DS.attr('string'),
parent_id: DS.attr('number')
});
Debug info:
DEBUG: Ember.VERSION : 1.0.0-rc.1
DEBUG: Handlebars.VERSION : 1.0.0-rc.3
DEBUG: jQuery.VERSION : 1.9.0
Hint: After writing this i realized that you probably did not get the model hook right. This hook is called when you are entering your app via url. It converts the URL into an appropriate model and transition with this model into the Route. I guess you thought that this model() hook would be called with the arguments of {{#linkTo}}? This is not the case!
This does not work because you are passing a single model to your #linkTo helper in your template. So Ember wants to set this single object as content of your ArrayController. This causes your error. And your model hook returns an array. Rule of Thumb: You should always pass the same data structure to #linkTo, which you are returning in your model hook.
Therefore i would suggest to use an event instead of linkTo and do the following:
<script type="text/x-handlebars" data-template-name="categories">
{{#each category in controller}}
<p {{action 'showParentCategory' category}}>{{category.name}}</p>
{{/each}}
</script>
App.CategoriesRoute = Ember.Route.extend({
model: function(){
return App.Category.find();
},
events: {
showParentCategory : function(parentCategory){
var cats = App.Category.find({parent: parentCategory.get("category_id")});
this.transitionTo("category", cats);
}
}
});
What have i done here?
I created an action called "showParentCategory".
As this is an action with is about routing, i am handling this event in your CategoriesRoute. As you see, events/action handlers are declared in the events property of your route.
I am performing the same logic there as in your model hook and then i am calling manually the transitinTo with the fetched categories.
UPDATE: How to serialize
By implementing serialize, you are telling Ember what to put into your url.
App.CategoryRoute = Ember.Route.extend({
model: function(params){
return App.Category.find({parent: params.category_id});
},
serialize : function(models){
var first = models.objectAt(0);
return {
category_id : first.get("parentId")
}
}
});
If you do #each over a numeric value instead of doing it on an array content in your template, this issue occurs.
I had a numeric value count in my 'poll' model . I was iterating like,
{{#each poll in content.count}}
{{/each}}
I think, we have to use #each only on ember arrays.

Ember.js: Loop over data from ArrayController in unassociated View

I have a route set up which pulls Account information from a REST endpoint:
Social.Router.map(function() {
this.resource('accounts');
});
Social.AccountsRoute = Ember.Route.extend({
model: function() {
return Social.Account.find();
}
});
Social.Account = DS.Model.extend({
name: DS.attr('string'),
username: DS.attr('string')
});
Social.AccountsController = Ember.ArrayController.extend();
I can loop over that data in my template like so:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
<div class="avatar-name">
<p>{{account.name}}</p>
<p>{{account.username}}</p>
</div>
{{/each}}
</script>
I have another template in which I'd like to use the same account data. How would I retrieve the information associated with the Account model from within a View so that I can make it available in the corresponding template?
Social.NewPostView = Ember.View.extend({
tagName: 'div',
accounts: function(){
// return Account data here?
}
});
Update 1
Here's a quick view of my UI
On the left is "live" account data. On the right is static HTML. I'd like to reuse the data from the left on the right. Make sense?
You should take a look to the needs property for controllers. This allow you to specify a list of controllers you will be able to access from the current one.
More information on the emberjs guides
NewPostController
Social.NewPostController = Ember.ObjectController.extend({
needs: ['accounts'],
init: function(){
this._super();
console.log(this.get('controllers.accounts'))
}
});
Then, from your template:
<script type="text/x-handlebars" data-template-name="newPost">
{{#each account in controller.controllers.accounts}}
<div class="avatar-name">
<p>{{account.name}}</p>
<p>{{account.username}}</p>
</div>
{{/each}}
</script>

How to render a stream view like in twitter of facebook with emberjs arraycontroller?

To render a content of an array with emberjs we usually do the following
<ul>
{{#each controller}}
<li>{{name}} by {{artist}}</li>
{{/each}}
</ul>
How to make a live stream view like we have with twitter (of facebook) where a new stream is added on the top of the streams list ?
On the controller you can set the sortProperties see here to specify on which property the array should be sorted and you can set sortAscending (which is a boolean) to specify which direction the array should be sorted.
When you change the array the view will automatically update.
see this fiddle: http://jsfiddle.net/ZnMFK/2/
or this fiddle: http://jsfiddle.net/KfzFE/ to show the DOM gets updated when the array is changed.
HTML:
<script type="text/x-handlebars" data-template-name="index">
<div class="patient-view extended">
{{#each controller}}
<p>Name: {{name}}</p>
{{/each}}
</div>
</script>
App:
window.App = Em.Application.create();
App.Patient = Em.Object.extend({
order: undefined,
name: undefined
});
App.IndexView = Em.View.extend({
click: function() {
this.get('controller')
.set('sortAscending', !this.get('controller').get('sortAscending'));
}
});
App.IndexController = Em.ArrayController.extend({
sortProperties: ['order'],
sortAscending: false
});
App.IndexRoute = Em.Route.extend({
model: function() {
return Em.A([App.Patient.create({
name: "Bert",
order: 2
}), App.Patient.create({
name: "Ernie",
order: 1
})]);
}
});