Advanced search/queue array collection question - javascript

I have a pretty large number of objects "usrSession" I store them in my ArrayCollection usrSessionCollection.
I'M looking for a function that returns the latest userSessions added with a unique userID. So something like this:
1.
search the usrSessionCollection and only return one userSessions per userID.
2.
When it has returned x number of userSessions then deleted them from the usrSessionCollection
I'M stuck - would really love some code that can help me with that.
function ArrayCollection() {
var myArray = new Array;
return {
empty: function () {
myArray.splice(0, myArray.length);
},
add: function (myElement) {
myArray.push(myElement);
}
}
}
function usrSession(userID, cords, color) {
this.UserID = userID;
this.Cords = cords;
this.Color = color;
}
usrSessionCollection = new ArrayCollection();
$.getJSON(dataurl, function (data) {
for (var x = 0; x < data.length; x++) {
usrSessionCollection.add(new usrSession(data[x].usrID.toString(), data[x].usrcords.toString() ,data[x].color.toString());
}
});
Thanks.

The biggest issue is that you have made the array private to the outside world. Only methods through which the array can be interacted with are add and empty. To be able to search the array, you need to either add that functionality in the returned object, or expose the array. Here is a modified ArrayCollection:
function ArrayCollection() {
var myArray = new Array;
return {
empty: function () {
myArray.splice(0, myArray.length);
},
add: function (myElement) {
myArray.push(myElement);
},
getAll: function() {
return myArray;
}
}
}
Now to get the last N unique session objects in usrSessionCollection, traverse the sessions array backwards. Maintain a hash of all userID's seen so far, so if a repeated userID comes along, that can be ignored. Once you've collected N such user sessions or reached the beginning of the array, return all collected sessions.
usrSessionCollection.getLast = function(n) {
var sessions = this.getAll();
var uniqueSessions = [];
var addedUserIDs = {}, session, count, userID;
for(var i = sessions.length - 1; i >= 0, uniqueSessions.length < n; i--) {
session = sessions[i];
userID = session.userID;
if(!addedUserIDs[userID]) {
uniqueSessions.push(session);
addedUserIDs[userID] = true;
}
}
return uniqueSessions;
}
I wouldn't combine the delete step with the traversal step, just to keep things simple. So here's the remove method that removes the given session from the array. Again, it's better to modify the interface returned by ArrayCollection rather than tampering with the sessions array directly.
function ArrayCollection(..) {
return {
..,
remove: function(item) {
for(var i = 0; i < myArray.length; i++) {
if(item == myArray[i]) {
return myArray.splice(i, 1);
}
}
return null;
}
};
}
Example: Get the last 10 unique sessions and delete them:
var sessions = usrSessionCollection.getLast(10);
for(var i = 0; i < sessions.length; i++) {
console.log(sessions[i].UserID); // don't need dummy variable, log directly
usrSessionCollection.remove(sessions[i]);
}
See a working example.

You made your array private, so you can't access the data, except adding a new element or removing them all. You need to make the array public, or provide a public interface to access the data. Like first(), next() or item(index).
Then you can add a search(userID) method to the usrSessionCollection, which uses this interface to go through the elements and search by userID.
UPDATE: this is how I would do it: - See it in action. (click preview)
// user session
function userSession(userID, cords, color) {
this.UserID = userID;
this.Cords = cords;
this.Color = color;
}
// a collection of user sessionions
// a decorated array basically, with
// tons of great methods available
var userSessionCollection = Array;
userSessionCollection.prototype.lastById = function( userID ) {
for ( var i = this.length; i--; ) {
if ( this[i].UserID === userID ) {
return this[i];
}
}
// NOTE: returns undefined by default
// which is good. means: no match
};
// we can have aliases for basic functions
userSessionCollection.prototype.add = Array.prototype.push;
// or make new ones
userSessionCollection.prototype.empty = function() {
return this.splice(0, this.length);
};
//////////////////////////////////////////////////////
// make a new collection
var coll = new userSessionCollection();
// put elements in (push and add are also available)
coll.add ( new userSession(134, [112, 443], "#fffff") );
coll.push( new userSession(23, [32, -32], "#fe233") );
coll.push( new userSession(324, [1, 53], "#ddddd") );
// search by id (custom method)
var search = coll.lastById(134);
if( search ) {
console.log(search.UserID);
} else {
console.log("there is no match");
}
// empty and search again
coll.empty();
search = coll.lastById(134);
if( search ) {
console.log(search.UserID);
} else {
console.log("there is no match");
}

Related

JavaScript - Issues recovering a map in an object after being saved in localStorage

I've been dealing with this for some time. I've a list of sections in which the user checks some checkboxes and that is sent to the server via AJAX. However, since the user can return to previous sections, I'm using some objects of mine to store some things the user has done (if he/she already finished working in that section, which checkboxes checked, etc). I'm doing this to not overload the database and only send new requests to store information if the user effectively changes a previous checkbox, not if he just starts clicking "Save" randomly. I'm using objects to see the sections of the page, and storing the previous state of the checkboxes in a Map. Here's my "supervisor":
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
var children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children().length;
for (var i = 0; i < children; i++) {
if (i % 2 == 0) {
var checkbox = $("#ContentPlaceHolder1_checkboxes_div_" + id).children()[i];
var idCheck = checkbox.id.split("_")[2];
this.selections.set(idCheck, false);
}
}
console.log("Length " + this.selections.size);
this.change = false;
}
The console.log gives me the expected output, so I assume my Map is created and initialized correctly. Since the session of the user can expire before he finishes his work, or he can close his browser by accident, I'm storing this object using local storage, so I can change the page accordingly to what he has done should anything happen. Here are my functions:
function setObj(id, supervisor) {
localStorage.setItem(id, JSON.stringify(supervisor));
}
function getObj(key) {
var supervisor = JSON.parse(localStorage.getItem(key));
return supervisor;
}
So, I'm trying to add to the record whenever an user clicks in a checkbox. And this is where the problem happens. Here's the function:
function checkboxClicked(idCbx) {
var idSection = $("#ContentPlaceHolder1_hdnActualField").val();
var supervisor = getObj(idSection);
console.log(typeof (supervisor)); //Returns object, everythings fine
console.log(typeof (supervisor.change)); //Returns boolean
supervisor.change = true;
var idCheck = idCbx.split("_")[2]; //I just want a part of the name
console.log(typeof(supervisor.selections)); //Prints object
console.log("Length " + supervisor.selections.size); //Undefined!
supervisor.selections.set(idCheck, true); //Error! Note: The true is just for testing purposes
setObj(idSection, supervisor);
}
What am I doing wrong? Thanks!
Please look at this example, I removed the jquery id discovery for clarity. You'll need to adapt this to meet your needs but it should get you mostly there.
const mapToJSON = (map) => [...map];
const mapFromJSON = (json) => new Map(json);
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
this.change = false;
this.selections.set('blah', 'hello');
}
Supervisor.from = function (data) {
const id = data.id;
const supervisor = new Supervisor(id);
supervisor.verif = data.verif;
supervisor.selections = new Map(data.selections);
return supervisor;
};
Supervisor.prototype.toJSON = function() {
return {
id: this.id,
verif: this.verif,
selections: mapToJSON(this.selections)
}
}
const expected = new Supervisor(1);
console.log(expected);
const json = JSON.stringify(expected);
const actual = Supervisor.from(JSON.parse(json));
console.log(actual);
If you cant use the spread operation in 'mapToJSON' you could loop and push.
const mapToJSON = (map) => {
const result = [];
for (let entry of map.entries()) {
result.push(entry);
}
return result;
}
Really the only thing id change is have the constructor do less, just accept values, assign with minimal fiddling, and have a factory query the dom and populate the constructor with values. Maybe something like fromDOM() or something. This will make Supervisor more flexible and easier to test.
function Supervisor(options) {
this.id = options.id;
this.verif = null;
this.selections = options.selections || new Map();
this.change = false;
}
Supervisor.fromDOM = function(id) {
const selections = new Map();
const children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children();
for (var i = 0; i < children.length; i++) {
if (i % 2 == 0) {
var checkbox = children[i];
var idCheck = checkbox.id.split("_")[2];
selections.set(idCheck, false);
}
}
return new Supervisor({ id: id, selections: selections });
};
console.log(Supervisor.fromDOM(2));
You can keep going and have another method that tries to parse a Supervisor from localStorageand default to the dom based factory if the localStorage one returns null.

