I have the following view to display a checked mark whenever the active property is set:
App.ActiveEntryView = Ember.View.extend({
render: function(buffer) {
var active = this.get('active'), icon;
if (active) {
icon = 'fa fa-check';
} else {
icon = '';
}
return buffer.push(icon);
}
});
But this is not re-rendering if I the active property changes. This is bound in my template like this:
{{#each model}}
<tr class="odd-even">
....
<td>{{view App.ActiveEntryView activeBinding="parked"}}</td>
....
</tr>
{{/each}}
Where parked is the property of the model that I want to evaluate. But whenever that property changes in the model, the view is not re-rendered. I have tried adding an observer:
App.ActiveEntryView = Ember.View.extend({
render: function(buffer) {
var active = this.get('active'), icon;
if (active) {
icon = 'fa fa-check';
} else {
icon = '';
}
return buffer.push(icon);
}.observes('active')
});
But then I get an error:
Uncaught TypeError: undefined is not a function
Referring to the line:
return buffer.push(icon);
I guess that the observer triggers the render function passing a different parameter than buffer.
How can I re-render the view then?
I don't know a ton about views, but from what I understand, this isn't the way to go about re-rendering. Specifically, render is a hook called by the system when it needs to render the template. Calling the hook yourself won't force the system to re-render the template, that has to be done at a higher level.
That being said, there's probably a way to force a re-render of the whole view, but that doesn't seem like a good solution for most issues. I see that you're doing something with icons, so I'm going to take a wild stab at a solution without knowing your entire problem. If you post more details, I can probably help you more.
From what I can tell, it seems like you're trying to change the icon of an element in the view when the active property changes. Here's how you might do that:
In your view template:
<i {{bind-attr class='view.active:fa view.active:fa-check'}}></i>
You can see more details about binding class names here.
Related
I have some views which will expand and show details when clicked.
For now, all the views can be clicked and expand, but the question is
How to expand only the latest clicked view?
For example, when I clicked view #1, it expand. So when I clicked view #2, the view #1 will collapse and view #2 expand etc.
I know we can bind a isSelected classname to the clicked view, but how do we tell the view to check "If any other view is selected" ?
Do we use CollectionView ? But how?
FYI this is the working JSBin.
First of all, I would change view to component. Although views have their valid use-cases, you are usually better off with a component.
Also, if you think about it, it makes sense that someone outside of the component would need to know which component was clicked last. That outside actor could be the controller, which could have a property called lastComponentClicked (which initially starts out as null)
App.IndexController = Ember.ArrayController.extend({
lastClickedComponent: null
});
Then, you can pass that property into each component and the property becomes bound between the controller and all the components as in:
<script type="text/x-handlebars" data-template-name="index">
{{#each content in model}}
{{ x-box content=content lastClickedComponent=lastClickedComponent}}
{{/each}}
</script>
So far, so good. Now, for the component itself:
App.XBoxComponent = Em.Component.extend({
classNames: ['box'],
isSelected: function(){
return this.get('lastClickedComponent') === this._uuid;
}.property('lastClickedComponent'),
click: function(){
this.set('lastClickedComponent', this._uuid);
}
});
Every time it is clicked, you can set a lastClickedComponent property, which is bound between ALL the components and the controller and thus will get reset every time. You can just set it to a value unique to the component, for example this._uuid.
isSelected computed property can then just check if lastClickedComponent property is that of THIS component, in which case the content you need to show will be expanded.
<script type="text/x-handlebars" id="components/x-box">
{{content}}
{{# if isSelected }}
<div>LAST SELECTED</div>
{{/if}}
</script>
Working solution here
I am using an {{if}} statement in a handlebars template (in Ember.js) and currently have it setup so if foo is true, a form is displayed.
I have a button which toggles the value of foo and hides/shows the form. This is done with an {{action}} attribute.
What I would like to do is animate this change. If possible, how can I do this using the current setup?
Note: I would be fine with fadeIn/fadeOut (jQuery).
Take a look at Liquid Fire (see: https://github.com/ef4/liquid-fire). I personally didn't work with it yet, but the examples look promising. Especially this form example looks similar to what you want: http://ef4.github.io/ember-animation-demo/#/animated-if-demo
Source is on the next slide: http://ef4.github.io/ember-animation-demo/#/animated-if-source
You can do this way:
in the handlebars file:
<script type="text/x-handlebars" data-template-name="newPost">
<p> Template for New Post </p>
<div class="errorMsg">
<p>Error Message</p>
</div>
</script>
in the view you can set a trigger with an action, and hide the div with the message error
App.NewPostView = Ember.View.extend({
showError: function() {
$('.errorMsg').fadeToggle("slow");
},
didInsertElement : function(){
$('.errorMsg').hide();
this.get('controller').on('showError', this, this.showError);
},
willClearRender: function()
{
this.get('controller').off('showError', this, this.showError);
}
});
And finally in the controller, this must extend to Ember.Evented and when you want to show the message error, you must call a this.trigger error.
App.NewPostController = Ember.ObjectController.extend(Ember.Evented,{
actions:{
// This an example the method that verify and show error after validating
addNewPost: function(post){
if(post.get('name') === 'valid'){
//do your business
}else{
//this will show the message error
this.trigger('showError');
}
}
}
})
when you want to hide the message error, you must call this.trigger('showError') again and this hide the message, you can use this with other effects.
I am using ember version 1.6.1. I would like to show an error message if user doest not enter username and password correctly. I think i have to use bind-style. now I have code like this:
<fieldset class="error-message" {{bind-style visibility="isVisible:visible:hidden"}}>
<span>invalid username/password</span>
</fieldset>
what is the best way to do it ?
Ember Handlebars supports dynamic class binding exceptionally better than it does style binding. To do that you'd bind-attr to the class. http://emberjs.com/guides/templates/binding-element-class-names/
Css
.visible{
visibility:visible;
}
.hidden{
visibility:hidden;
}
Handlebars
<fieldset {{bind-attr class=":error-message isVisible:visible:hidden"}}>
<span>invalid username/password</span>
</fieldset>
Example: http://emberjs.jsbin.com/didax/1/edit
You can bind-attr the style property and create a computed property that returns the raw style text visibility:visible, but that's ugly and not necessary in this situation.
Although class is generally the best way to set these visual changes, consider using classNameBindings instead of bind-attr. That would require you to create a View class.
However, the best way to bind element attributes that don't have a specific binding mechanism, would be via attributeBindings:
(this approach also needs a View class)
App.IndexView = Ember.View.extend({
attributeBindings: ['style'],
style: function() {
return 'color: #F00';
}.property()
});
This is way is a little better because you can watch the style property of your view class and it will automatically bind to your view markup. And since that is a computed property, you can create your own code to determine changes of other attributes in your view that could cause the style attribute to be reconstructed, and again, automatically bound to your view.
You could have a property that the style property watches with property('dependency'), so when it changes, style is once again computed and the view is updated. For example, let's say that you have a view which is a custom input box with built-in validation. You have a property valid which returns boolean, being true for valid and false for invalid values.
App.IndexView = Ember.View.extend({
attributeBindings: ['style'],
valid: function() {
return false;
}.property(),
style: function() {
// these variables and all should ideally be somewhere else,
// as color codes could potentially be global for the app
var _invalidColor = "#F00";
var _validColor= "#000";
if (this.get('valid')) {
return 'color: ' + _validColor + ';';
} else {
return 'color: ' + _invalidColor + ';';
}
}.property('valid')
});
(see jsbin)
Keep in mind this is a crude example to show the functionality/possibilities. Manually change the return value of valid property of the IndexView in JS Bin to see how it affects the view template.
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...
Trying to figure out the "ember best practices" for my app, regarding MVC. also for reference, I'm using ember-data, ember-layout, and ember-route-manager.
I'll use User as an example:
what I feel like I want to do is to get a User model from the database... then wrap it in a UserController, and set the model on a "content" property... then in a View, I want to bind to the controller for some functionality, and to the controller.content for model-level data. so a controller might look something like:
App.UserViewController = Em.Object.create({
content: userRecord,
isFollowingBinding : 'content.you_follow',
toggleFollow: function() {
make server call to change following flag
}
});
then the view could bind to the {{controller.content.name}}, or {{#if controller.isFollowing}}, or {{action "toggleFollowing" target="controller"}}
but say I get a list of User models back from the database... I feel like what should happen is that each of those models should be wrapped with a controller, and that should be returned as a list... so the view would have a list of UserControllers
Incidentally, I've done this... and it is working nicely.... except that everytime I reload the list, I wrap all of the new model objects with new controllers... and over time, the # of controllers in memory get larger and larger. on my base Controller class, I'm logging calls to "destroy", and I dont see it ever happening
when it comes to Em.View... I know that everytime it is removed from the screen, .destroy() gets calls (I am logging those as well). so if I were to move my code into a view, i know it will get destroyed and recreated everytime... but I dont feel like the functionality like toggleFollow() is supposed to be in view...
SO QUESTIONS:
is this how MVC is supposed to work? every instance of a model wrapped in a controller for that model? where there could be lots of controller instances created for one screen?
if I go down this approach, then I'm responsible for destroy()ing all of the controllers I create?
or is the functionality I've described above really meant for a View, and them Ember would create/destroy them as they are added/removed from the screen? also allowing template designers to decide what functionality they need (if they just need the {{user.name}}, theres no need to instantiate other controller/view classes... but if they need a "toggle" button, then they could wrap that part of the template in {{#view App.UserViewController contentBinding="this"}} )
I re-wrote this a few times... hopefully it makes sense....
I wouldn't wrap every user into an own controller.
Instead I would bind the user to a view, say App.UserView and handle the action toggleFollow on that view. This action will then delegate it's action to a controller which will handle the server call, see http://jsfiddle.net/pangratz666/hSwEZ/
Handlebars:
<script type="text/x-handlebars" >
{{#each App.usersController}}
{{#view App.UserView userBinding="this" controllerBinding="App.usersController"}}
{{user.name}}
{{#if isFollowing}}
<a {{action "toggleFollowing"}} class="clickable" >stop following</a>
{{else}}
<a {{action "toggleFollowing"}} class="clickable" >start following</a>
{{/if}}
{{#if user.isSaving}}saving ...{{/if}}
{{/view}}
{{/each}}
</script>
JavaScript:
App.usersController = Ember.ArrayProxy.create({
content: [],
toggleFollowing: function(user) {
user.set('isSaving', true);
Ember.run.later(function() {
user.toggleProperty('you_follow');
user.set('isSaving', false);
}, 1000);
}
});
App.UserView = Ember.View.extend({
isFollowingBinding: 'user.you_follow',
toggleFollowing: function() {
var user = this.get('user');
var controller = this.get('controller');
controller.toggleFollowing(user);
}
});