I'm building a playlist. I have two models describing a json response from the server
App.ChartsModel = Em.Model.extend({
playlist: Em.belongsTo('App.PlaylistModel', { key: 'playlist', embedded: true })
})
App.PlaylistModel = Em.Model.extend({
description: Em.attr()
uc_date: Em.attr()
})
The instances of these models are loaded into the controller successfully in the route. Then I have my primary template:
#playlists
App.PlaylistView controllerBinding="controllers.playlist" contentBinding="content.playlist"
p {{content.playlist}}
Then the App.PlaylistView template looks like:
#playlist
p {{this}}
p {{model}}
Here is what renders:
<div id="playlists">
<div id="playlist">
<p>
<App.PlaylistController:ember456>
</p>
</div>
<p>
<App.PlaylistModel:ember454>
</p>
</div>
I expected the "#playlist" div to have a p-tag set with the instance of App.PlaylistModel. Not sure why the contentBinding isn't taking effect.
Any ideas?
UPDATE * It appears that setting the controllerBinding nullifies the contentBinding. Is there a way to set the controller AND the content in the view?
I think loading the models into the controller is the right approach. I will assume that it's the PlayListController, so its model is a Playlist and you have another property called 'charts' with the Charts instance.
For the view, however, you should just use the controller as its context (not content) and access the models through that context. I would not mess with its controller or context bindings directly nor think of them as different. If you really need to specify things in a binding, you might try using a unique name e.g. chartsBinding="controllers.playlist.charts". Your contentBinding may very well be working. You would access it in the template with {{content}}, not {{this}}.
Anyway, you may want to try specifying the controller as the view's context like:
#playlists
App.PlaylistView contextBinding=controller
Then, in the App.PlaylistView template:
#playlist
p {{this}}
p {{charts}}
p {{model}}
Should give you: playlist controller (the context), charts instance, and playlist instance.
Related
I am looking for a way to access model data in a route when using a view to display model attributes.
Example
Template
<h2>New post</h2>
<form {{action save model on="submit"}}>
<label>Title</label>
{{input type="text" value=title placeholder="title" id="title"}}
<label>Text</label>
{{view "tinymce" value=text }}
<button>Post</button>
</form>
View Template
<textarea id="tinymce">
</textarea>
View
export default Ember.View.extend({
templateName: 'views/tinymce-textarea',
didInsertElement: function() {
tinymce.EditorManager.execCommand('mceRemoveEditor',true, 'tinymce');
tinymce.EditorManager.execCommand('mceAddEditor',true, 'tinymce');
}
});
Router
export default Ember.Route.extend({
....
actions : {
save : function(model) {
if (!model.get('title').trim() || !model.get('text').trim()) {
return;
}
model.save().then(this.onSuccessfulSave.bind(this), this.onFailedSave.bind(this));
}
}
});
Now, obviously this doesn't work, since model.text is never bound in the view, like it would be if I were to use the textarea template helper:
{{textarea value=text placeholder="text" id="text"}}
But this is just one of many (many) ways I have tried to get this to work, and I am at a complete loss as how one would access model attributes in the route when using a view. And it does seem like a pretty common usecase to me too.
I have failed to find information regarding this on SO or anywhere else, so if anyone is able to help me, thanks in advance! / AS.
So one of the main things that you're missing out is binding the view to the controller. This is actually really straight forward to do, but without it Ember doesn't know that it should propagate changes between the two. The first thing I would do is this:
{{view "tinymce" valueBinding="text" }}
This says that the views value will be binded to the controller's text value. Whenever view's value is updated, it will propogate to the controller and vice versa.
The next item to take care of is actually binding the value in the view. All you need to do is tell the input to bind it's value to the view's value. This can be done like this
{{textarea value="view.value" placeholder="text" id="text"}}
Try this out, and you can use this jsbin that I created as an example:
http://emberjs.jsbin.com/qanudu/26/
If you have any other questions just let me know, but this should solve your issues!
My Ember.js app is set up roughly like this:
Router:
App.Router.map ->
#resource('site', { path: '/' }, ->
#resource('dashboard')
#resource('account')
#resource('pages', { path: '/:page_slug'}))
Routes:
App.ApplicationRoute = Ember.Route.extend
model: ->
return App.Site.find(1)
App.PagesRoute = Ember.Route.extend
model: (params)->
return App.Page.find(params.page_slug)
EDIT:
Controller (JS not Coffee):
App.PagesController = Ember.ObjectController.extend({
needs: 'ApplicationController',
...
});
I have an ApplicationController and a PagesController. When I'm on a page, I want to call an action to delete the current page. If I place it in the PagesController it works kind of ok, but a navigation menu with a list of pages in the ApplicationView doesn't get updated until I refresh the page. So... I assume I need to place the action in the ApplicationController and add needs: ['ApplicationController'] to my PagesController.
However, when I do that, everything in my Pages template disappears.
I have an {{outlet}} in my Application template and another on in the Site template which ultimately displays the Pages template. Yeah, complicated, I know and there's probably a better way to do it, so any suggestions would be greatly appreciated. Oh and BTW, I'm a real Ember.js newb, so examples need to be spelled out explicitly. (Drawing them with pretty colors in crayon would actually be ideal.)
Thanks in advance for any help.
Here's the refresh issue. In the root resource site I'm loading in one Site model which the Rails backend returns using the url being sent in. Eventually, this app will be used with multiple domains, and each domain will be served it's own website (roughly based on the WP-Multisite concept). Then in the pages route, I'm loading one page of the site based on its slug attribute. That's all working fine. So, now I want to allow the user to be able to add and remove pages as they want.
So, the issue is this. When I delete a page at the PagesController level, the page deletes just fine, but the ApplicationController doesn't get notified. Namely, my nav menu:
<ul class="left">
{{#each page in page}}
<li>{{#link-to 'pages' page.slug}}{{page.menu_name}}{{/link-to}}</li>
{{/each}}
<li id="add-page-button"><a data-tooltip title="Click here to add a new page to your site." href="#" data-reveal-id="addPageModal" data-reveal>+</a></li>
</ul>
doesn't get notified that a page is missing and so it doesn't update the nav menu by removing that page from the list. Adding works fine, the nav list is updated when a new page is added, but I'm doing that at the ApplicationController level like so:
addNewPage: ->
# Get the site
site = #get('model')
# Get all the pages associated with the site
pages = site.get('page')
title = this.get('newTitle')
if (!title.trim())
return
slug = this.get('newSlug')
if (!slug.trim())
return
menu_name = this.get('newMenuName')
if (!menu_name.trim())
return
# Create a new page passing in our title, slug and menu_name
pages.create({title: title, slug: slug, menu_name: menu_name})
# Save the pages
pages.save()
if (pages.isError)
console.log(pages.errors)
else
#set('newTitle', '')
#set('newSlug', '')
#set('newMenuName', '')
$('#addPageModal').foundation('reveal', 'close')
And this is my deletePage code on the PagesController (sorry, I've got a mix of JS and CoffeeScript):
deletePage: function (slug) {
var page = this.get('model');
this.get('ApplicationController').deletePage(page);
this.toggleProperty('isEditingTitle');
this.toggleProperty('isShowingDeleteConfirmation');
this.transitionToRoute('site.index');
}
When I try this, Ember rather helpfully lets me know I need to add needs but again, when I do that, my Pages template is blank.
Oh yes, I have even tried setting the target attribute on the button calling the deletePage action.
I realize what I need to do is somehow access the Site model in my PagesController, but how should I go about that.
This post has taken on epic proportions. Sorry about that. Thanks again for any help.
needs doesn't do anything on the route, if you want to access a controller from a route you can just use this.controllerFor('application')
Additionally when using needs in a controller definition it should be like this needs: 'application' or needs: ['foo', 'application']...
Okay, all, I figured it out. For posterity's sake (and others who go looking for this). Here's how to delete an object in a model that has a belongsTo association.
ApplicationController:
deletePage: function () {
# First we find the Site which all pages belongTo
# Just ignore the hard-coded '1' it's something I have to do to get it to work
site = App.Site.find(1);
# Then we get the current model, i.e., the 'page' we're on and about to delete
page = this.get('model');
# Then, we get all 'pages'
pages = site.get('page');
# We remove the object from the page which updates the navigation menu
pages.removeObject(page);
# We delete the record and save the 'pages' model
page.deleteRecord();
pages.save();
}
Pages template:
{{#if isShowingDeleteConfirmation}}
<div class="large-7 large-offset-5">
<label class="inline left">Are you sure? (Can't be undone)</label>
<button {{action 'deletePage'}} class='button tiny alert'>Yes</button>
<button {{action 'cancelDeletePage'}} class='button tiny'>Cancel</button>
</div>
{{else}}
<div class="large-2 right">
<button {{action 'showDeleteConfirmation'}} class='button tiny alert'>Delete Page</button>
</div>
{{/if}}
I can show you the attributes and logic behind the if...else statement if you need me to.
Basically, the user clicks on the 'Delete' button, showing the other buttons 'yes' and 'cancel'. Once 'yes' is clicked, the deletePage action gets called.
I found very little documentation on the deleteRecord method and basically had to piece this all together myself. Perhaps there's a better way and this can be refactored. Just let me know in the comments.
I have the following template setup displaying a list of items, and wrapping each list in its own controller:
{{#each controller itemController="contact"}}
<div class="contact-entry">
<div class="title" {{action toggle on="click"}}>{{title}}</div>
{{#if showDetails}}
<div class="details">
<div>{{safe details}}</div>
</div>
{{/if}}
</div>
{{/each}}
The main controller is an ArrayController called ContentsController, and each item is wrapped in an ObjectController called ContentController. The content of the ContentsController is set explicitly in the route (I've simplified the data):
App.ContactsRoute = Em.Route.extend({
model: function() {
return [{'title': 'first'}, {'title': 'second'}];
}
});
This works really well. However if I navigate to another path and then back again, the settings on the ContentController don't persist. What happens is that the model data gets reloaded, and I assume a ObjectController gets created for each of the list items. Incidentally this is not the case for the ContentsController, which keeps its settings.
What is the solution to preventing ember of creating new ContentController for every list item every time I access the page?
I'm assuming your reference to ContentController is really ContactsController since you are using itemController="contact" in your #each block.
What kind of data are you trying to persist? The showDetails flag? The ContactControllers are going be created and destroyed anytime you exit / enter the route and there isn't anyway I know of to keep those around.
The ContactsController keeps its properties because its a singleton controller generated because you have a ContactsRoute.
Ember has a Application which has ApplicationView, ApplicationController and 'application' named template and 'main' named outlet and all these connect automatically.
eg.
App.ApplicationView = Ember.View.extend();
and
App.ApplicationController = Ember.Controller.extend();
so whenever my application template renders its default controller is an automatic instance of
App.ApplicationController
whose properties i can access in the template. But why does it not works with other views and controllers, i.e. if I have
App.SongView = Em.View.extend()
and
App.SongController = Em.Controller.extend()
these two do not connect. I can use any property of
App.SongController
in my song template.
I can use like :
{{view App.SongView}}
and in the template:
<script type='text/x-handlebars' data-template-name='song'>
{{name}}
</script>
and if i have a name property in App.SongController then it wont get picked up because its not connected to the View.
although i can do it like this
{{view App.SongView controllerBinding='App.songController'}}
but this requires the instance of App.songController in my js file and also using this approach we are hard-coding the controller to the template.
What is the best way for this?
Update
I am also attaching a js fiddle for my problem here:
http://jsfiddle.net/anshulguleria/K6KPJ/
If you want to render a template and its associated controller and view, you can use the {{render}} template. It works similarly to this.render in the router.
{{render "song" song}}
This example will render the song template with an instance of App.SongController and App.SongView. It will set the song controller's model to the value of song in the current context.
Here is a working JSBin that illustrates how this works.
I'm new at using ember, but already familiar with it, basically following some tutorials here and there and reading the api docs. But tutorials don't go too deep into more complex topics.
These are the details: I already implemented a web page that shows a list of items. The following are the relevant code excerpts from different parts of the app.
// the data model, the view and the controller
App.Item = DS.Model.extend({
name: DS.attr('string')
});
App.ItemsController = Ember.ArrayController.extend();
App.ItemsView = Ember.View.extend({ templateName: 'items' })
// in the router's corresponding route
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('items', App.Item.find())
}
// in the handlebars template
<ul class="items">
{{#each content}}
<li>{{name}}</li>
{{/each}}
</ul>
The data for this list is loaded remotely via ember-data (notice the App.Item.find() call in the route's connectOutlet method above) and a handlebar template displays it, and dynamically updates the list as the data changes. Up to here this is basic ember.
Now I want to have a text field at the top of the list, and when the user types in this text field, the list should be updated, by filtering and showing only the items with a name that matches what the user is typing. The actual definition of what a matching name is, is irrelevant to this question. It could be those names that contain the typed string, or that start with it.
I know my next step is to include a textfield view on top of the list in the handlebars template:
<div class="search-bar">
{{view Ember.TextField}}
</div>
<ul class="items">
{{#each content}}
<li>{{name}}</li>
{{/each}}
</ul>
So my questions at this point are the following:
How do I refer to this text field in javascript code so I can attach a listener to it to detect when it changes?
And more importantly, what do I need to do inside this event listener so the list gets filtered?
I would like to know how to achieve it filtering data loaded locally, but also how to do it by loading the filtering data remotely everytime the user types.
I actually need to implement something slightly more complex than this, but knowing how to do this will help.
You can have a computed property on your controller that filters the content based on a text field.
App.ItemsController = Ember.ArrayController.extend({
// ...
searchedContent: function() {
var regexp = new RegExp(this.get('search'));
return this.get('content').filter(function(item) {
return regexp.test(item.get('name'));
});
}.property('search', 'content.#each.name')
});
Then you just bind your text field to controller.search. Example: http://www.emberplay.com#/workspace/2356272909
To filter data remotely you should have ember data load more items every time search changes. This can be done by sending an event to the router every time there is a change.