Ember.js global routed/transitioned events - ember.js

Is it possible to subscribe to all transition events in the application? Or alternatively some observable property containing the current route?
I'm integrating with a third-party UI component that needs to be synchronized to the current route.

The application controller has a currentRouteName property, as explained here. It's mostly for debugging, but I imagine that it's a fairly stable property that could be used in production.
EDIT: If you need to be alerted of all changes, use the hashchange event like Ember does internally. This will only work if you're using hash based routing though. If you're using Ember's history API based routing, you'll have to use that.

In your app_controller you can add this snippet which fires on every path/route change
currentPathDidChange: function currentPathDidChange() {
var path = this.get('currentPath')
}.observes('currentPath')

I solved this by hooking into Router.didTransition
Live example: http://jsbin.com/yuzedacu/5/edit (modified the example found here)
App.Router.reopen({
updateCurrentRoute: function(infos) {
var appController = this.container.lookup('controller:application');
if (!('currentRoute' in appController)) {
Ember.defineProperty(appController, 'currentRoute');
}
if (infos && infos.length > 0) {
// The last part of the route contains the route name
var route = infos[infos.length - 1].name;
// Collect the dynamic route parameters
var params = infos.reduce(function(a, b) {
// Parameter can be named anything
// assume there are 0 or 1 parameters
for (var name in b.params) {
if (b.params.hasOwnProperty(name)) {
// 1 parameter
return a.concat(b.params[name]);
}
}
// 0 parameters
return a;
}, []);
var path = [route].concat(params);
Ember.set(appController, 'currentRoute', path);
} else {
Ember.set(appController, 'currentRoute', []);
}
},
didTransition: function(infos) {
this.updateCurrentRoute(infos);
return this._super(infos);
}
});

Related

Sitecore 8 SPEAK - Calling Custom components Javascript method

My question is somewhat similar to followin unanswered question. (Not sure though)
Sitecore 8 SPEAK: Getting an Error When calling a Method in JS File
I am using Sitecore8
On my page there is a button and on its click event I want to call add() of custom datasource component.
Layout:
JS Code for the Page:
define(["sitecore"], function (Sitecore) {
var JsonListPage = Sitecore.Definitions.App.extend({
initialized: function () {
alert('Inside Json PageList Init');
},
loadData: function () {
alert('Button clicked');
app.add();
}
});
return JsonListPage;
});
JS Code for the custom datasource component:
define(["sitecore"], function (Sitecore) {
var model = Sitecore.Definitions.Models.ControlModel.extend({
initialize: function (options) {
this._super();
this.set("json", null);
alert('Inside Jsondatasource Init');
},
add: function (data) {
var json = this.get("json");
if (json === null)
json = new Array();
// this is done because array.push changes the array to an object which then do no work on the SPEAK listcontrol.
var newArray = new Array(json.length + 1);
for (var i = json.length - 1; i >= 0; i--)
newArray[i + 1] = json[i];
newArray[0] = data;
this.set("json", newArray);
}
});
var view = Sitecore.Definitions.Views.ControlView.extend({
initialize: function (options) {
this._super();
this.model.set("json", null);
}
});
Sitecore.Factories.createComponent("JsonDatasource", model, view, ".x-sitecore-jsondatasource");
});
.cshtml for Custom component:
#using Sitecore.Mvc
#using Sitecore.Mvc.Presentation
#using Sitecore.Web.UI.Controls.Common.UserControls
#model RenderingModel
#{
var userControl = Html.Sitecore().Controls().GetUserControl(Model.Rendering);
userControl.Requires.Script("client", "JsonDatasource.js");
userControl.Class = "x-sitecore-jsondatasource";
userControl.Attributes["type"] = "text/x-sitecore-jsondatasource";
userControl.DataBind = "Json: json";
var htmlAttributes = userControl.HtmlAttributes;
}
<div #htmlAttributes>
am here again
</div>
When the page loads:
It shows alert from Custom components Init
Then shows alert from host page's Init
On button click it shows the alert and after that gives error on "app".
There is some bit which I am missing.. any help would be appreciated.. Please let me know if you need anymore inputs.
Thanks in advance!
app is only available in debug mode so id avoid using that, use "this" instead.
From your code example it appears that you are calling app.Add(), There is no Add function on your pageCode, this is what your code is doing. Instead you need to access your components's Add Method.
Instead to access events within your component you want to call the function like this:
this.ComponentID.Add();
I have an example of a custom SPEAK component here you can refer to for how to create the component. https://github.com/sobek1985/MikeRobbinsSPEAKRichTextEditor
From the code is seems your creating a JSON datasource, there is an example by Anders here http://laubplusco.net/creating-simple-sitecore-speak-json-datasource/

Extend all views with custom data and filters in Sails.js

