Specify action for view in template? - ember.js

I would like to know if it is possible to assign an action to a view like I could assign an action to a HTML tag:
This works:
<button {{action "show2" }}>Test 1</button>
This doesn't:
{{#view NewApp.MenuButton }}
{{action "show3" target="controller"}}
{{/view}}
I know that I could implement the click function in the view. But I would like to use the button as some sort of reusable component.

You typically want to use the Handlebars action helper on an HTML element, not on an Ember.View.
Since you want to attach an event to the NewApp.MenuButton View you, define the event in your view class definition. For example, here we handle the click event:
NewApp.MenuButton = Ember.View.extend({
click: function(event){
// When the user clicks this view,
// this function will be called.
// ... handle the click
App.myController.menuButtonWasClicked();
}
});
If the event you want to attach is not one of the built-in events, you can register your own events. Find the built-in supported events and how to register custom events here: Ember.js - Events
Edit: You say you want to be able to reuse it. You can define a mixin for attaching arbitrary events and targeting arbitrary objects:
Ember.MyEventAttacher = Ember.Mixin.create({
init: function() {
var action = this.get('action');
target = this.get('target'),
targetObj = Ember.getPath(target);
if (action && targetObj) {
var targetEventFnc = targetObj[action];
if (typeof targetEventFnc === 'function') {
var actionFnc = function(event) {
targetEventFnc(event);
}
this.set(action, actionFnc);
}
this._super();
}
});
Include the Mixin in your View:
NewApp.MenuButton = Ember.View.extend(Ember.MyEventAttacher);
And then re-use this view in your templates, making sure to define the action and target properties. Example:
{{#view NewApp.MenuButton action="show3" target="NewApp.myController"}}
<!-- ... -->
{{/view}}
Targeting:
NewApp.myController = Ember.Controller.create({
show3: function(event) {
// the event is sent here!
}
});

Related

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 - Triggering properties outside of controller

I am building a music site where I have multiple songs, each with it's own play/pause button, and then a global player which has a master play/pause button. I cannot figure out how to trigger the computed properties so that when I click the play button on the individual song, the master play/pause button also toggles from play to pause and vice versa.
I have the following code
Tracks.TrackController = Ember.ObjectController.extend({
currentTime: 0,
isLoaded: false,
isPlaying:false,
songStarted:false,
actions: {
play: function(){
var track_id = this.id;
var mySound = soundManager.createSound({
id: track_id,
url: 'https://api.soundcloud.com/tracks/' + track_id + '/stream?client_id=d61f17a08f86bfb1dea28539908bc9bf',
autoplay: false,
whileplaying: function() {
$('#positionBar').css('width', ((this.position/this.duration) * 100) + '%');
},
});
songStarted:true;
this.set("isPlaying", true);
this.set('mySound', mySound);
soundManager.stopAll();
mySound.play();
},
pause: function(){
var mySound = this.get('mySound');
this.set("isPlaying", false);
this.set("isPaused", true);
if(mySound && mySound.pause){
mySound.pause();
}
},
resume: function(){
var mySound = this.get('mySound');
this.set("isPlaying", true);
this.set("isPaused", false);
mySound.resume();
}
}
});
and this as the markup:
{{#if isPlaying}}
<li class="playBtn pause"><button {{action 'pause' this}} class="play-btn sm2_button" id="masterPlayBtn"></button></li>
{{else}}
{{#if isPaused}}
<li class="playBtn"><button {{action 'resume' this}} class="play-btn sm2_button" id="masterPlayBtn"></button></li>
{{else}}
<li class="playBtn"><button {{action 'play' this}} class="play-btn sm2_button" id="masterPlayBtn"></button></li>
{{/if}}
{{/if}}
I assume I need to add actions to the
Tracks.TracksController = Ember.ArrayController.extend({
});
controller, but that is where I get lost. I can't seem to figure out how to set isPlaying to the master play/pause button and also call the actions so that when I click the master pause/play button, it will find a particular song and play that on click.
You need:
A template that wraps the master button and individual songs
A component that will handle individual songs events and state
from here you handle individual song events that propagate upwards
Event bubbling from the components to the template controller
the template controller is notified of component events
here you can update the master control
Handle play/stop messages through binds
Working example: http://emberjs.jsbin.com/dopoho/1/edit?html,js,console,output
I should have used an array controller for the list of songs and I'm sure there are use cases I didn't considered. The point is to show you how you can use components to reuse code and how to pass events from the template to the component and then to the controller.
You don't need to change the actions on the individual play buttons. Only on the global button.
Update TracksController thusly:
Tracks.TracksController = Ember.ArrayController.extend({
restartTrack: null, // Need to restart with the global button? Use my model!
currentTrack: function () {
var filtered = this.get('content').filter(function (track) {
return this.get('isPlaying');
});
if(Ember.isEmpty(filtered)){
return null;
}
var track = filtered.objectAt(0);
this.set('restartTrack', track); // Should be on the item action...
return track;
}.property('#each.isPlaying'),
isPlaying: function () {
return this.get('content').any(function (track) {
return this.get('isPlaying');
});
}.property('#each.isPlaying')
});
So now your TracksController always knows if a song has individually changed, and will change its own isPlaying. It also knows which track to use if you want to restart the song with the global button...
This is just the first step to this and I don't really want to code the whole thing for you. All you really need to do at this point is do a little bit of refactoring in your handlebars and update the action on the global button.
You can also use the currentTrack in some sort of template and it will always re-render whenever you change the track. Pretty cool Ember-tastic stuff.
Good luck!
You could create hierarchy of controller objects like below:
Tracks.BaseTrackController = Ember.ObjectController.extend({...});
Tracks.TrackController = Tracks.BaseTrackController.extend({...});
With that you could place all you play pause functionality in Base controller and toggle them through actions in base itself (or child controller depending on your need)
Hope this makes sense.

Setting model from action in Ember route

I am calling an event like so:
{{#each}}
<div class='row'>{{name}} <a {{action showModal this}} href="#">Open modal</a>
{{/each}}
and have this route but NEED to set the model for the modal view but nothing seems to work. How can I get this to work?
App.ApplicationRoute = Ember.Route.extend({
events: {
showModal: function(obj) {
var v = this.container.lookup('view:modal');
// this.get('controller').set('model',obj); nope
v.appendTo(Emberjs1.rootElement);
}
}
});
You can set properties directly on the view and access them in your view's template using the view.property
var v = this.container.lookup('view:foo');
v.set('bar', 'hello');
v.appendTo(App.rootElement);
Then in the template it'd be
{{view.bar}}
http://emberjs.jsbin.com/qofoxazu/1/edit
If you really felt like create a controller/model you could get a controller, set the model on the controller, then set that model on your view.
var v = this.container.lookup('view:foo'),
controller = this.container.lookup('controller:foo');
controller.set('model', 'hello');
v.set('controller', controller);
v.appendTo(App.rootElement);
Then your controller will be in scope and you wouldn't have to use view to access the properties.
http://emberjs.jsbin.com/qofoxazu/2/edit

Add and remove views

I would like to insert in the DOM a view that displays a form with 2 buttons: + and -;
when you click "+" another identical view is inserted, when you press "-" the current view is removed;
I've tried to create a container view and the function for adding a view is simple:
in the template:
{{view Ember.ContainerView elementId="containerView"}}
in the childView's template:
<button class="form-button" {{action "addProduct"}}>+</button>
in the route's controller:
addProduct: function() {
var container = Ember.View.views['containerView'];
var child = container.createChildView(Gmcontrolpanel.InserisciProdottoView);
container.pushObject(child);
}
But i'm not able to manage the "-" function; because for that i need to get the view that the button i'm clicking belongs to in order to remove it, and i don't know how to do this;
All the childviews can have a controller? Because from the childview's button i can only call actions from the route's controller;
Or there is a better way to get this work?
so in that case, have an action in the childview rather controller like this
<button class="form-button" {{action "deleteProduct" target="view"}}>-</button>
in the views actions handle the deleteProduct like this
deleteProduct: function() {
this.destroy();
}
If you want to handle any of the model part then send an event from above method to controller

Global Notifications View using Ember

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?