How to update JavaScript array dynamically

I have an empty javascript array(matrix) that I created to achieve refresh of divs. I created a function to dynamically put data in it. Then I created a function to update the Array (which I have issues).
The Data populated in the Array are data attributes that I put in a JSON file.
To better undertand, here are my data attributes which i put in json file:
var currentAge = $(this).data("age");
var currentDate = $(this).data("date");
var currentFullName = $(this).data("fullname");
var currentIDPerson = $(this).data("idPerson");
var currentGender = $(this).data("gender");
Creation of the array:
var arrayData = [];
Here is the function a created to initiate and addind element to the Array :
function initMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
var isFound = false;
// search if the unique index match the ID of the HTML one
for (var i = 0; i < arrayData.length; i++) {
if(arrayData[i].idPerson== p_currentIDPerson) {
isFound = true;
}
}
// If it doesn't exist we add elements
if(isFound == false) {
var tempArray = [
{
currentIDPerson: p_currentIDPerson,
currentGender: p_currentGender,
currentFullName: p_currentFullName,
currentDate: p_currentDate, currentAge: p_currentAge
}
];
arrayData.push(tempArray);
}
}
The update function here is what I tried, but it doesn't work, maybe I'm not coding it the right way. If you can help please.
function updateMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
for (var i = 0; i < arguments.length; i++) {
for (var key in arguments[i]) {
arrayData[i] = arguments[i][key];
}
}
}
To understand the '$this' and elm: elm is the clickableDivs where I put click event:
(function( $ ) {
// Plugin to manage clickable divs
$.fn.infoClickable = function() {
this.each(function() {
var elm = $( this );
//Call init function
initMatrixRefresh(elm.attr("idPerson"), elm.data("gender"), elm.data("fullname"), elm.data("date"), elm.data("age"));
//call function update
updateMatrix("idTest", "Alarme", "none", "10-02-17 08:20", 10);
// Définition de l'evenement click
elm.on("click", function(){});
});
}
$('.clickableDiv').infoClickable();
}( jQuery ));
Thank you in advance
Well... I would recommend you to use an object in which each key is a person id for keeping this list, instead of an array. This way you can write cleaner code that achieves the same results but with improved performance. For example:
var myDataCollection = {};
function initMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
if (!myDataCollection[p_currentIDPerson]) {
myDataCollection[p_currentIDPerson] = {
currentIDPerson: p_currentIDPerson,
currentGender: p_currentGender,
currentFullName: p_currentFullName,
currentDate: p_currentDate,
currentAge: p_currentAge
};
}
}
function updateMatrix(p_currentIDPerson, p_currentGender, p_currentFullName, p_currentDate, p_currentAge) {
if (myDataCollection[p_currentIDPerson]) {
myDataCollection[p_currentIDPerson] = {
currentGender: p_currentGender,
currentFullName: p_currentFullName,
currentDate: p_currentDate,
currentAge: p_currentAge
};
}
}
Depending on your business logic, you can remove the if statements and keep only one function that adds the object when there is no object with the specified id and updates the object when there is one.
I think the shape of the resulting matrix is different than you think. Specifically, the matrix after init looks like [ [ {id, ...} ] ]. Your update function isn't looping enough. It seems like you are trying to create a data structure for storing and updating a list of users. I would recommend a flat list or an object indexed by userID since thats your lookup.
var userStorage = {}
// add/update users
userStorage[id] = {id:u_id};
// list of users
var users = Object.keys(users);

