Global Notifications View using Ember - ember.js

I have a notification view responsible for displaying global messages at the top of the page (info, warning, confirmation messages ...)
I created a NotificationView for the purpose, defined its content property and provided two handlers to show and hide the view.
APP.NotificationView = Ember.View.extend({
templateName: 'notification',
classNames:['nNote'],
content:null,
didInsertElement : function(){
},
click: function() {
var _self = this;
_self.$().fadeTo(200, 0.00, function(){ //fade
_self.$().slideUp(200, function() { //slide up
_self.$().remove(); //then remove from the DOM
});
});
_self.destroy();
},
show: function() {
var _self = this;
_self.$().css('display','block').css('opacity', 0).slideDown('slow').animate(
{ opacity: 1 },
{ queue: false, duration: 'slow' }
);
}
});
Ideally, i should be able to send an event from any controller or route to show the view with the proper content and styling. What would be the best way to architect this
I thought of using a named outlet in my application's template, however outlets are not quite suited for dynamic views.
<div id="content">
{{outlet notification}}
{{outlet}}
</div>
I was also thinking of architecting the notification view to be a response to "The application" or "A Module" state.

Because you have animations you want to run when the notifications change, you will want to create a subclass of Ember.View (a "widget"):
App.NotificationView = Ember.View.extend({
notificationDidChange: function() {
if (this.get('notification') !== null) {
this.$().slideDown();
}
}.observes('notification'),
close: function() {
this.$().slideUp().then(function() {
self.set('notification', null);
});
},
template: Ember.Handlebars.compile(
"<button {{action 'close' target='view'}}>Close</button>" +
"{{view.notification}}"
)
});
This widget will expect to have a notification property. You can set it from your application template:
{{view App.NotificationView id="notifications" notificationBinding="notification"}}
This will gets its notification property from the ApplicationController, so we will create a couple of methods on the controller that other controllers can use to send notifications:
App.ApplicationController = Ember.Controller.extend({
closeNotification: function() {
this.set('notification', null);
},
notify: function(notification) {
this.set('notification', notification);
}
});
Now, let's say we want to create a notification every time we enter the dashboard route:
App.DashboardRoute = Ember.Route.extend({
setupController: function() {
var notification = "You have entered the dashboard";
this.controllerFor('application').notify(notification);
}
});
The view itself manages the DOM, while the application controller manages the notification property. You can see it all working at this JSBin.
Note that if all you wanted to do was display a notification, and didn't care about animations, you could just have done:
{{#if notification}}
<div id="notification">
<button {{action "closeNotification"}}>Close</button>
<p id="notification">{{notification}}</p>
</div>
{{/if}}
in your application template, with the same ApplicationController, and everything would just work.

I don't agree that Notifications should be a View, I think they should be a Component. Then they are also more flexible to be used across your application.
You could a Notification Component instead as answered here: How can I make an Alert Notifications component using Ember.js?

Related

Access jquery event from ember component action

I'm trying to work with a simple overlay component, and close this overlay if someone clicks outside of the overlay content:
<div class="overlay" {{action 'close' on='click'}}>
<div class="item">
<form {{action 'submit' on='submit'}}>
{{yield}}
{{#link-to closeRoute class="close"}}Close{{/link-to}}
</form>
</div>
</div>
The component looks like this:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
submit: function() {
this.sendAction();
},
close: function(param) {
console.log(param); // -> undefined
console.log(this); // -> complete component object, no reference to the event?
// this.$("a.close").click();
}
}
});
This works like advertised, however, I need to determine the target of the click event, because also clicks on the item and form will trigger this click(close) action.
Question: How can I access the (jQuery) event object which has a target from within the close action inside the component?
I am using EmberCLI, and Ember 1.9
I have found this to provide the required result:
export default Ember.Component.extend({
classNames: ['overlay-block'],
didInsertElement: function() {
var self = this;
self.$().click(function(e) {
if (self.$(e.target).hasClass("overlay-block")) {
self.$("a.close").click();
}
});
}
});
This does not use an ember action like I expected. I'll leave the question open for a while to see if somebody comes up with an more 'Ember way' of doing this.
More Ember way
export default Ember.Component.extend({
classNames: ['overlay-block'],
click: function(e) {
if (this.$(e.target).hasClass("overlay-block")){
this.$("a.close").click();
}
}
});

How to prevent double clicks with ember.js?

I'm trying to figure out the idiomatic way to prevent a button from being clicked multiple times.
Imagine I have a simple controller action like so ...
var FooController = Ember.ObjectController.extend({
actions: {
go: function() {
console.log("done!");
}
}
});
and in my template I have a button defined like so ...
<button {{action go}}>Click Me Fast</button>
Does the action have an option to disable it immediately / making it so only once true event will be handled by the controller (until it's disabled for example)
Edit
I'm looking for a long term / multi use solution. One idea I'm thinking about is creating a special ember-component called "button-disable" that would allow me to create a custom button type that generally disables after a single click -but will still allow me to bubble up events to a parent controller. This feels a little heavier weight than I'd like so if another option exists, or if someone has created an addon for just this - let me know
As a one-off, if you bind the disabled attribute on your button like so
<button {{action go}} {{bind-attr disabled=actionPerformed}}>
and then set up your controller like
var FooController = Ember.ObjectController.extend({
actionPerformed: false,
actions: {
go: function() {
this.set("actionPerformed", true);
console.log("done!");
}
}
});
then the button will become disabled after you click it once
If you want a reusable component I'd borrow the spinner button from http://emberjs.com/guides/cookbook/helpers_and_components/spin_button_for_asynchronous_actions/ and tweak it as you need.
So your JS would be along the lines of
window.SpinEg = Ember.Application.create({});
SpinEg.ApplicationController = Ember.Controller.extend({
isLoading:false,
buttonText:"Submit",
actions:{
saveData:function(){
var self = this;
var saveTime = Ember.run.later(function(){
self.set('isLoading', false);
}, 1000);
}
}
});
SpinEg.SpinButtonComponent = Ember.Component.extend({
classNames: ['button'],
buttonText:"Save",
isLoading:false,
actions:{
showLoading:function(){
if(!this.get('isLoading')){
this.set('isLoading', true);
this.sendAction('action');
}
}
}
});
The template for your component would be
<script type='text/x-handlebars' id='components/spin-button'>
<button {{bind-attr id=id}} {{action 'showLoading'}}>
{{#if isLoading}}
<img src="http://i639.photobucket.com/albums/uu116/pksjce/spiffygif_18x18.gif"></img>
{{else}}
{{buttonText}}
{{/if}}
</button>
</script>
and you would then just include the following where you need the button to appear
<script type='text/x-handlebars' id='application'>
{{spin-button id="forapplication" isLoading = isLoading buttonText=buttonText action='saveData'}}
</script>

Ember.js: when using {{action}}, how to target a specific controller?

I put my question in a code example here:
http://jsbin.com/urukil/12/edit
See, I can use a {{action}} (which is placed in a child view) with target option to trigger an event in ApplicationView or ApplicationController or ChildView, only except the ChildController which is the one I truly wanted.
According the document, if no target specified, the event itself should handled in corresponding controller, in my case, which is should be ChildController. But why this action always lookup in ApplicationController? Did I miss something obviously important?
You can use needs to call a action on different controller...
App.ApplicationController = Ember.Controller.extend({
needs: ['child'],
doSomething: function() {
alert("From ApplicationController");
}
});
And the target can be specified as "controllers.child" from the template
<p {{action doSomething target="controllers.child"}}>Blah blah</p>
Here is your working fiddle...
http://jsbin.com/agusen/1/edit
Use this.controllerFor('') to call different controller event. A working example is given below.
JS:
/// <reference path="Lib/ember.js" />
var app = Ember.Application.create()
app.Router.map(function () {
this.resource('post')
});
app.ApplicationRoute = Ember.Route.extend({
model: function () {
return { "firstName": "amit", "lastName": "pandey" }
}
});
app.ApplicationController = Ember.ObjectController.extend({
Address: "House no 93-B",
fullName: function () {
return this.get("model.firstName") + " " + this.get("model.lastName")
}.property("model.firstName", "model.lastName"),
actions: {
submit: function (name) {
this.controllerFor('post').send('handleclick')
},
makeMeUpper:function()
{
alert('calling application controller Event');
this.set("model.firstName",this.get("model.firstName").toUpperCase())
}
}
});
app.PostRoute = Ember.Route.extend({
model:function()
{
return user;
}
});
app.PostController = Ember.ObjectController.extend({
Hello: "afa",
handleclick: function ()
{
alert('calling post controller Event');
this.controllerFor('application').send('makeMeUpper');
}
});
var user = [
{
id: "1",
Name: "sushil "
},
{
id: "2",
Name: "amit"
}
];
//hbs
<script type="text/x-handlebars">
<button {{action submit firstName}}>CLICK HERE TO CALL Post controller event</button>
{{input type="text" action= "makeMeUpper" value=firstName }}
{{#if check}}
No Record Exist
{{else}}
{{firstName}}{{lastName}}
{{/if}}
{{#linkTo 'post'}}click {{/linkTo}}
{{outlet}}
</script>
<script type="text/x-handlebars" id="post">
<button {{action hanleclick}}>Click here to call application controller event</button>
</script>
As far as I know the view class does not change the current controller. Since you are calling the view from the Application template, it remains in the ApplicationController.
Emberjs.com guides on render:
{{render}} does several things:
When no model is provided it gets the singleton instance of the corresponding controller
Simply changing your code from a view to a render call seems to do the trick:
Trigger ApplicationController
</p>
{{render 'child'}}
Since controllerFor is getting deprecated, the correct way to do this now is to specify needs in the controller, retrieve it from the controllers list, and then send it there. Example:
App.SomeController = Em.Controller.extend({
needs: ['other'],
actions: {
sayHello: function () {
console.log("Hello from inside SomeController.");
this.get('controllers.other').send('helloAgain');
}
}
});
App.OtherController = Em.Controller.extend({
actions: {
helloAgain: function () {
console.log("Hello again from inside OtherController!");
}
}
});
EDIT: oops... Looks like someone already posted this answer in essence. Will revise if needed.

Calling controller action from view in Ember

I have a submit button with a onClick view event. This event checks a flag and depending upon the condition it will allow form submission. I'd like the submit action on the controller to be called. What is the best way to do this?
Here another solution based on the example by albertjan for the case you have to perform some logic in your View and afterwards delegate to your controller. This is the way i understood your question:
HBS:
<script type="text/x-handlebars" data-template-name="index">
<button {{action submit target="view"}} >Sumbit</button>
</script>
View:
App.ThingView = Ember.View.extend({
submit : function(){
//do the view part of your logic
var object = //do whatever you may need
this.get("controller").send("submitInController", object); //you do not have to send object, if you do not need to
}
});
Controller:
App.ThingController = Em.ObjectController.extend({
submitInController: function(model) {
// do the controller part of your logic
}
});
Note: The call from your view will also bubble up to your current route. So this is basically the same code, that ember is executing when using the action helper.
I would handle the whole event on the controller:
HBS:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "submit"}}>Sumbit</button>
</script>
Controller:
App.ThingController = Em.ObjectController.extend({
submit: function() {
//handle things here!
//change the state of your object here to reflect the changes that
//the submit made so that the view shows these.
}
});
In Ember version 1.0.0, I've been having success adding actions to their own object literal in the controller.
IndexTemplate.html
<script type="text/x-handlebars" data-template-name="index">
<button {{action "submit"}}>Submit</button>
</script>
ThingController.js
App.ThingController = Ember.ObjectController.extend({
actions: {
submit: function() {
//handle things here!
//change the state of your object here to reflect the changes that
//the submit made so that the view shows these.
}
}
});
For more information, check out the {{action}} helper documentation from Ember Guides.
You can trigger an action from a view if the view uses the ViewTargetActionSupport mixin. The following example demonstrates its usage:
App.SomeController = Ember.Controller.extend({
actions: {
doSomething: function() {
alert('Doing something!');
}
}
});
App.SomeView = Ember.View.extend(Ember.ViewTargetActionSupport, {
someMethod: function() {
this.triggerAction({action: 'doSomething'});
}
});

{{action}} with a click event doesn't trigger the function in the v2 Ember router

I just updated Ember and I am trying to convert an old app to the new router API.
In my template I have this:
<button {{ action "createNewApp" }} class="btn btn-primary">Create application</button>
And I put a createNewApp in my route:
App.CreateAppRoute = Ember.Route.extend({
renderTemplates: function() {
this.render({ outlet: 'content'});
},
createNewApp: function(){
console.log("It's clicked");
}
});
However, when I click on the button the function is never called. I have tried to change the target of the event to the controller but it's still not working. Is this a bug or am I doing something wrong?
I was able to handle the click using a function in the events property of the router (thanks sly7_7) or using a function in the controller:
Template:
<script type="text/x-handlebars" data-template-name="application">
<h1>Content Here:</h1>
{{outlet content}}
</script>
<script type="text/x-handlebars" data-template-name="content-view">
<h2>content</h2>
<button {{ action "createNewApp" }} class="btn btn-primary">Create application</button>
</script>
JS:
var App = Ember.Application.create();
App.CreateAppView = Ember.View.extend({
templateName: "content-view"
});
App.CreateAppController = Ember.Controller.extend({
createNewApp: function(){
console.log("It's clicked, controller");
}
});
App.Router.map(function(match) {
match('/').to('createApp');
});
App.CreateAppRoute = Ember.Route.extend({
renderTemplates: function() {
this.render({ outlet: 'content'});
},
events: {
createNewApp: function(){
console.log("It's clicked, router");
}
}
});
When the button is clicked, the console log message is "It's clicked, controller" and when the function in the controller is removed the action is handled by the router, and the console log message is "It's clicked, router". If no target is specified in the {{action}} Ember tries to find the event in the view, then the controller, then the route.
This is using a build of emberjs built today from source.
Original answer below:
The {{action}} in the new router is replaced with {{linkTo}}.
The emberjs.com guides are constantly being updated with new info about the new router. The links guide covers the {{linkTo}} helper, and the actions guide discusses using the {{action}} helper to handle events in templates.