How to avoid deeply nested promises in protractor - javascript

I have a 'select' element in a UI component from which I need to retrieve the selected option (if any). As a beginner in both JavaScript and protractor, I am having trouble figuring out how to accomplish this without a bunch of nested promises:
I have two locators -- one for the selector's current selection and one for all the options:
selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));
getSelectedOption = function () {
return this.selector.getText().then( function (selectionText) {
return this.selectorOptions.filter(function (option) {
option.getText().then(function (optionText) {
if(optionText === selectionText) {
option.getAttribute("value").then(function (value) {
// Some logic here which uses the value to return an pojo representing the selection
})
}
})
})
})
};
The above is just awful and I am sure this can be done better. I have looked at a lot of examples, but I haven't found one that involves dealing with nested promises which need to take parameters and then do something conditional based on the value, so I am having difficultly applying them to my situation, mostly because I don't really feel comfortable with asynchronous programming yet. How can I take the mess above and refactor it into something that isn't a nested callback hell?

Maybe playing a little bit with promises, protractor, arguments and bind you could get it quite cleaner.
Then you are using the protractor filter method which needs a boolean to be returned, in order to filter your values. But, from the way you used it, maybe
you were looking for each():
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.each
I didn't have any chance to test the following code, so it may most probably not work :D
selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));
getSelectedOption = function () {
return this.selector.getText().then(firstText.bind(this));
};
function firstText(text) {
return this.selectorOptions.filter(filterSelector.bind(this, text));
}
function filterSelector(text, option) {
return option.getText().then(optionText.bind(this, text, option));
}
function optionText(text, option, optionText) {
if(optionText === text) {
return option.getAttribute("value").then(someLogic);
}
}
function someLogic(value) {
console.log(value);
// value should be your value
// Some logic here which uses the value to return an pojo representing the selection
// return true or false, filter is still waiting for a boolean...
}
Another version just using arguments without function parameters. Specially follow the arguments which got printed, to see if the order is correct:
selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));
getSelectedOption = function () {
return this.selector.getText().then(firstText.bind(this));
};
function firstText() {
console.log(arguments);
// arguments[0] should be your selectionText
return this.selectorOptions.filter(filterSelector.bind(this, arguments[0]));
}
function filterSelector() {
console.log(arguments);
// arguments[0] should be your previous selectionText
// arguments[1] should be your option
return arguments[1].getText().then(optionText.bind(this, arguments[0], arguments[1]));
}
function optionText() {
console.log(arguments);
// arguments[0] should be your optionText
// arguments[1] should be your selectionText
// arguments[2] should be your option
if(arguments[0] === arguments[1]) {
return arguments[2].getAttribute("value").then(someLogic);
}
}
function someLogic(value) {
console.log(value);
// value should be your value
// Some logic here which uses the value to return an pojo representing the selection
// return true or false, filter is still waiting for a boolean...
}

Related

Angular services with default values for non-existing attributes