I'm developing a Sails.js application and I want to extend all views with custom data and functions.
What would be the best course of action to do so?
I've tried to create a policy to do so, but policies are only applied to routes with controllers.
Custom data
You can use a custom hook in order to achieve that.
Create a file at the specified path: api/hooks/viewsGlobals/index.js with the following content:
module.exports = function viewsGlobals (sails) {
return {
routes: {
before: {
// This middleware will be executed for every request.
'*': function (req, res, next) {
// Using condition to filter out static file requests.
if (req.accepted.some(function (type) {
return type.value === 'text/html';
})) {
res.locals.someData = {
// Place your custom data here.
};
}
return next();
}
}
}
}
};
Custom filters
Create a file at the following path: config/view-filters/toUpper.js and the following content:
// Replace this with templating engine that you use.
var swig = require('swig');
// Use the API of your templating engine to add custom filter.
swig.setFilter('toUpper', function (value) {
return value.toUpperCase();
});

Ember: How to get computed properties from a nested model?

First: I have no idea how to work with promises in Ember.js.
I want to call a property of my controller which depends on async model-data which is also nested.
Also, my model looks something like that:
+-------------+         +------------+
| Method      | hasMany |  Practice  |
|             +--------->            |
|             |         |            |
+-------------+         +------------+
                              |       
                              | hasMany
                        +-----v------+
                        | Alpha      |
                        |            |
                        |            |
                        +------------+
So I created something like this:
allAlphas: function() {
var self = this;
var returnValue = "nichts";
var promises = {
allAlphas: self.get('model.method').then(function(method) {
//get the practices
return method.get('practices');
}).then(function(practices) {
//get the alphaSField in EVERY practice
//the alphasField is the (hasmany 'alpha')member in practice
var alphasFields = practices.getEach('alphas');
return Ember.RSVP.all(alphasFields).then(function() {
return alphasFields;
});
}).then(function(alphasFields) {
// here: get all the alphas via promise or something
})
};
Ember.RSVP.hash(promises).then(function(results) {
// return all the alphas (of all pracitces in the method) in some way
});
}.property()
There are two Problems (like already metioned in the comments):
How to load nested hasMany async models like all alphas in all practices.
How to return the complete result as a property in the RSVP.hash-Method for use in templates or something
Can anybody help me?
Edit 06/20/2015
As #Kingpin2k suggested, Ive added a Gist for better understanding of my Problem:
https://gist.github.com/MarcManhart/e5c1d91e8fdfd876de37
just return an array, and populate the array after the fact.
allAlphas: function() {
var self = this,
returnValue = [];
this.get('model.method').then(function(method) {
//get the practices
return method.get('practices');
}).then(function(practices) {
//get the alphasField in EVERY practice
//the alphasField is the (hasmany 'alpha')member in practice
var alphas= practices.getEach('alphas');
Ember.RSVP.all(alphas).then(function(resolvedAlphas) {
resolvedAlphas.forEach(function(afs){
returnValue.pushObjects(afs.toArray());
});
});
});
return returnValue;
}.property()
Update
It looks like pushObjects doesn't like the ED Collection (or maybe it doesn't like the promises underneath, I didn't look into it that much). Also we should use the resolved values instead of the promises sent in (alphas vs resolvedAlphas in my code below).
Example: http://emberjs.jsbin.com/cinobetoyu/1/edit?js,output

Ember not updating query parameters when calling transitionToRoute() right after