Javascript object to array, length = 0

I'm building a webshop where users are able to add products for one of more stores in their basket and checkout (like AliExpress).
On the cart overview page, the content of the basket is shown sorted by store. If the same product is added multiple times over different stores, the product is show by every store.
Now, I want to create an order for every store with the products ordered by that store. I'm using Angular to create the list with products ordered/filtered by store.
That data will be sent to my Node.JS server, to loop the contents and create some orders with items.
The problem, I think, is that the data is processed like a 'object' and not an 'array'. I have found a function which converts a object to an array, but the length is still '0'.
How can I process the data so I can loop through the different items?
AngularJS code to sort cart by store
$scope.filterProducts = function(groupName) {
$scope.productList = [];
$http({
method: 'GET',
xhrFields: {withCredentials: true},
url: '/loadCart'
}).then(function successCallback(response){
if (response.data) {
var mapByShop = function(arr, groupName) {
return arr.reduce(function(result, item) {
result[item[groupName]] = result[item[groupName]] || {};
result[item[groupName]][item['productId']] = item;
console.log('GROUPNAME en RESULT', groupName, result);
return result;
}, {});
};
if (response.data.length > 0) {
if (groupName == 'shopName') {
$scope.productList = mapByShop(response.data, groupName);
} else {
$scope.checkoutList = mapByShop(response.data, groupName);
}
}
}
}, function errorCallback(response){
console.log(response);
});
}
The $scope.productList is sent as 'data' in a $http POST function.
Node.JS code to convert an object to an array
function convertObjectToArray(object, cb){
var cartContent = [];
for (var i in object) {
cartContent[i] = object[i];
}
console.log("convertObjectToArray");
return cb(cartContent);
}
Code to process the data (where length is zero)
convertObjectToArray(req.body.cart, function(result){
console.log(isArray(result));
console.log('result', result);
console.log("lenght", result.length);
})
FYI: the isArray function
function isArray(myArray) {
return myArray.constructor.toString().indexOf("Array") > -1;
}
if array order is not important, you should use
cartContent.push(object[i]);
It will update the .length property automaticly
Your problem is that you are adding properties to the array object, and not using the Array API to insert at integer locations in the object. This means the array essentially remains "empty". If you key on an integer when inserting into the array then your code will work better.
The broken bit:
for (var i in object) {
cartContent[i] = object[i];
}
i is a string key here and will not increment the length of the Array unless the value coerces to an integer value (I think).
Something like this might work:
// Untested...
var keys = Object.keys(object);
for (var i = 0; i < keys.length; i++) {
cartContent[i] = object[keys[i]];
}
Or like the other answer suggested, use the push API.
Aside:
If you are in a modern JS engine you can:
Use Object.values, or
Write a couple of utility functions and convert an object to an array using the following
:
var o = iterable({foo:'fooValue', bar: 'barValue'});
console.log([...o]);
The utility functions:
function iterable(o) {
if(o[Symbol.iterator]) {
return o;
}
o[Symbol.iterator] = iter.bind(null, o);
return o;
}
function* iter(o) {
var keys = Object.keys(o);
for (var i=0; i<keys.length; i++) {
yield o[keys[i]];
}
}