Working on an Ionic application that performs both in Android and Windows.
There are services, such as Ionic's $ionicLoading, which we override functionality in order to work properly in windows:
angular.factory('$ionicLoading', function(){
return {
show: function (){...} // custom implementation
hide: function (){...} // custom implementation
}
});
But there are other services which we have to override only to not break the app.
In this cases it would be really useful to provide a service that won't do anything. For example:
angular.factory('$ionicExampleService', function(){
return {
*foo*: angular.noop // for operations
*bar*: promise // returns promise
}
});
Note: I know that a better way of doing this would be with a service that chooses between Ionic's implementation or a made one, but this is just for the sake of learning.
The ideal would be going even further, it would be magnificent to be able to return something even more bulletproof. Something like a generic flexible services:
angular.factory('$ionicPopup', function(){
return /*magic*/;
});
$ionicPopup.show({...}) // show was not defined
.then(foo); // won't break and will execute foo()
It is possible?
From what I understood you need to override implementation of existing services. You can do that with an angular service decorator.
A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service.
For more information you can check angular documentation. One simple example would be:
app.factory('someService', function () {
return {
method1: function () { return '1'; }
method2: function () { return '2'; }
};
});
app.decorator('someService', function ($delegate) {
// NOTE: $delegate is the original service
// override method2
$delegate.method2 = function () { return '^2'; };
// add new method
$delegate.method3 = function () { return '3'; };
return $delegate;
});
// usage
app.controller('SomeController', function(someService) {
console.log(someService.method1());
console.log(someService.method2());
console.log(someService.method3());
});
EDIT: Question - How to override every method in the service?
var dummyMethod = angular.noop;
for(var prop in $delegate) {
if (angular.isFunction($delegate[prop])) {
$delegate[prop] = dummyMethod;
}
}
I hope that this helps you.
Using an evaluation for each assignment based on an object property, similar to this:
myVar = myObj.myPropVar === undefined ? "default replacement" : myObj.myPropVar;
Basically you're using a check for if the property has been defined, substituting a default value if it hasn't, and assigning it if it has.
Alternatively, you can use a modified version of the global function in Sunny's linkback to define defaults for all those properties you might assume to be undefined at specific points in your code.
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else if(prop == "foo") return "default value for foo";
else if(prop == "bar") return "default value for bar";
/* etc */
else return "default for missing prop";
}
Hope that helps,
C§
use var a = {}; to declare new variable.

Evaluate all functions in Javascript Object

I've created a Javascript object with a layout like this:
var myObject : {
doSomething : function (args) {
//do some stuff
return result;
}
//note the args for both functions are of the same format
doSomethingElse : function (args){
//do some other stuff
return otherResult
}
}
Now I want to achieve the following, but am not sure on how to do it in a clean way:
console.log(myObject(args));
/*
Output:
{
doSomething : result,
doSomethingElse : otherResult
}
*/
I'd like to keep both functions separate, as I would like to be able to refer to them in separate instances, but also evaluate both at once to get the desired output as above.
Thanks in advance for your help!
I'd imagine you'd have to iterate, something like:
var results = {},
args = "some args";
for (var key in myObject) {
if (typeof myObject[key] === "function") results[key] = myObject[key](args);
}
console.log(results); //should be the output you want (untested)
Add one more method that calls both methods (passing them the arguments) and returns your desired object output. Something like this would do:
...
doBoth: function (args) {
return {
doSomething: this.doSomething(args),
doSomethingElse: this.doSomethingElse(args)
};
}
...
Calling myObject.doBoth(args) will return the result you hoped for.
jsFiddle Demo
You can also do something a bit more advanced, for example listing the method names you expect to be run for the result:
...
doThese: function (methods, args) {
var result = {};
methods.forEach(function (m) {
result[m] = this[m](args);
}, this);
return result;
}
...
You could invoke this with myObject.doThese(['doSomething', 'doSomethingElse'], args).
jsFiddle Demo
I would advise you not to run all the methods on the object (like other answers suggest). Seems easier at first, but will make your object difficult to modify and cause unintended behaviour in the future.

Subscribe arrayChange on knockout computed accessor

I have a case where I need to listen for array changes of a computed that is simply returning a filtered value of an observable.
However, I do need to have the full list of changes, as .subscribe(function(changes){},null,'arrayChange') would do on an observableArray.
What I understand is that arrayChange does not work in the case of a computed value, because it probably remakes a new array and so there's no specific change to list.
See http://jsfiddle.net/darknessm0404/A6D8u/1/ for a complete example.
// The following does not work, but I'd like it
computedTest.subscribe(function(changesList){
console.log('COMPUTED subscription : arrayChange');
}, null, 'arrayChange');
The only way I seem to achieve what I want is to create another observable array which would have push/delete depending on the changes, so I would be able to get the 'arrayChange' method work.
Full example of my idea:
this.events.listFiltered = ko.observableArray().extend({ rateLimit: 0 });
this.events.listFiltered_Worker = ko.computed(function () {
var listFiltered = me.events.listFiltered();
ko.utils.arrayForEach(me.events.list(), function (item) {
index = listFiltered.indexOf(item);
if (FILTERING_CASE_HERE) {
if (index < 0) {
listFiltered.push(item);
}
} else if (index >= 0) { // Delete
listFiltered.splice(index, 1);
}
});
return ko.utils.arrayFilter(me.events.list(), function (item) {
return !(item.end().isBefore(filterStart) || item.start().isAfter(filterEnd));
});
return __rd++;
}).extend({ rateLimit: 0 });
this.events.listFiltered.subscribe(function () {
debug('inside subscribe');
debugger;
}, null, 'arrayChange');
However I was wondering if there's a easier solution to this problem?
Knockout supports arrayChange for any observable, which you have to enable specifically.
var computedTest = ko.computed(function() {
...
}).extend({trackArrayChanges: true});
http://jsfiddle.net/mbest/A6D8u/2/
If you look at the Knockout source code, this is what's done automatically for observable arrays.
If this a common scenario in your project you could create a wrapper function that does this:
function computedArray() {
return ko.computed.apply(ko, arguments).extend({trackArrayChanges: true});
}

