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.
Related
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/
I'm trying to do something which must be really simple to accomplish in Ember.
I want to show a button in my template based on the boolean state of a property:
{{#if canFavoriteTag}}
{{d-button action="favoriteTag" label="tagging.favorite" icon="star-o" class="admin-tag favorite-tag"}}
{{else}}
{{d-button action="unFavoriteTag" label="tagging.unfavorite" icon="star-o" class="admin-tag favorite-tag tag-unfavorite"}}
{{/if}}
I have created a property called canFavoriteTag with a function which I want to return true or false to the template based on whether the user can favorite the tag or not:
export default Ember.Controller.extend(BulkTopicSelection, {
canFavoriteTag: function() {
const self = this;
var ticker = this.get('tag.id');
console.log('checking can fav stock:' + ticker);
Discourse.ajax("/stock/get_users_favorite_stocks", {
type: "GET",
}).then(function(data) {
var favable = true;
for (var i = data.stock.length - 1; i >= 0; i--) {
var stock = jQuery.parseJSON(data.stock[i]);
if(ticker.toLowerCase() == stock.symbol.toLowerCase()) { console.log(ticker + ' is a favorite stock: ' + stock.symbol.toLowerCase()); favable = false; }
}
console.log(favable);
return favable;
});
}.property('canFavoriteTag') <-- unsure about this?
...
When the page loads, the wrong button shows (always the "false" one).. I see in the console that the favable variable gets set to false when the ajax call completes, but the button never changes. How do I get it to show the right button based on the function? Do I need to use a promise? If so, how?
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);
}
});
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();
I have an Ember object that has several actions :
MyObject = Ember.Object.extend {
action1: ->
console.log "do 1"
action2: ->
console.log "do 2"
}
I'd like to call one of these action depending on some condition. The easy way is
to check for this condition and call the needed condition using if or switch-case. But, I need something more scalable like a hash of method inside my object and invoke the needed method by just sending their name like object.exec('action1').
Does ember object has already this feature ?
Here's a quick implementation of a dynamic methods' call method in javascript.
var obj = {
action1 : function(item) {
return item;
},
action2 : function(item) {
return item;
},
exec:function(name){
if(this[name] ==== 'undefined') return;
var args = Array.prototype.slice.call(arguments);
return this[name](args.slice(1,args.length))
}
}
obj.exec('action1','Hello')
obj.exec('action2','World')
Should return Hello World