Emberjs + Handlebars + Object onchange and events - ember.js

I'm finding that jQuery observers aren't bound to elements that are not shown in handlebars logic.
Let's say I have the following;
{{#if person}}
Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
{{else}}
Please <a class="login">log in</a>.
{{/if}}
<script>
$('.login').click(function() {
alert("Hi there.");
});
</script>
If I run in console, person = null (or whatever's needed to convince that person is empty) - the login observer doesn't work. I'm already using embers didInsertElement() to load a few other things, but is there a "onChange" event I can hook into so I can rebind event observers?

The big question is why you want that? Ember has excellent built in support for click handlers without going via jQuery. The reason your <script> is not working is likely to be down to the deferred way ember inserts views into the DOM. When you do Ember.View.append() the element is inserted in the DOM later.
That said, here's a fiddle that does what I think you want attaching the jQuery click handler in didInsertElement().
http://jsfiddle.net/algesten/5LPPz/1/
didInsertElement: function () {
// appending click handler directly with jQuery
$('.login').click(function() {
alert("Hi there.");
});
}
However the ember way would be to just use the click implicit handler function:
http://jsfiddle.net/algesten/5LPPz/2/
click: function () {
alert("Hi there.");
}
N.B. the latter handler attaches to the surrounding handlebar div and not the a, but clicks bubble.

The problem your facing is that javascript can only bind to elements that exist in the dom. Once you add a new element it wants you to re-bind those events. Luckily, jQuery is your friend on this one.
<script>
$('body').on('click', '.login', function() {
alert("Hi there.");
});
</script>
Ideally, your selector is the closest parent to .login that doesn't get added by javascript. The above is safe bet if you're not sure

Related

Ember re render component

I have a component with a jquery menu that transforms the DOM. I need to re render the component for initiate the structure again. The jquery menu doesn't work dinamically, so I need to re render the component.
//Parent Component hbs
<div id="container">
{{menu-jquery model=model}}
</div>
//Parent Component js
export default Ember.Component.extend({
refreshMenuData(){
callToServer()// ajax call
updateModel()// generate model from ajax response
-> //how to delete and create menu component? or re render menu component?
}
}
Thanks
A component has a function called rerenderthat you can call to force a rerender of the component. Although, I might recommend a better approach. Upon resolution of the promise that fetches your server data, manually destroy the jquery plugin and then reinit said plugin.
I found a workaround putting the component inside a conditional block. I set loading in false before the server call and true after data parsing.
{{#if loading}}
// show loading message
{{else}}
{{menu-jquery model=model}}
{{/if}}
This force the menu component to re render.
Your question is not quite clear. I am assuming that, you will be using jquery ajax for server calls inside the component. In Ember.Component, you have to use .on("didInsertElement") which works similarly as that of jQuery(document).ready(). So your component will look something like this
export default Ember.Component.extend({
_onReady: function() {
refreshMenuData();
//whatever jquery code you want to execute here
}.on('didInsertElement')
})
If you can provide your example in Ember Twiddle or JSBin, I can help you better.

ember.js | How to bind an event of a sub-component to an action of an outer component

unfortunately i am not able to figure out, how to receive an event of a component i use from within a component.
What i mean actually sounds harder than it is, consider the following toy example, with a component my-outer and another component my-inner (a short explanation follows the code, at the end i link to jsbin).
The templates:
<script type='text/x-handlebars' id='components/my-outer'>
<div {{bind-attr class="isRed:red"}}>Buttons should toggle my background color</div>
<button {{action "toggleRed"}}>It works from my-outer</button>
{{my-inner action="toggleRed"}}
</script>
<script type='text/x-handlebars' id='components/my-inner'>
<button {{action "action"}}>It doesn't work from my-inner</button>
</script>
The javascript:
App.MyOuterComponent = Ember.Component.extend({
isRed: false,
actions: {
toggleRed: function() {
this.toggleProperty("isRed");
}
}
});
my-outer contains a short text, with a background-color, which can be toggled from and to red by invoking the toggleRed action. the first button demonstrates that this works in principle.
now i would like to bind the default action of the second component to this same toggleRed action, that's the point of the following line.
{{my-inner action="toggleRed"}}
But on clicking the second button (which is part of my-inner) an error is thrown and the action is not fired.
How do I fix this example?
http://emberjs.jsbin.com/cabasuru/2/edit?html,js,console,output
Thanks so much in advance
(and this is my first question on so, i am happy about any meta-critics)
Since Components work just like views, easiest way is to get the parentView and forward the action. You may have to handle the action in my-inner like following.
App.MyInnerComponent = Ember.Component.extend({
isRed: false,
actions: {
toggleRed: function() {
this.get('parentView').send('toggleRed');
}
}
});
You can see outer component can be accessed as parentView in inner component. Here is the working jsbin link
http://emberjs.jsbin.com/cabasuru/5/edit
My question actually missed the main point. What goes wrong in the example above, is that the action helper in the inner component
<button {{action "action"}}>It doesn't work from my-inner</button>
does not trigger the default action associated with the component. Instead it invokes a new event named action, which is not allowed to bubble (due to the component confinement).
It turns out, there are two ways to solve that:
Properly reroute the event in an actions block on the my-inner component
<button {{action "my-action"}}>...</button>
together with a definition of the my-action action for my-inner:
App.MyInnerComponent = Ember.Component.extend({
actions: {
myaction: function(){
this.sendAction();
}
}
});
This is basically, the idea #CodeJack proposes, with the difference,
that here we rely on the wiring, which is set-up in the template of my-outer.
http://emberjs.jsbin.com/cabasuru/3/edit
As #torazaburo hinted at, setting the target property on the my-inner component to the my-outer component allows the event triggered from the action helper to bypass the component isolation.
{{my-inner target=controller}} in the my-outer template and a <button {{action "toggleRed"}}>...</button> in the my-inner template.

How can we get the original event in ember's action

I'm updating a personal project where I used the ember.js version 0.9.x.
So a new version was released and I have a problem related with ember action.
I have the following html code:
<li><a href="#" id="startApp" {{action activateView target="view"}}> Home</a> <span class="divider">|</span></li>
where, when I click its call this function activateView:
activateView: function(event, context) {
console.log(event);
}
but the event and the context are undefined. I've already tried this.context and it returns undefined.
The main idea its obtain the id of the link when the user click.
I know about routes and the handlebar helper link to, but I really need that id for other things,
In Ember 2...
Inside your action you always have access to the Javascript event object which has the DOM element e.g.
actions: {
myAction() {
console.log(event.target) // prints the DOM node reference
}
}
The event is not passed using the action helper. If you really want the event object, you need to define a view and use the click event:
App.MyLink = Em.View.extend({
click: function(e) {
}
});
and then:
<li>{{view App.MyLink}}</li>
but requiring access to the dom event is a rare case, because you can pass arguments to {{action}}. In your case:
<li><a href="#" id="startApp" {{action activateView "startApp" target="view"}}> Home</a> <span class="divider">|</span></li>
and in the event:
activateView: function(id) {
console.log(id);
}
There are two ways you can receive event object in actions,
1.If you are using component, then you can define any of this list of event names in component and that is designed to receive native event object. eg., {{my-button model=model}}
export default Ember.Component.extend({
click(event){
//oncliking on this componen will trigger this function
return true; //to bubble this event up
}
})
2.If you are using html tag like button then you need to assign a (closure) action to an inline event handler.
{{#each item as |model|}}
<button onclick={{action 'toggle' model}}>{{model.title}}</button>
{{/each}}
In actions hash toggle function will always receive native browser event object as the last argument.
actions:{
toggle(model,event){
}
}
In the below format, action toggle will not receive event object,
<button {{action 'toggle'}}>{{model.title}}</button>
Input helpers such as {{input key-press="toggle" and {{text-area key-press="toggle"
Explained really well in ember guide https://guides.emberjs.com/v2.12.0/components/handling-events/#toc_sending-actions
you need to pass the id into your function like so to have it accessible in the view, you can pass along what ever you want, but in your example this should do it
html
<li><a href="#" id="startApp" {{action activateView "startApp" target="view"}}> Home</a> <span class="divider">|</span></li>
then you have access to the id or what ever you passed in, in the view
js
...
activateView: function(data){
console.log(data); // should be the ID "startApp"
}
...
Just use event handler directly.
Reference: https://github.com/emberjs/ember.js/issues/1684
I don't have enough reputation for a comment, but here is the relevant documentation using Ember Octane.
The callback function will receive the event as its first argument:
import Component from '#glimmer/component';
import { action } from '#ember/object';
export default class ExampleComponent extends Component {
#action
handleClick(event) {
event.preventDefault();
}
}

toastr and ember.js

Is the popup library toastr not going to work with Ember because of direct dom manipulation that ember doesn't like?
Are there any other libraries like this one that work nicely with ember?
Edit
Even through the working example posted below I could not get this to work locally. I finally used Pine Notify which worked straight away.
This works fine in Ember, you just have to handle the event in the right place. The "right place" depends on your implementation. If you want this to be fired from a button within your view, you'll need to use the {{action}} helper passing the action name. Example:
<script type="text/x-handlebars" >
<button class="btn btn-info" {{action showInfo}}>Info</button>
</script>
In the template above, I'm saying that the button should fire the showInfo event, so the Controller responsible for this view should have a function with the same name:
App.ApplicationController = Em.ArrayController.extend({
showInfo: function() {
toastr.info('This is some sample information');
}
});
You can also have the view handle the event; the code below defines a click event, so if you click anywhere in the view, it would run your function:
App.OtherView = Em.View.extend({
click: function(e) {
toastr.error('This is some sample error');
}
});
and in your Handlebars template, you don't have do tell the action since you are already saying in the view class that you want to handle the click event for that view, so you can simple render the view and style it:
{{#view App.OtherView class="btn btn-danger"}}
Error
{{/view}}
Here's a sample in JSFiddle: http://jsfiddle.net/schawaska/YZwDh/
I recommend that you read the Ember Guide about the {{action}} helper

binding context to action in ember textfield

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...