I'm generating date list in a selected month where for every date, I will make a Backbone.Collection fetch.
Here is how I loop for every single date in a month
for (var i = 1; i <= numOfDays; i++) {
var d = i < 10 ? '0'+i:i;
var v = new View({
dt: this.year + this.month + (d),
param: this.array
});
this.$el.append(v.render().el);
}
As you can see above, each view (backbone.view) will represent a date and param object. Then I loop the param object using UnderscoreJs _.each method upon callin render()
_.each(this.param, this.reading, this);
and later it will initiate a new object of backbone.collection and perform fetch right away.
reading: function (value, key) {
var _this = this;
this.fetchData(new Data.Collection(), '/api/ + value.ipid').done(function(coll) {
var input = $('<input>').val(value.ipid).attr('data-inid', coll.first().get('in_id'));
_this.$el.append(input);
});
}
I separate a function this.fetchData like this
fetchData: function (obj, url) {
var deferred = $.Deferred(),
collection = obj;
collection.url = url;
var xhr = collection.fetch().done(function(data, textStatus, jqXHR) {
deferred.resolve(collection, data, textStatus, jqXHR);
}).fail(deferred.reject);
var promise = deferred.promise();
promise.abort = _.bind(xhr.abort, xhr);
return promise;
}
Unfortunately, for each view, the order of item inside param object will shift because it depends on race condition of backbone.ajax. Let see the items of param object
[{ipid: 44, measure: "cumec"},{ipid: 45, measure: "meter"},{ipid: 46, measure: "milimeter"}{ipid: 47, measure: "cumec"}]
The object items are in proper order. 44, 45, 46 and 47. But to get the listing as we pass it initially will change after fetch operation.
How do I tell backbonejs or underscorejs to wait every fetch operation to complete before start looping another item inside param object
I just want the loop (_.each) to wait for the fetch operation to complete before it continue looping
Hope somebody can enlighten the way to achieve this. Thank you and have a good day
If you create and append the DOM element at view creation time (i.e. inside the reading function), then reference from inside the done callback, the elements will be in the same order as inside your param array.
Side note: You can instantiate the collection with the url directly, and there is no need to wrap another jQuery promise around the promise returned by fetch.
I think the solution to your problem is using the iterator provided by Async.js library, so your code would look like:
async.eachSeries(this.param, _.bind(this.reading, this), yourCallbackWhenTheArrayIsFinished);
reading: function (value, callback) {
var _this = this;
this.fetchData(new Data.Collection(), '/api/ + value.ipid').done(function(coll) {
var input = $('<input>').val(value.ipid).attr('data-inid', coll.first().get('in_id'));
_this.$el.append(input);
callback();
});
}
So until callback() is called from the done handler, eachSeries doesn't process the next element
Related
I have this code that is called in an ajax callback once the data is fetched:
function onFetchCallback(data) {
onFetchCallback.accumData ??= [];
onFetchCallback.timeLine ??= [];
onFetchCallback.tempValues1 ??= [];
onFetchCallback.tempValues2 ??= [];
onFetchCallback.char;
const hasNulls = data.includes(null);
if (!hasNulls) {
//push values into different arrays
} else {
//push the rest of no nulls if there is any...
}
}
I dont find this clean, bacause I am checking if the arrays that accumulate the data are initialized for every callback call. I think it woull be better to have the callback function initialized, so that the arrays are created, and then call the functions that will store the data in the arrays.
So I did:
function onFetchCallback() {
function init() {
onFetchCallback.accumData ??= [];
onFetchCallback.timeLine ??= [];
onFetchCallback.tempValues1 ??= [];
onFetchCallback.tempValues2 ??= [];
onFetchCallback.char;
}
function store(data) {
const hasNulls = data.includes(null);
if (!hasNulls) {
//push values into different arrays
} else {
//push the rest of no nulls if there is any...
}
}
onFetchCallback.init = init;
onFetchCallback.store = store;
}
So then when I need to use my callback I do:
onFetchCallback();
onFetchCallback.init();
myWhateverFunc(onFetchCallback.store);
Being myWhateverFunc the one calling the callback:
function myWhateverFunc(callback) {
$.ajax({
//whatever
})
.done(function (data) {
callback(data); //CALL
});
}
This works and I find it super javasScriptic so I do it all the time. Meaning the onFetchCallback initialization + other methods call to handle the function members. I do not know js in depth so I would like to know of there are any flaws with this pattern, or if there is any other better/cooler/javaScriptStylish way to do this.
The pattern you're using has a lot of resemblence with the function constructor which is more commonly used in JavaScript.
An implementation of your code in the function constructor pattern would like like this:
function FetchCallback() {
this.accumData = [];
this.timeLine = [];
this.tempValues1 = [];
this.tempValues2 = [];
this.char;
}
FetchCallback.prototype.store = function(data) {
const hasNulls = data.includes(null);
if (!hasNulls) {
// push values into different arrays
} else {
// push the rest of no nulls if there is any...
}
};
It enables you to create an object with properties and methods which are predefined. This removes the hassle of repetition when you need multiple instances of this same object.
To use the constructor you'll need to create a new instance with the new keyword. This will return an object with all the properties and methods set.
const fetchCallback = new FetchCallback();
// Note the .bind() method!
myWhateverFunc(fetchCallback.store.bind(fetchCallback));
Edit
You'll need to specifically set the value of this to the created instance that is stored in fetchCallback. You can do this with the bind() method. This methods explicitly tells that this should refer to a specific object.
The reason to do this is that whenever you pass the store method as the callback to the myWhateverFunc, it loses it's context with the FetchCallback function. You can read more about this in this post
The main difference between this and your code is that here the FetchCallback function will be unaltered, where your function is reassigned every time you call onFetchCallback() and onFetchCallback.init(). The constructor pattern will result in more predictable behavior, albeit that the this keyword has a learning curve.
I'm using array value as variable and then call the function N method, how I get them in function N.
I really want to simulate the Javascript array method, I don't want to use parameters to achieve it. For example,
var p1 = [1,2,3,4,5]; p1.push(6);
function _Array() {
this._this = this;
}
_Array.prototype.show = function () {
this._this.forEach(function(item){alert(item);}) //how to print 1,2,3,4,5
};
var p1 = [1,2,3,4,5];
p1 = new _Array();
//p1._Array.call(p1); //not work
// new _Array().show.call(p1); //not work
// p1.show(); //not work
You have to store that in the instance
function N(arr) {
this._this = arr
}
N.prototype.say = function () {
this._this.forEach(function (item) {
console.log(item)
})
}
p1 = new N([1, 2, 3, 4, 5])
p1.say()
If you are insistent on wanting to write a method that takes the array by reference, you can modify the array prototype like so:
Array.prototype.show = function() {
this.forEach(item => alert(item));
}
However, it is a VERY BAD IDEA to modify the built in object prototypes, as this can cause conflicts with external libraries implementing their own "show" function that is being used differently, or cause incompatibilities with future versions of JS that implements this method.
It would be far more prudent in most situations to pass the array as a parameter, unless you have a very specific reason why you're not doing so. In that case, you should at least prefix the method with some sort of project identifier to minimize the chances of conflicts occurring.
This is a follow-up question to Take elements while a condition evaluates to true (extending ElementArrayFinder) topic and #cvakiitho's answer in particular.
The problem:
After executing the following code:
var i = 0;
var el = element.all(by.css('ul li a'));
var tableItems = [];
(function loop() {
el.get(i).getText().then(function(text){
if(text){
tableItems.push(el.get(i));
i+=1;
loop();
}
});
}());
tableItems would contain an array of ElementFinder instances - or, to put it simple - an array of web elements.
The Question:
Is it possible to convert an array of ElementFinders to an ElementArrayFinder instance?
The Motivation:
The reason I want this is to have all of the convenient ElementArrayFinder functional utilities, like map(), each(), reduce() and to be able to call getText() on it producing a promise which would resolve into an array of element texts.
Constructor of ElementArrayFinder basically requires two main arguments: protractor instance and a function getWebElements, which should return a promise resolving to an array of Web Elements. As soon as every ElementFinder has a method to get it's Web Element called getWebElement, it is possible to create such a function.
var arrayOfElementFinders = [el1, el2, el3];
var getWebElements = function () {
// create array of WebElements from array of ElementFinders
var webElements = arrayOfElementFinders.map(function (ef) {
return ef.getWebElement();
});
// immediately resolve and return promise
return protractor.promise.fulfilled(webElements);
};
Now when all requirements are satisfied, it is possible to create a new instance of ElementArrayFinder:
var elArrayFinder = new protractor.ElementArrayFinder(protractor, getWebElements);
To make it easy to use I would add a static method to ElementArrayFinder constructor and include it somewhere before tests start:
protractor.ElementArrayFinder.fromArray = function (arrayOfElementFinders) {
var getWebElements = function () {
var webElements = arrayOfElementFinders.map(function (ef) {
return ef.getWebElement();
})
return protractor.promise.fulfilled(webElements);
};
return new protractor.ElementArrayFinder(protractor, getWebElements);
};
And use it in tests like:
var elArrayFinder = protractor.ElementArrayFinder.fromArray(arrayOfElementFinders);
I am working on some legacy code for my workplace and cannot figure out how to process data from a data object that is returned. The retrieveThis function is supposed to retrieve the object data:
myObj.retrieveThis(new myObj.getThisData({num : 10, page : 1, sorting : "stampDescending"}), function () {myCallback(this);});
var myObj = function () {
var getThisData = {
// this function populates an array and returns it to the retrieveThis function
}
var theObject = {
retrieveThis: function(a, b) {
var OBJ = {};
// OBJ is populated by the data from var getThisData...I checked
setTimeout(function () {
b(OBJ);
}, 1000);
}
}
return theObject;
})();
I am having trouble getting the data ("theObject") to my callback function (or at all). I pass this to myCallback(), where myCallback is:
function myCallback(obj) {
alert(Object.keys(obj));
}
The alert box shows a list of keys including document, jQuery, and myObj. It looks like the data from OBJ is populated from the array allTheData, but I can't seem to pass this back (as return theObject;) to process it. Where am I going wrong here?
Note - I cannot modify this legacy code. I need to process "theObject".
You pass wrong callback function
inside you call it with parameters, so you should define it with parameters
myObj.retrieveThis(
new myObj.getThisData({num : 10, page : 1, sorting : "stampDescending"}),
myCallback);
in this case in first param to myCallback passed OBJ object
I am trying to use the following code to set and get name value pairs in a Chrome Extension.
if (!this.Chrome_getValue || (this.Chrome_getValue.toString && this.Chrome_getValue.toString().indexOf("not supported") > -1)) {
this.Chrome_getValue = function (key, def) {
chrome.storage.local.get(key, function (result) {
return result[key];
});
};
this.Chrome_setValue = function (key, value) {
var obj = {};
obj[key] = value;
return chrome.storage.local.set(obj)
}
}
I am then invoking these as follows:
Chrome_setValue("City", "London");
var keyValue = Chrome_getValue("City");
The problem is the keyValue is always 'undefined' even if I put a 1 second delay in trying to read back the value. I understand this is because the 'chrome.storage.local.get' function is asynchronous.. the following code works fine.
Chrome_setValue("City", "London");
chrome.storage.local.get("City", function (result) {
alert(result["City"]);
});
Is there any way when binding the keyValue (using get) I can force the code to wait for the function to return a response? Or perhaps I'm approaching this from the wrong angle. Essentially I am looking for a way I can abstract out the set and get methods for handling data within the chrome.storage framework? Ideally two simple functions I can call for setting and retrieving name value pairs.
Before I was using localStorage which was very straightforward.
//Set
localStorage["City"] = "London";
//Get
var keyValue = localStorage["City"];
QF_D, I'm into educated guesswork here, so be prepared for this not to work first time .....
..... here goes :
if (!this.Chrome_getValue || (this.Chrome_getValue.toString && this.Chrome_getValue.toString().indexOf("not supported") > -1)) {
this.Chrome_getValue = function (key, def) {
var dfrd = $.Deferred();
chrome.storage.local.get(key, function (result) {
dfrd.resolve(result[key]);
});
return dfrd.promise();
};
this.Chrome_setValue = function (key, value) {
var dfrd = $.Deferred();
var obj = {};
obj[key] = value;
var listen = function(changes, namespace) {
dfrd.resolve(changes[key]);
chrome.storage.onChanged.removeListener(listen);//remove, to prevent accumulation of listeners
}
chrome.storage.onChanged.addListener(listen);
chrome.storage.local.set(obj);
return dfrd.promise()
}
}
The get and set functions should thus return jQuery promises. The promise returned by the set function is resolved with a StorageChange object defined here. In general you won't need this object, rather just respond to the promise being resolved.
Everything above is untested. I'm not too sure about :
chrome.storage.onChanged..... Should it be chrome.storage.local.onChanged....?
.removeListener(). It seems resonable that it should exist if addListener exists, though I can't find any evidence of it.
Well, it's essentially because the chrome.storage API is asynchronous. You are facing something which is at the core of JavaScript.
What's executed in the chrome.storage.get callback is not returned by chrome.storage.get. You have 2 ways of dealing with it:
deal with the storage value in the callback
use the Promise pattern to keep your code feeling synchronous (cf. the previous answer)
You really have to figure out the difference between synchronous and asynchronous.