I'm working on an Ember.js application and have a table of devices, where I would like each listed device to open a modal dialog, when clicked, using that specific device as a model for the dialog.
The official documentation is slightly vague on how to do this, so I've been doing some guessing. I tried to pass the id of each device as a parameter inside the template:
{{#each}}
<button data-toggle="modal" data-target="#modal" {{action 'openViewModal' 'viewDeviceModal' id}} class="btn btn-default btn-xs">View details</button>
{{/each}}
{{id}} works as intended elsewhere in the template.
Now, inside the route, I put this:
actions: {
openModal: function(modalName, id) {
this.controllerFor(modalName).set("model", this.store.find('device', id));
return this.render(modalName, {
into: "application",
outlet: "deviceModal"
});
},
This gives me the following error:
"The value that #each loops over must be an Array. You passed (generated devices controller)"
Presumably, there is some confusion with the model of the list of devices (#each) and the model of the modal dialog. The modal dialog does not have #eachinside it.
I also lack a means to find out if my variables actually are what they are supposed to be. console.log does not work. I have not yet read all the documentation on debugging ember, so I'm hoping there will be some pointers there. Meanwhile, any help on how to get my modal dialog models to work would be greatly appreciated.
This is not a problem with your each loop, but with your model. You're setting the model for the controller as a single record when it looks like it expects an array of records. Change the first line of your action to this:
this.controllerFor(modalName).set("model", [this.store.find('device', id)]);
Also, as a tip, don't load the record from the store again when you already have it. Just pass the whole record in the action helper:
{{action 'openViewModal' 'viewDeviceModal' this}}
Then your action handler can look like this:
openModal: function(modalName, record) {
this.controllerFor(modalName).set("model", [record]);
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!
Is it possible to dynamically render content in ember directly from the template?
i.e., using 4 links that are bound to 4 different template names:
v1
v2
v3
v4
{{render view.view_to_render generic_controller}}
or are there more efficient ways to achieve this?
From the original post, I guess that you would like to render dynamically an actual view, rather than a partial template.
The snippet
{{render view.view_to_render generic_controller}}
does not work (in my experience), because ember tries to look for a view named 'view.view_to_render', rather than interpreting it as a variable to read the view from.
The solution I use is to have a custom helper:
Ember.Handlebars.registerBoundHelper( 'renderBoundView', function ( panel ) {
var args = Array.prototype.slice.call(arguments, 1)
Array.prototype.splice.call(args, 0,0,panel.view, 'panel.model')
// Call the render helper
return Ember.Handlebars.helpers.render.apply(this, args)
})
This helper extracts the view name from the variable 'view' in the passed object, and then passes that name to the standard render helper. Then using
{{renderBoundView panel}}
where panel has properties 'view' with the name of the view and 'model' containing the (resolved) model does the trick.
Of course you could also interpret the object passed as a variable name to get from the current context (which is also one of the arguments passed to the helper).
The entire purpose of Ember is to dynamically render content.
If you want to render particular "views" that are driven from data, that's pretty easy in Ember. Ember calls these partial templates "partials", appropriately-enough :)
Say you have an attribute called partialToRender set in your controller for the template that you're doing your "generic rendering" on. Say it's bound to a set of buttons which are bound to actions which each change the value of partialToRender. Something like this:
<button {{action changePartialToRender 'hello'}}>Change to Hello</button>
<button {{action changePartialToRender 'goodbye'}}>Change to Goodbye</button>
<button {{action changePartialToRender 'yes'}}>Change to Yes</button>
<button {{action changePartialToRender 'no'}}>Change to No</button>
and then in your controller you'd have an action something like this:
App.IndexController = Em.ObjectController.extend({
partialToRender: null,
actions: [
changePartialToRender: function(newValue) {
this.set('partialToRender', newValue);
}
]
});
That'd mean whenever the user clicked on one of your buttons, the value of partialToRender would be changing. Sweet, right? :)
So now all that we need to do is hook up our bit of template code that renders the partial. A partial is just another template, but it's part of a page rather than a full one... some bit of different content in each case, to render into our initial template...
So, we revisit the template like this:
<button {{action changePartialToRender 'hello'}}>Change to Hello</button>
<button {{action changePartialToRender 'goodbye'}}>Change to Goodbye</button>
<button {{action changePartialToRender 'yes'}}>Change to Yes</button>
<button {{action changePartialToRender 'no'}}>Change to No</button>
{{#if partialToRender}}
{{partial partialToRender}}
{{/if}}
Note I'm just wrapping the partial in an if statement to make sure it doesn't render if it's not set.
Also note that I haven't specified the partials here for you. I've just kind of whet your appetite. If you're really interested in this, I suggest watching the Ember video on the getting started guide in the ember site. It's a bit rambling, but it shows off some of Ember's powerful features, or possibly go through the guides / tutorial over at the Ember main site.
Hope that answers your question :)
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.
i try to create my first ember.js app. A calendar-
my day model
App.Day = Ember.Object.extend({
today : null,
dayNumber : null,
addEvent : function() {
console.log(this);
$("#myModal").modal('show');
}
});
the html view
<div class="cal">
{{#each App.DayList}}
{{#if this.today}}
<div class="day today" {{action "addEvent" target="model" }}>
{{#with this as model}}
<span class="text">{{this.dayNumber}}</span>
{{/with}}
</div>
{{else}}
<div class="day" {{action "addEvent" target="model" }}>
{{#with this as model}}
<span class="text">{{this.dayNumber}}</span>
{{/with}}
</div>
{{/if}}
{{/each}}
</div>
so on click on day i show the bootstrap dialog and I wont to load extern data, but I need a information about clicked day.
My understanding is I create a view
App.DayDetails = Ember.View.extend({
});
and inside this view I send an ajax request, but how to get information about clicked day inside this view?
You should almost never be doing any AJAX in a view.
Views do two things:
(1) draw themselves
(2) respond to UI events (clicks, typing, etc)
Your view should get its contents from a controller, in this case I suppose App.DayController or DayDetailsController. (that's another thing, it's best practice to end your subclasses with View or Controller, etc, so its obvious at a glance what they do).
Where the controller gets that data from is where things might get complicated. Ideally, in a mature app, you'd have a data store (a combination—in concept—of your server-side database and ActiveRecord, if you use rails) that would be queried. Simplistically, however, you could have the controller be responsible for using jQuery to manually handle an ajax request. So long as we're taking short-cuts, you could put such a call in a number of place, (a singleton controller, a day-specific item controller, the day model itself), just NOT the view. And it's important when taking these sorts of short-cuts to limit the contagion... all you should be doing with the manual ajax is fetching the JSON and then immediately and expeditiously getting it back into the ember ecosystem by setting it as the content of an array controller. I.e., no going one or two steps further by trying to insert the data into a view manually or whatnot. Don't fight Ember, if you can avoid it.
A few things:
(1) Your use of this is superfluous, as are the {{with}} statements. Inside an {{each}} block the context will be the current object (or its wrapping controller, if you're using itemController) in the iteration. (UNLESS you use "x in y" syntax, in which case the context remains the controller)
(2) The model should NOT be attempting to modify the DOM. Instead, rely on bindings and your controllers to coordinate UI changes. What you might want to do is have a App.DayController that you can put addEvent on, and then in your {{each}} use itemController="App.DayController".
App.DayController = Ember.ObjectController.extend({
addEvent: function () {
// ...
}
});
Then, the context for each loop in your {{each}} template will be each individual day controller. The controller will automatically be the target and context for the views so your template would look like this:
{{#each App.DayList itemController="App.DayController"}}
<div {{bindAttr class=":day today"}} {{action addEvent}}>{{dayNumber}}</div>
{{/each}}
(the : in :day means that day will always be a class, but today will only be a class if the today property on the context is truthy)
Because each day sends addEvent to its own controller, there's no need for figuring out what day to load.
I've got an ember application that needs to manage multiple chat windows. A window for each active chat is created within an {{#each}} loop. This is straightforward enough. The place that I'm having trouble is sending the chat message when the user presses enter.
The window looks like this
{{#each chats}}
... stuff to display already existing chats...
{{view Ember.TextField valueBinding="text" action="sendChat"}}
<button {{action sendChat this}}> Send </button>
{{/each}}
This works fine for the button, since I can pass this to it. By default the function defined in the textfield view action just gets the text within that textfield, which is not enough in this case. Since there can be multiple chat windows open, I need to know which window the message was typed into. Is it possible to pass this to the textfield action function? (or can you suggest a different way to solve this problem?)
Add contentBinding="this" to the definition of the view, like:
{{view Ember.TextField valueBinding="text" action=sendChat contentBinding="this"}}
EDIT
Ember master already has this change, but the official downloadable verstion still don't.. so you will need to subclass the Ember.TextField and change its insertNewline to achieve required functionality:
App.ActionTextField = Em.TextField.extend({
insertNewline: function(event) {
var controller = this.get('controller'),
action = this.get('action');
if (action) {
controller.send(action, this.get('value'), this);
if (!this.get('bubbles')) {
event.stopPropagation();
}
}
}
});
After that, the action handler will receive additional argument, the view:
{{view App.ActionTextField valueBinding="text" action=sendChat myfieldBinding="this"}}
and in controller:
sendChat: function (text, view) {
var myField = view.get('myfield');
//do stuff with my field
}
You may use ember master instead of subclassing Ember.TextField..
I hope the ember guys will release the next version soon..
I know this question has been answered but I said let me add some information that may help out someone in the situation of actions and TextField. One word "Component". TextField in Ember is a Component so if you think of TextField from that perspective it may help when it comes to sending actions and using TextField in an application.
So when you say App.SomeTextField = Ember.TexField.extend({...});App.SomeTextField is subclassing Ember.TextField (remember which is a component). You could add your logic inside and that works and you could access it from your template such as {{view App.SomeTextField}}
You may be thinking I see the word 'view' this guy sucks, TextField is a View. Well, it is sort of a View because Ember Components are a subclass of Ember.View so they have all that Views have. But there are some important things to keep in mind Components un-like Views do not absorb their surrounding context(information/data), they lock out everything and if you want to send something from the outside surrounding context you must explicitly do so.
So to pass things into App.SomeTextField in your template where you have it you would do something like {{view App.SomeTextField value=foo action="sendChat"}} where you are passing in two things value, and action in this case. You may be able to ride the fine line between View/Component for a bit but things come crashing why is your action not sending?
Now this is where things get a little trippy. Remember TextField is a Component which is subclassed from View but a View is not a Component. Since Components are their own encapsulated element when you are trying to do this.get('controller').send('someAction', someParam), "this" is referring to the Component its self, and the controller is once again the component its self in regards to this code. The action that you are hoping will go to the outside surrounding context and your application will not.
In order to fix this you have to follow the protocol for sending actions from a Component. It would be something like
App.SomeTextField = Ember.TextField.extend({
//this will fire when enter is pressed
insertNewline: function() {
//this is how you send actions from components
//we passed sendChat action in
//Your logic......then send...
this.sendAction('sendChat');
}
});
Now in the controller that is associated with where your SomeTextField component/view element is you would do
App.SomeController = Ember.Controller.extend({
//In actions hash capture action sent from SomeTextField component/view element
actions: {
sendChat: function() {
//Your logic well go here...
}
}
});
Now I said to think of TextField as a Component but I have been riding the tail of the view and declaring {{view AppSomeTextField...}}. Lets do it like a component.
So you would have in your template where you want to use it
//inside some template
`{{some-text-field}}`
Then you get a specfic template for the component with the name:
//template associated with component
<script type="text/x-handlebars" data-template-name="components/some-text-field">
Add what you want
</script>
In your JS declare your component:
//important word 'Component' must be at end
App.SomeTextFieldComponent = Ember.TextField.extend({
//same stuff as above example
});
Since we on a role you could probably get the same functionality using Ember input helpers. They are pretty powerful.
{{input action="sendChat" onEvent="enter"}}
Welp hopefully this information will help someone if they get stuck wondering why is my action not sending from this textField.
This jsBin is a sandBox for Components/Views sending actions etc....Nothing too fancy but it may help someone..
http://emberjs.jsbin.com/suwaqobo/3/
Peace, Im off this...