Ember.beforeOberver callback is asynchronous to property change - ember.js

Context: http://emberjs.jsbin.com/diyirecu/23/edit
Is there anyway to have the Ember.beforeOberver callback run first and then the property changes? Or should I be using a different strategy all together?

Not sure if you can turn that event synchronous... However, I refactored your code to use some simple jQuery patterns (callbacks... I know, very non-Ember of me) that achieve the same thing. The code is clunky compared to what you were looking to achieve, but it works.
Example JSBin: http://emberjs.jsbin.com/diyirecu/26/edit
I also changed popObject to removeObject so that it actually removed the item you clicked on.
App.IndexController = Ember.ObjectController.extend({
content: {},
colors: ['red','blue','green'],
color: 'orange',
actions: {
addColor: function () {
var color = this.get('color'),
$ul = Ember.$('ul');
if (color) {
$ul.fadeOut(500, function () {
this.get('colors').pushObject(color);
this.set('color', '');
$ul.fadeIn();
}.bind(this));
}
},
removeColor: function (color) {
var $ul = Ember.$('ul');
$ul.fadeOut(500, function () {
this.get('colors').removeObject(color); //Changed from popObject
$ul.fadeIn();
}.bind(this));
}
},
});

Related

Ember setupController fires observes, how can i prevent that

My code looks something like this
App.ItemRoute = Em.Route.extend({
setupController: function(controller) {
var model = this.modelFor('item');
controller.setProperties({
name : model.get('name'),
title: model.get('title')
});
}
});
App.ItemController = Em.ObjectController.extend({
saveOnChange: function() {
console.log('saveOnChange');
}.observes('name', 'title'),
});
From my understanding because i am using setProperties the observe should only fire once , but it fire two times
also wrapping the setProperties with beginPropertyChanges & endPropertyChanges still fires twice
what i ultimately is for it to not fire at all, so what i ended up doing was changing the controller code to be like this
App.ItemController = Em.ObjectController.extend({
load: false,
saveOnChange: function() {
if(!this.get('load')) {
this.set('load', true);
return;
}
console.log('saveOnChange');
}.observes('name', 'title'),
});
this code would work if the change is only fired once, but it won't work if its fired multiple times (that's my case)
The setProperties function doesn't coalesce your observers (unfortunately there's no way to do that), it just groups them into one operation. The source might help you to better see what it does:
Ember.setProperties = function(self, hash) {
changeProperties(function() {
for(var prop in hash) {
if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
}
});
return self;
};
So, back to your problem. The best way that I can think of is to debounce your function.
App.ItemController = Em.ObjecController.extend({
load: false,
saveOnChange: function() {
Em.run(this, 'debouncedSave', 150);
}.observes('name', 'title'),
debouncedSave: function() {
if(!this.get('load')) {
this.set('load', true);
}
}
});
If you're not familiar with debouncing, you can read about it here. There are probably some other solutions involving direct manipulation of the properties, but I'm not sure if that's a road you want to go down.

How to order addEventlistener in Ember. Want to execute my eventListener before 3rd party on same element

Hi I am trying to do things with emberjs and google maps.
The issue with google autocomplete api is if user enters text in location search but does not select any value from dropdown suggestion in textbox the first suggestion is not selected. I found a good solution for it on stack overflow solutiion in jquery only
I am trying to do it in ember.
App.AutocompleteAddressView = Ember.TextField.extend({
tagName: 'input',
autocomplete: null,
didInsertElement: function () {
this._super();
var options = {
componentRestrictions: {
country: "xx"
}
};
this.set('autocomplete', new google.maps.places.Autocomplete(this.$()[0], options));
//var autocomplete = new google.maps.places.Autocomplete(this.$()[0], options);
},
keyDown: function (e) {
var that = this;
var suggestion_selected = $(".pac-item.pac-selected").length > 0;
if (e.keyCode === 13 && !suggestion_selected) {
//key down arrow simulations
var event = Ember.$.Event('keypress', {
keyCode: 40
});
Ember.run(this.$("#searchTextField"), 'trigger', event);
console.log('loggin autocomplete');
console.log(this.get('autocomplete'));
//adding google map event and then triggering it
google.maps.event.addListener(this.get('autocomplete'), 'place_changed', function () {
var place = that.get('autocomplete').getPlace();
that.set('autocomplete_result', place)
console.log('google event was triggered');
console.log(that.get('autocomplete_result'));
});
google.maps.event.trigger(this.get('autocomplete'), 'place_changed');
}
}
});
Now I need to do simulation part. I guess ember testing has somthing that simulates keypress but I cannot used it.
If I use the solution from the link I mentioned things work fine first time but on clicking browser back navigation it does not work.
The solution I tried in code not working