Storin a variable with many id's in array

var arrayOfId[] = $scope.rep.Selected.id;
I have this code. My question is if I had a some id's in $scope...Selected.id and then I make this (code) all these id's will be in every other array or in one array?
$scope.delIt = function () {
if ($scope.rep.Selected > 0) {
$scope.rep.Selected = true;
} else {
$scope.rep.Selected = false
}
var arrayOfId [] = $scope.rep.Selected.id;
for(i=0; i<=arrayOfId[i]; i++){
$http.delete("http://localhost:3000/users/" +arrayOfId[]);
}
}
This is full code. I wanted to send request every single id wich is stored in $scope.rep.Selected.id

return from JS function

basic JS question, please go easy on me I'm a newb :)
I pass 2 variables to the findRelatedRecords function which queries other related tables and assembles an Array of Objects, called data. Since findRelatedRecords has so many inner functions, I'm having a hard time getting the data Array out of the function.
As it currently is, I call showWin inside findRelatedRecords, but I'd like to change it so that I can get data Array directly out of findRelatedRecords, and not jump to showWin
function findRelatedRecords(features,evtObj){
//first relationship query to find related branches
var selFeat = features
var featObjId = selFeat[0].attributes.OBJECTID_1
var relatedBranch = new esri.tasks.RelationshipQuery();
relatedBranch.outFields = ["*"];
relatedBranch.relationshipId = 1; //fac -to- Branch
relatedBranch.objectIds = [featObjId];
facSel.queryRelatedFeatures(relatedBranch, function(relatedBranches) {
var branchFound = false;
if(relatedBranches.hasOwnProperty(featObjId) == true){
branchFound = true;
var branchSet = relatedBranches[featObjId]
var cmdBranch = dojo.map(branchSet.features, function(feature){
return feature.attributes;
})
}
//regardless of whether a branch is found or not, we have to run the cmdMain relationship query
//the parent is still fac, no advantage of the parent being branch since cmcMain query has to be run regardless
//fac - branch - cmdMain - cmdSub <--sometimes
//fac - cmdMain - cmdSub <-- sometimes
//second relationship query to find related cmdMains
var relatedQuery = new esri.tasks.RelationshipQuery();
relatedQuery.outFields = ["*"];
relatedQuery.relationshipId = 0; //fac -to- cmdMain
relatedQuery.objectIds = [featObjId];
//rather then listen for "OnSelectionComplete" we are using the queryRelatedFeatures callback function
facSel.queryRelatedFeatures(relatedQuery, function(relatedRecords) {
var data = []
//if any cmdMain records were found, relatedRecords object will have a property = to the OBJECTID of the clicked feature
//i.e. if cmdMain records are found, true will be returned; and continue with finding cmdSub records
if(relatedRecords.hasOwnProperty(featObjId) == true){
var fset = relatedRecords[featObjId]
var cmdMain = dojo.map(fset.features, function(feature) {
return feature.attributes;
})
//we need to fill an array with the objectids of the returned cmdMain records
//the length of this list == total number of mainCmd records returned for the clicked facility
objs = []
for (var k in cmdMain){
var o = cmdMain[k];
objs.push(o.OBJECTID)
}
//third relationship query to find records related to cmdMain (cmdSub)
var subQuery = new esri.tasks.RelationshipQuery();
subQuery.outFields = ["*"];
subQuery.relationshipId = 2;
subQuery.objectIds = [objs]
subTbl.queryRelatedFeatures(subQuery, function (subRecords){
//subRecords is an object where each property is the objectid of a cmdMain record
//if a cmdRecord objectid is present in subRecords property, cmdMain has sub records
//we no longer need these objectids, so we'll remove them and put the array into cmdsub
var cmdSub = []
for (id in subRecords){
dojo.forEach(subRecords[id].features, function(rec){
cmdSub.push(rec.attributes)
})
}
var j = cmdSub.length;
var p;
var sub_key;
var obj;
if (branchFound == true){
var p1 = "branch";
obj1 = {};
obj1[p1] = [cmdBranch[0].Branches]
data.push(obj1)
}
for (var i=0, iLen = cmdMain.length; i<iLen; i++) {
p = cmdMain[i].ASGMT_Name
obj = {};
obj[p] = [];
sub_key = cmdMain[i].sub_key;
for (var j=0, jLen=cmdSub.length; j<jLen; j++) {
if (cmdSub[j].sub_key == sub_key) {
obj[p].push(cmdSub[j].Long_Name);
}
}
data.push(obj);
}
showWin(data,evtObj) <---this would go away
})
}
//no returned cmdRecords; cmdData not available
else{
p = "No Data Available"
obj = {}
obj[p] = []
data.push(obj)
}
showWin(data,evtObj) <--this would go away
})
})
}
I'd like to have access to data array simply by calling
function findRelatedRecords(feature,evt){
//code pasted above
}
function newfunct(){
var newData = findRelatedRecords(feature,evt)
console.log(newData)
}
is this possible?
thanks!
Edit
Little more explanation.....
I'm connecting an Object event Listener to a Function like so:
function b (input){
dojo.connect(obj, "onQueryRelatedFeaturesComplete", getData);
obj.queryRelatedFeatures(input);
console.log(arr) //<----this doesn't work
}
function getData(relatedFeatData){
var arr = [];
//populate arr
return arr;
}
So when obj.QueryRelatedFeatures() is complete, getData fires; this part works fine, but how to I access arr from function b ?
Post Edit Update:
Due to the way that this event is being hooked up you can't simple return data from it. Returning will just let Dojo call to the next method that is hooked up to onSelectionComplete.
When init runs it is long before findRelatedRecords will ever be executed/fired by the onSelectionComplete event of the well, which is why you were seeing undefined/null values. The only way to work with this sort of system is to either 1) call off to a method like you're already doing or 2) fire off a custom event/message (technically it's still just calling off to a method).
If you want to make this method easier to work with you should refactor/extract snippets of it to make it a smaller function but contained in many functions. Also, changing it to have only one exit point at the end of the findRelatedRecords method will help. The function defined inside of subTbl.queryRelatedFeatures() would be a great place to start.
Sorry, you're kind of limited by what Dojo gives you in this case.
Pre Edit Answer:
Just return your data out of it. Everywhere where there is a showWin call just use this return.
return {
data: data,
evtObj: evtObj
}
Then your newfunct would look like this.
function newfunct(){
var newData = findRelatedRecords(feature,evt);
console.log(newData);
console.log(newData.data);
console.log(newData.evtObj);
}
If you only need that "data" object, then change your return to just return data;.
Also, start using semicolons to terminate statements.

Categories

Resources