In my application, I have an overall controller that manages state for a portion of the application, called SimpleSearch.
Within SimpleSearch, I have multiple SimpleSearchOptions, that display a list of choices to a user.
A user can select an option, and that selection is an action that is called from the view, that bubbles up to the SimpleSearchOptionController:
App.SimpleSearchOptionController = Ember.ObjectController.extend({
//....
select: function (option) {
option.queryName = this.get('queryName');
this.get('simpleSearch').setSelection(option);
this.set('selectedOption', option);
this.set('hasSelectedOption', true);
this.send('transitionToNextOption');
},
//....
This action calls this.get('simpleSearch').setSelection(option);, which registers the selection with the SimpleSearchController:
App.SimpleSearchController = Ember.ObjectController.extend({
//....
setSelection: function (option) {
this.set(option.queryName, option.value);
this.get('selectedOptions').set(option.queryName, option.value);
this.get('model').notifyPropertyChange('selectedOptions');
this.checkIfAllOptionsSelected();
},
//....
The important line in there is: this.set(option.queryName, option.value);.
After it registers the selection, it moves to the next option, and if there isn't one, it skips to the results of the search. That is called from this.send('transitionToNextOption');
App.SimpleSearchOptionController = Ember.ObjectController.extend({
//....
transitionToNextOption: function () {
var nextOptionId = parseInt(this.get("id")) + 1;
var numOfOptions = this.get('simpleSearch.numOfOptions');
if (nextOptionId < numOfOptions) {
this.transitionToRoute('simpleSearchOption', nextOptionId);
}
else {
this.transitionToRoute('simpleSearchResults');
}
},
//....
In setSelection() above, the line this.set(option.queryName, option.value); is setting a query parameter's value. This only works correctly, and the url gets updated accordingly for all options, when I'm not transitioning to a different route.
If I comment out the lines:
else {
this.transitionToRoute('simpleSearchResults');
}
Setting the property (this.set(option.queryName, option.value);) actually has the side effect of Ember updating the query parameter in the URL, which is my intent. If I include that line, and transition to a different route after setting that variable, the query parameter is not updated.
I was stepping through Ember's code, but I can't quite follow how it handles this. It continues into _doTransition(), and I've noticed that the transition to the route 'simpleSearchResults' always happens before the queryParams are passed through.
How do I get Ember to update the query parameter before it transitions to 'simpleSearchResults'?
Thank you for any and all help.
I solved my issue by wrapping the transition in an Ember.run.next() function:
transitionToNextOption: function () {
var nextOptionId = parseInt(this.get("id")) + 1;
var numOfOptions = this.get('simpleSearch.numOfOptions');
if (nextOptionId < numOfOptions) {
this.transitionToRoute('simpleSearchOption', nextOptionId);
}
else {
var self = this;
Ember.run.next(function() { self.transitionToRoute('simpleSearchResults'); });
}
},
I'm assuming, but have not verified, that Ember was queuing up the action to transition to 'simpleSearchResults', and handles the query parameters similarly. Perhaps the transition to a different route was somehow interrupting or overwriting the query parameter being written to the URL.

How can I simulate blur when testing directives in angularjs?

The problem
I am trying to test some directives (code for both below). One of them is an "email" (called "epost" in the code(norwegian)) directive. The solution to this should work for all of them, so I am keeping it to this one for now.
Technologies: Angularjs, Jasmine, Requirejs, (grunt & karma running in Chrome)
The directive validates email addresses in two ways; on upshift and on blur. I can test the upshift without problems as you can see in the test below, but I can't figure out how to simulate a blur so the bind('blur') in the directive runs.
What I have done
I have tried to catch the compiled element like this:
elem = angular.element(html);
element = $compile(elem)($scope);
And then in the test i tried several permutations to trigger the blur with a console log just inside the bind function in the directive. None of the below works. It does not trigger.
elem.trigger('blur');
element.trigger('blur');
elem.triggerHandler('blur');
element.triggerHandler('blur');
element.blur();
elem.blur();
I based the injection and setup on this: To test a custom validation angularjs directive
The email directive in angularjs wrapped in requirejs
define(function() {
var Directive = function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var pattern = /^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
elem.bind('blur', function() {
scope.$apply(function () {
if (!elem.val() || pattern.test(elem.val())) {
ctrl.$setValidity('epost', true);
} else {
ctrl.$setValidity('epost', false);
}
});
});
ctrl.$parsers.unshift(function(viewValue) {
if (pattern.test(viewValue)) {
ctrl.$setValidity('epost', true);
return viewValue;
} else {
return undefined;
}
});
}
};
};
return Directive;
});
The test (using jasmine and requirejs)
define([
'Angular',
'AngularMocks',
], function () {
describe('Directives', function () {
var $scope;
var form;
beforeEach(module('common'));
beforeEach(function () {
var html = '<form name="form">';
html += '<input type="text" id="epost" name="epost" epost="" ng-model="model.epost"/>';
html += '</form>';
inject(function ($compile, $rootScope) {
$scope = $rootScope.$new();
$scope.model = {
epost: null
};
// Compile the element, run digest cycle
var elem = angular.element(html);
$compile(elem)($scope);
$scope.$digest();
form = $scope.form;
});
});
describe('(epost) Given an input field hooked up with the email directive', function () {
var validEmail = 'a#b.no';
var invalidEmail = 'asdf#asdf';
it('should bind data to model and be valid when email is valid on upshift', function () {
form.epost.$setViewValue(validEmail);
expect($scope.model.epost).toBe(validEmail);
expect(form.epost.$valid).toBe(true);
});
});
});
});
I have been able to figure out where I went wrong after some breakpoint debugging.
The "element" item I get out using the approach described in the top of the question is not actually the directive it self. It's an object which wraps the form and the directive.
Like this
{ 0: // The form
{ 0: // The directive (input element)
{
}
}
}
To actually simulate a blur on the directive it self, I did something like this
var directiveElement = $(element[0][0]);
directiveElement.blur();
After getting the element I wanted, and wrapping it in a jQuery object (may be optional), it worked like a charm. I then used the approach like in the test in the question with $setViewValue and checked the model value like this.
form.epost.$setViewValue('a#b.no');
directiveElement.blur();
expect($scope.model.epost).toBe('a#b.no');
expect($scope.form.epost.$valid).toBeTruthy();
Hope this could be of help to others trying to figure the directive testing out.
I too ran into a similar problem and it mystified me. My solution was to use JQuery to get the input and then use angular.element(input).triggerHandler('blur') to make it work. This is odd to me because I do not have to do this with the click event.
spyOn(controller, 'setRevenueIsInvalid');
var sugarRow = $(element).find('tr#ve_id_5')[0];
var amount = $(sugarRow).find('input.amount')[0];
angular.element(amount).triggerHandler('blur');
expect(controller.setRevenueIsInvalid).toHaveBeenCalled();