In Javascript, I created a function, activateClickable, to encapsulate the assignment of a callback to both the clicking and use of the enter key or spacebar on an element such as an with a tabindex. I set it up to allow optional arguments to be provided in case the callback takes arguments.
I'm testing this by having a click listener for the Previous arrow on a carousel receive arguments "Foo" and "Bar"; take care of some preliminary logic; and then pass the arguments, params, to the callback function, goPreviousSlide.
function goPreviousSlide(...test) {
console.log("Test");
console.log(test);
alert("This will go to the previous slide.");
}
function goNextSlide() {
alert("This will go to the next slide.");
}
function attachListeners() {
activateClickable("carousel-previous-arrow", function() { goPreviousSlide(); }, "Foo", "Bar");
activateClickable("carousel-next-arrow", function() { goNextSlide(); });
}
function activateClickable(className, callback, ...params) {
Array.from(document.getElementsByClassName(className)).forEach(function(elem) {
elem.addEventListener("click", function(event) {
event.preventDefault();
console.log("Hello");
console.log(params);
callback(...params);
});
elem.addEventListener("keyup", function(event) {
if (event.keyCode === 13 || event.keyCode === 32) { // Enter or spacebar
event.preventDefault();
callback(...params);
}
});
});
}
When I click the Previous arrow, what I'm expecting to see in Chrome is
Hello
> (2) ["Foo", "Bar"]
Test
> (2) ["Foo", "Bar"]
What I'm getting is
Hello
> (2) ["Foo", "Bar"]
Test
> []
The result in Edge is the similar:
Hello
> [object Array]: ["Foo", "Bar"]
Test
> [object Array]: []
I'm seeing, in other words, that params does have two elements when I'm in the click handler; I pass it to the custom callback, goPreviousSlide using ...params; but when it gets to the callee, which is supposed to receive it as ...test, the array test is empty. What am I missing?
You are not actually passing the params to goPreviousSlide because you are calling goPreviousSlide() inside callback without params:
function() { goPreviousSlide(); }, "Foo", "Bar")
//Called Here ------------^
When you call the callback with params you aren't calling `goPreviousSlide(), you are calling this function, which doesn't take parameters:
function() { goPreviousSlide(); }
Maybe you just want to pass goPreviousSlide as the callback alone:
activateClickable("carousel-previous-arrow", goPreviousSlide, "Foo", "Bar");
Then when you call the callback you can pass the params.
Alternatively you could accepts params in the callback and forward them on:
function attachListeners() {
activateClickable("carousel-previous-arrow", function(...params) { goPreviousSlide(...params); }, "Foo", "Bar");
}
Related
I'm converting from Async to Bluebird and can't figure out how to break a loop
Here's what I'm trying to achieve:
Loop through an array of data.
For each item, check if it exists on DB.
Add one item to the DB (first item that doesn't exist), and exit the .each() loop.
Any help will be highly appreciated.
Bluebird does not have a built in function for that type of operation and it's a little bit difficult to fit into the promise iteration model because iterators return a single value (a promise) which doesn't really give you the opportunity to communicate back both success/error and stop iteration.
Use Rejection to Stop Iteration
You could use Promise.each(), but you'd have to use a coded rejection in order to stop the iteration like this:
var data = [...];
Promise.each(data, function(item, index, length) {
return checkIfItemExists(item).then(function(exists) {
if (!exists) {
return addItemToDb(item).then(function() {
// successfully added item to DB
// lets reject now to stop the iteration
// but reject with a custom signature that can be discerned from an actual error
throw {code: "success", index: index};
});
}
})
}).then(function() {
// finished the iteration, but nothing was added to the DB
}, function(err) {
if (typeof err === "object" && err.code === "success") {
// success
} else {
// some sort of error here
}
});
This structure could be put into a reusable function/method if you have to use it regularly. You just have to adopt a convention for a rejected promise that really just meant to stop the iteration successfully rather than an actual error.
This does seem like an interesting and not all that uncommon need, but I haven't seen any particular defined structure with Promises for handling this type of issue.
If it feels like overloading a reject as in the above scenario is too much of a hack (which it sort of does), then you could write your own iteration method that uses a resolved value convention to tell the iterator when to stop:
Custom Iteration
Promise.eachStop = function(array, fn) {
var index = 0;
return new Promise(function(resolve, reject) {
function next() {
if (index < array.length) {
// chain next promise
fn(array[index], index, array.length).then(function(result) {
if (typeof result === "object" && result.stopIteration === true) {
// stopped after processing index item
resolve(index);
} else {
// do next iteration
++index;
next();
}
}, reject);
} else {
// finished iteration without stopping
resolve(null);
}
}
// start the iteration
next();
});
}
Here if the iterator resolves with a value that is an object has has a property stopIteration: true, then the iterator will stop.
The final promise will reject if there's an error anywhere and will resolve with a value of null if the iterator finished and never stopped or with a number that is the index where the iteration was stopped.
You would use that like this:
Promise.eachStop(data, function(item, index, length) {
return checkIfItemExists(item).then(function(exists) {
if (!exists) {
return addItemToDb(item).then(function() {
// return special coded object that has stopIteration: true
// to tell the iteration engine to stop
return {stopIteration: true};
});
}
})
}).then(function(result) {
if (result === null) {
// finished the iteration, but nothing was added to the DB
} else {
// added result item to the database and then stopped further processing
}
}, function(err) {
// error
});
Flag Variable That Tells Iterator Whether to Skip Its Work
In thinking about this some more, I came up with another way to do this by allowing the Promise.each() iteration to run to completion, but setting a higher scoped variable that tells your iterator when it should skip its work:
var data = [...];
// set property to indicate whether we're done or not
data.done = false;
Promise.each(data, function(item, index, length) {
if (!data.done) {
return checkIfItemExists(item).then(function(exists) {
if (!exists) {
return addItemToDb(item).then(function() {
data.done = true;
});
}
})
}
}).then(function() {
// finished
}, function(err) {
// error
});
Is there any way to access the first argument of the call() method of the ES6 arrow function?
var obj = {
getFn: function() { return () => {
//how to access bar here
return this.what;
}
},
any: 1
}
var foo = obj.getFn();
var bar = {
any: 2
};
foo.call(bar);
I'm not sure what you are trying to do, but if you are asking how to access the this value passed via .call, the answer is: you can't.
Since arrow functions don't have their own this, there is nothing that .call could set the value to. If you want a function to have its own this value, don't use an arrow function.
As noticed by Felix, arrow functions does not have own this value, so .call and .apply methods can't change the this of arrow function.
If you want to get access to the bar inside your arrow function, you can pass it via argument.
var obj = {
getFn: function() {
return (bar) => {
console.log(bar);
return this.what;
}
},
any: 1
};
var foo = obj.getFn();
var bar = {
any: 2
};
foo(bar); // prints bar into console
I need a way to catch the JSON response every time my datastore has loaded. My first try was to use the autoLoad property but the callback fires only on first load :
autoLoad: {
callback: function (records, operation) {
// do something with operation.response.responseText
}
}
So, I have decided to extend the load method :
load: function (options) {
var callback = options && options.callback;
return this.callParent([Ext.apply(options || {}, {
callback: function (records, operation) {
// do something with operation.response.responseText
if (callback) {
return callback.apply(this, arguments);
}
}
})]);
}
It works, but I wonder if the framework already provides a more elegant solution.
You can add a load listener to the store and grab the current request from it's proxy when the load event is fired.
var myStore = Ext.create("Ext.data.store", {
...whatever here
listeners: {
load: function(store){
store.getProxy().activeRequest.options.operation.response.responseText;
}
}
});
Thats if you want the response text specifically. If you want the response as a JSON object, you can use store.getProxy().reader.rawData; which is a little simpler
How can execute a function after a number of ajax requests have all completed regardless of whether they succeeded or error-ed out?
I've been trying to use $.when.apply(this, array) to pass an array of deferred jqXHR objects. However just like the docs say
In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when immediately >fires the failCallbacks for its master Deferred. Note that some of the Deferreds may still be >unresolved at that point.
How can leverage jQuery deferred objects to always wait for all the ajax calls to finish?
Maybe I should create my own deferred that will wrap all the other deferreds? If so I'm not quite clear how to set that up.
In the spirit of how the Promise specification is likely going for the future with a PromiseInspection object, here's a jQuery add-on function that tells you when all promises are done, whether fulfilled or rejected:
(function() {
// pass either multiple promises as separate arguments or an array of promises
$.settle = function(p1) {
var args;
if (Array.isArray(p1)) {
args = p1;
} else {
args = Array.prototype.slice.call(arguments);
}
return $.when.apply($, args.map(function(p) {
// make sure p is a promise (it could be just a value)
p = wrapInPromise(p);
// Make sure that the returned promise here is always resolved with a PromiseInspection object, never rejected
return p.then(function(val) {
return new PromiseInspection(true, val);
}, function(reason) {
// Convert rejected promise into resolved promise by returning a resolved promised
// One could just return the promiseInspection object directly if jQuery was
// Promise spec compliant, but jQuery 1.x and 2.x are not so we have to take this extra step
return wrapInPromise(new PromiseInspection(false, reason));
});
})).then(function() {
// return an array of results which is just more convenient to work with
// than the separate arguments that $.when() would normally return
return Array.prototype.slice.call(arguments);
});
}
// utility functions and objects
function isPromise(p) {
return p && (typeof p === "object" || typeof p === "function") && typeof p.then === "function";
}
function wrapInPromise(p) {
if (!isPromise(p)) {
p = $.Deferred().resolve(p);
}
return p;
}
function PromiseInspection(fulfilled, val) {
return {
isFulfilled: function() {
return fulfilled;
}, isRejected: function() {
return !fulfilled;
}, isPending: function() {
// PromiseInspection objects created here are never pending
return false;
}, value: function() {
if (!fulfilled) {
throw new Error("Can't call .value() on a promise that is not fulfilled");
}
return val;
}, reason: function() {
if (fulfilled) {
throw new Error("Can't call .reason() on a promise that is fulfilled");
}
return val;
}
};
}
})();
Then, you can use it like this:
$.settle(promiseArray).then(function(inspectionArray) {
inspectionArray.forEach(function(pi) {
if (pi.isFulfilled()) {
// pi.value() is the value of the fulfilled promise
} else {
// pi.reason() is the reason for the rejection
}
});
});
Keep in mind that $.settle() will always fulfill (never reject) and the fulfilled value is an array of PromiseInspection objects and you can interrogate each one to see if it was fulfilled or rejected and then fetch the corresponding value or reason. See the demo below for example usage:
Working demo: https://jsfiddle.net/jfriend00/y0gjs31r/
Updated code and issue:
I am creating a test harness for my RPC server. Currently it consists of a page which immeadiately fires off an AJAX request to retrieve all functions on the server. Once that is returned it creates a list of buttons so I can click to test. Eventually I will add dialog boxes to test parameter passing to the functions but currently I want to just fire off the basic request when I click the button. The issue I am seeing is that the onclick function is always firing the last function in the list presumably because when the click is fired key is set to the last value in the array. I thought to pass button.innerHTML value but that too suffers that the last button.innerHTML is that of the final key.
What do I need to do to fire off the action correctly?
Here is the business end of the code:
$(document).ready(function() {
$.jsonRPC.setup({
endPoint: '//api.localhost/index.php'
});
$.jsonRPC.request('getExampleData', {
params: [],
success: function(result) {
for (var key in result.result) {
console.log(key+' => '+result.result[key]);
var button = document.createElement('button');
button.innerHTML = result.result[key];
button.onclick = function() { callRPCFunction(result.result[key]); return false; }
var foo = document.getElementById("page");
foo.appendChild(button);
}
},
error: function(result) {
console.log(result);
}
});
});
function callRPCFunction(target) {
$.jsonRPC.request(target, {
params: [],
success: function(result) {
console.log(result);
},
error: function(result) {
console.log(result);
}
});
}
Assignment to element.onClick will not work until the element is added to the DOM. You may call element.onClick(callRPCFunction(result.result[key])); after foo.appendChild(element);. That might work!
You may use jQuery's live() here, it was created for these purposes:
$(element).live('click', callRPCFunction(result.result[key])