How to reference a custom jQuery validator method in an $.ajax() success callback?

My apologies for the one millionth iteration of this type of question. I've already seen a number of other posts talking about this, and I still can't wrap my head around being able to invoke a function after the callback is successful and returns. I must have read this post over half a dozen times:
How do I return the response from an asynchronous call?
The Code
Anyway .., my question I hope anyone could shed some light on. I have a custom jQuery validator method that checks if a username is available.
jQuery.validator.addMethod("usernameAvailability",
function(value, element) {
console.log('starting usernameAvailability');
if(this.optional(element) === true){return true;}
else{
console.log('ending usernameAvailability, about to return get_availability');
return availabilityCheck.get_availability('username');
}
}, "Username already in use." );
The custom jQuery validator method calls get_availability(element) in namespace availabilityCheck.
var availabilityCheck = (function() {
var usernameIsAvailable, emailIsAvailable, Callback, AJAX_Availability_Check, change_Availability;
usernameIsAvailable = null;
emailIsAvailable = null;
AJAX_Availability_Check = function(callback, element){
console.log('starting AJAX_Availability_Check');
if(element==="username"){
selection = {username:$('#id_username').val()};
}
else{
selection = {email:$('#id_email').val()};
}
$.ajax({
url: '/register/',
type: 'get',
data: selection,
dataType:'text',
async: true
}).done(Callback(element));
};
change_Availability = function(element, bool) {
if(element === 'username'){
usernameIsAvailable = bool;
}
else{
emailIsAvailable = bool;
}
};
Callback = function(element) {
return function(result, textStatus){
bool = result === "True" ? true: false;
change_Availability(element, bool);
return usernameIsAvailable;
};
};
return{
get_availability: function(element){
AJAX_Availability_Check(Callback, element);
return element === 'username' ? usernameIsAvailable : emailIsAvailable;
}
}
})();
The Problem and My Question
My problem The input correctly validates whether the username is already in use, but the user needs to trigger validation twice since get_availability returns before the Callback can change usernameIsAvailable to the proper boolean.
My Question How do I restructure my code so my custom jQuery validate method is invoked by the callback? Or how do I ensure that it won't validate until the Callback returns?
The problem is your structure... I don't know where you got that from but throw it away and burn it, then forget you ever saw it.
You can simplify your code to look like this:
var availabilityCheck = function() {
var usernameIsAvailable = null, emailIsAvailable = null,
AJAXAvailabilityCheck, changeAvailability;
AJAXAvailabilityCheck = function(element){
console.log('starting AJAX_Availability_Check');
if(element==="username"){
selection = {username:$('#id_username').val()};
}
else{
selection = {email:$('#id_email').val()};
}
$.ajax({
url: '/register/',
type: 'get',
data: selection,
dataType:'text',
async: true
}).done(changeAvailability(element));
};
changeAvailability = function(element, theBool) {
if(element === 'username'){
usernameIsAvailable = theBool;
}
else{
emailIsAvailable = theBool;
}
};
this.getAvailability = function(element) {
AJAXAvailabilityCheck(element);
return element === 'username' ? usernameIsAvailable : emailIsAvailable;
}
};
So your callback is now actually something useful instead of just another useless layer.
However as I point out below, your result wasn't ever actually defined as far as I could tell so your going to have to figure out what theBool should be.
Some things of note:
Callback = function(element) {
return function(result, textStatus){
bool = result === "True" ? true: false;
change_Availability(element, bool);
return usernameIsAvailable;
};
};
You return an anonymous function for no particular reason, and your result variable isn't defined... at least with the code as you have it, so with what you have it's always resolving to false. Also if your just checking for truthyness then you don't need a tuple you can just do result === "True" which will evaluate to true or false, no need for the extra ? true : false.
Also, don't use words like bool for variable names. Bool is a type of variable and is a reserved word. Javascript lets you use it cause... Javascript will let you do just about anything, but its bad practice.
Finally you mixed like 10 different types of casing. Now casing is a personal preference (I personally prefer underscores to camelCase, which is the javascript convention), but no matter what case you use. USE ONLY ONE CASE!
Now I believe your real issue here is that you don't understand what a self-invoking function is for.
You use this syntax: var availabilityCheck = (function(){})(); which creates a self-invoking function; which means that it's going to fire without being called! Which means all this does is call your ajax and cause an extraneous server hit when your user hasn't even entered any data yet.
I believe that you did it so you could use this syntax availabilityCheck.getAvailability() in your invoking function, but the better way to do that is to do what I did above and make getAvailability a property of availabilityCheck by using the this keyword. Then you can use the same syntax without running your whole function twice.
You should almost NEVER (there are always exceptions of course) put an ajax call in a self invoking function. If the call doesn't depend on user input, then you should of just loaded the data when your page was requested the first time.