Using tryInvoke when controller actions are wrapped

Newest guidelines state to wrap controller actions inside a
actions: {
loadMore: function() {}
}
When I do so the code I previously had to fire an action on a controller no longer works:
Ember.tryInvoke(view.get('controller'), 'loadMore');
What would be the proper way to get this going again?
Edit
The complete code that uses the tryInvoke:
didInsertElement: function() {
'use strict';
var view = this;
this.$().bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
if (isInView) {
Ember.tryInvoke(view.get('controller'), 'loadMore');
}
});
},
You can use the Ember.ViewTargetActionSupport, and set the action property to the action name that you want, in that case loadMore. Using triggerAction will send the action.
Because the jquery event is detached from ember run loop I wrapped the triggerAction in Ember.run.
App.YourView = Ember.View.extend(Ember.ViewTargetActionSupport, {
action: 'loadMore',
didInsertElement: function() {
'use strict';
var view = this;
this.$().bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
if (isInView) {
Ember.run(function() {
view.triggerAction();
});
}
});
},
});
Are you sure you're getting the controller right? According to a commit a couple months ago in Ember 1.2, the controller is now accessible from a view as "targetObject" rather than "controller":
https://github.com/emberjs/ember.js/commit/326af5a9c88df76f5effe11156a07b64c8b178a3#packages/ember-handlebars/lib/controls/text_support.js

How do I call an action method on Controller from the outside, with the same behavior by clicking {{action}}

Please look at this code...
```
App.BooksRoute = Ember.Route.extend({
model: return function () {
return this.store.find('books');
}
});
App.BooksController = Ember.ArrayController.extend({
actions: {
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomething…
}
}
}
});
```
I want to call the updateData action on BooksController from the outside.
I tried this code.
App.__container__.lookup("controller:books").send('updateData');
It works actually. But, in the updateData action, the this is different from the one in which updateData was called by clicking {{action 'updateData'}} on books template.
In the case of clicking {{action 'updateData'}}, the this.filter() method in updateData action will return books models.
But, In the case of calling App.__container__.lookup("controller:books").send('updateData');, the this.filter() method in updateData action will return nothing.
How do I call the updateData action on BooksController from the outside, with the same behavior by clicking {{action 'updateData'}}.
I would appreciate knowing about it.
(I'm using Ember.js 1.0.0)
You can use either bind or jQuery.proxy. bind is provided in JS since version 1.8.5, so it's pretty safe to use unless you need to support very old browsers. http://kangax.github.io/es5-compat-table/
Either way, you're basically manually scoping the this object.
So, if you have this IndexController, and you wanted to trigger raiseAlert from outside the app.
App.IndexController = Ember.ArrayController.extend({
testValue : "fooBar!",
actions : {
raiseAlert : function(source){
alert( source + " " + this.get('testValue') );
}
}
});
With bind :
function externalAlertBind(){
var controller = App.__container__.lookup("controller:index");
var boundSend = controller.send.bind(controller);
boundSend('raiseAlert','External Bind');
}
With jQuery.proxy
function externalAlertProxy(){
var controller = App.__container__.lookup("controller:index");
var proxySend = jQuery.proxy(controller.send,controller);
proxySend('raiseAlert','External Proxy');
}
Interestingly this seems to be OK without using either bind or proxy in this JSBin.
function externalAlert(){
var controller = App.__container__.lookup("controller:index");
controller.send('raiseAlert','External');
}
Here's a JSBin showing all of these: http://jsbin.com/ucanam/1080/edit
[UPDATE] : Another JSBin that calls filter in the action : http://jsbin.com/ucanam/1082/edit
[UPDATE 2] : I got things to work by looking up "controller:booksIndex" instead of "controller:books-index".
Here's a JSBin : http://jsbin.com/ICaMimo/1/edit
And the way to see it work (since the routes are weird) : http://jsbin.com/ICaMimo/1#/index
This solved my similar issue
Read more about action boubling here: http://emberjs.com/guides/templates/actions/#toc_action-bubbling
SpeedMind.ApplicationRoute = Ember.Route.extend({
actions: {
// This makes sure that all calls to the {{action 'goBack'}}
// in the end is run by the application-controllers implementation
// using the boubling action system. (controller->route->parentroutes)
goBack: function() {
this.controllerFor('application').send('goBack');
}
},
};
SpeedMind.ApplicationController = Ember.Controller.extend({
actions: {
goBack: function(){
console.log("This is the real goBack method definition!");
}
},
});
You could just have the ember action call your method rather than handling it inside of the action itself.
App.BooksController = Ember.ArrayController.extend({
actions: {
fireUpdateData: function(){
App.BooksController.updateData();
}
},
// This is outside of the action
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomething…
}
}
});
Now whenever you want to call updateData(), just use
App.BooksController.updateData();
Or in the case of a handlebars file
{{action "fireUpdateData"}}

EmberJS: How to create modal forms

Whats a good way or a pattern to creating modal forms in EmberJS. Something like this.
I will describe the way i manage modal views with a CSS approach:
Add CSS class names:
.modal {
-webkit-transition: -webkit-transform #modal-duration #ease-animation-style;
-webkit-transform: translate3d(0,0,0);
-webkit-backface-visibility: hidden;
}
.modal.from-left.is-hidden {
-webkit-transform: translate3d(-320px,0,0);
}
.modal.from-right.is-hidden {
-webkit-transform: translate3d(320px,0,0);
}
.modal.from-up.is-hidden {
-webkit-transform: translate3d(0,-1*#app-height,0);
}
.modal.from-down.is-hidden {
-webkit-transform: translate3d(0,#app-height,0);
}
Add custom events to your Application Namespace to receive the transitionEnd event in your view:
Yn.Application = Em.Application.extend( Em.Evented, {
customEvents: {
webkitTransitionEnd: 'transitionEnd',
......
}
});
Now create a mixin to be used in your view as:
Yn.Modal = Em.Mixin.create({
isHidden: true,
classNameBindings: ['isHidden'],
classNames: ['modal'],
transitionEnd: function(event) {
// transitionEnd triggers multiple times
// http://stackoverflow.com/questions/4062105/webkit-transitionend-event-grouping
if ( event.originalEvent.propertyName === '-webkit-transform' &&
$(event.target)[0] === this.$()[0] ) {
var eventName = this.get('isHidden') ? 'transitionHide' : 'transitionShow' ;
this.trigger(eventName);
}
}
});
You can now insert the view in the DOM via appendTo or any handlebars view template or whatever method you use, and manages your view with its isHidden property which can be bound for example to a controller UI property, you could also interact with the view with the view lifecycle hooks as didInsertElement or the new defined as transitionHide, transitionShow hooks.
You can use my modified Bootstrap.ModalPane
ex:
App.ContactView = Bootstrap.ModalPane.extend({
closeOnEscape: false,
showBackdrop: true,
showCloseButton: false,
heading: 'Contact',
primary: "Save changes",
secondary: "Cancel",
classNames: ["large-modal"],
bodyViewClass: Ember.View.extend({
templateName: 'contact-body'
}),
callback: function (opts, event) {
var controller = this.get('controller');
if (opts.primary) {
controller.save();
} else {
controller.cancel();
}
}
});
In you controller you can then do something like this:
editContact: function (contact) {
var contactController = this.controllerFor('contact');
contactController.set('content', contact);
var contactView = App.ContactView.create({
controller: contactController
});
contactView.append();
},
You can also define your own modalPaneTemplate with customizations. It's the principle of how the Boostrap.ModelPane works that matters, default it only supports 2 buttons at the bottom. If you want 5 buttons, or buttons in the header, start coding yourself and create a custom modalPaneTemplate.