jQuery/Javascript: How can I evaluate the validity of function arguments efficiently with a range of valid types?

I've got a rather large plugin that I am currently writing in jQuery which is using a lot of internal functions that can accept varying arguments depending on the function.
I caught myself constantly writing the following in every function to stop the code from running if an argument hasn't been supplied or isn't valid:
add : function(args) {
if (args===undefined) return;
// function code;
},...
I was hoping that in a DRY type of sense it would be a good idea to write a little internal helper function that would do this for me.
Is this actually a good idea and most importantly what is the best/secure way to check for a varied range of acceptable arguments?
There are a lot of functions with multiple arguments in this plugin, for example:
load : function( filename , path , excludeFromRandom , callback ) {}
where filename is a string,
path is a string,
excludeFromRandom is a boolean and
callback can be a function or a string.
What is a good way to check for the existence and validity of these types of arguments without rewriting the same code over and over?
Any suggestions and ideas would be great.
Thanks for reading.
It depends to what extent you want to do this. In idea would be to create a validation function which takes a argument -> rule mapping. E.g.:
function foo(somestring, somenumber) {
var rules = {
'somestring': Validator.rules.isString,
'somenumber': Validator.rules.inRange(5,10);
};
}
Validator would contain the basic logic and some helper functions (rules):
var Validator = {
valid: function(args, rules) {
for(var name in rules) {
if(!rules[name](args[name])) {
return false;
}
}
return true;
},
rules: {
isString: function(arg) {
return (typeof arg === 'string');
},
inRange: function(x,y) {
return function(arg) {
return !isNaN(+arg) && x <= arg && arg <= y;
}
}
}
}
This is just a sketch, it certainly can be extended (like accepting multiple rules per argument), but it should give you some idea.
That said, you don't have to check every argument. Provide decent documentation. If people use your plugin in a wrong way, i.e. passing wrong argument types, then your code will throw an error anyway.
Update:
If want to do this very often, then a good idea is to write a wrapper function and you just pass the function and the rules to it:
function ensure(func, rules, context) {
context = context || this;
return function() {
if(Validator.valid(arguments, rules)) {
return func.apply(context, arguments);
}
return null; // or throw error, whatever you want
}
}
Then you can define your function normally as:
var foo = function(somestring, somenumber) {
// ...
};
and just add validation to it:
var rules = {...};
foo = ensure(foo, rules);
You could even consider to make ensure accept a callback which gets called on error or success of the function, instead of returning a value. There are a lot of possibilities.

Categories

Resources