I have a large object, mixed with arrays of data (it's a treeview of folders and images - I have no control over what is outputted here.
For example:
var test = {
Folders: [{
Folders:[{
Folders:[{
Folders:[
{value:1},{value:2}
]
}]
}]
},{}
]
}
Value 1 (which in my case is an image) can be found here:
test.Folders[0].Folders[0].Folders[0].Folders[0].value
My end users are using a drop down to select their folder, I need to somehow pass the location via the drop down.
I've tried adding the "path" to the data-attr attribute of the drop down:
<option value="folder6" data-attr="[0].Folders[0].Folders[0].Folders[0]">Folder6</option>
However attempting to use that like this:
var myLocation = $('#element').find('option:selected').attr('data-attr');
//myLocation now is a string "Folders[0].Folders[0].Folders[0].Folders[0].value"
console.log(test[myLocation]
Doesn't work (it's down to the Arrays and numbers as passing a string as an object location in this fashion normally works).
I'm quietly confident I'm going about this the wrong way fullstop. I'm open to ideas on how better to do this in general, or how to get this horrible fudge to work.
There are many ways to solve this issue and it mostly depends on your needs and global architecture.
#1 The Evil Way (using eval)
var path = '[0].Folders[0].Folders[0].Folders[0]';
eval('test.Folders' + path); //Object {value: 1}
#2 Use an object as a map to index every folder
var foldersMap = {};
//loop over your tree and build the index
foldersMap[path] = folder;
//then retrieve it later
foldersMap[path];
#3 Store the object on the option directly
//while building the option
optionEl.folder = folder;
//then later retrieve it from the selected option
yourSelect.options[yourSelect.selectedIndex].folder;
#4 Create your own keypath function that can traverse an object structure based on a string keypath rather than using eval. I will provide an implementation as soon as I have more time.
There are probably many other ways, but these are just ideas.
Conceptually a tree can be n-levels deep. Baking in the path like that may not be a good ideas unless you know how "deep" the structure will be ahead of time.
It looks like you might be trying to show each potential folder in a drop down list (or maybe just the ones with images).
To do this, I would create a function to recursively loop through your json structure. The result of this function would either directly build the drop down menu or flatten the values into an array which could then be bound to the drop down menu. I would use a front-end templating library like handlebar.js, but you could also manipulate a dropdown control via JQuery.
Try storing it as a JSON array in data-attr. jQuery will pull it out as an array. Then you just need to find a way to loop it. Here's my first crack at it (with no validation and exception handling)...
HTML:
<select>
<option data-attr="[0,0,0,0]">Foo</option>
</select>
JavaScript:
var test = {
Folders: [{
Folders: [{
Folders: [{
Folders: [{
value: 1
}, {
value: 2
}]
}]
}]
}, {}]
};
var folderIndexes = $("option").data("attr"); // an array
alert(getValue(test, folderIndexes));
function getValue(folders, folderIndexes) {
for (var i = 0; i < folderIndexes.length; i++) {
folders = folders.Folders[folderIndexes[i]];
}
return folders.value;
}
Fiddle... http://jsfiddle.net/atn22/
Related
I am following ag-grid's official tutorial:
https://www.ag-grid.com/angular-getting-started/?utm_source=ag-grid-readme&utm_medium=repository&utm_campaign=github
I reached the part where I have to manipulate the information regarding the selected checkboxes. However, the documentation is not detailed; It does not explain how the code actually works. Perhaps, this makes sense since it is not their job to explain in detail. However, for someone like me who doesn't have solid experience with working with angular technology and who wants to truly understand how things work, this is troublesome!
In the html file, I have been asked to add this:
<button (click)="getSelectedRows()">Get Selected Rows</button>
And in app.component.ts, I have been asked to add this:
getSelectedRows() {
const selectedNodes = this.agGrid.api.getSelectedNodes();
const selectedData = selectedNodes.map(node => node.data);
const selectedDataStringPresentation = selectedData.map( node => node.make + ' ' + node.model).join(', ');
alert('Selected nodes: ${selectedDataStringPresentation}');
}
If someone could explain what the typescript code is doing exactly, that would be very generous.
Thank you!
I guess agGrid is the service storing your mock values, this simply gets an array of data from somwhere.
selectedData is another array that is created by transforming (transforms the array while providing a new reference, thus not modifying the original array) the selectedNodes array and only selecting the data property of each node.
selectedDataStringPresentation is the same, but this time it provides an array of formatted strings by merging the properties make and model of each object from selectedData.
What you probably fail to grasp is the usage of the ES6 (JavaScript standard) functions that are used here, and especially the map() function.
Here is the official documentation of the map() function for arrays : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
Simply explained, it is a function that iterates over an array, and transforming each object by applying the function declared in the map, returning the results in a new array.
If you take the example of selectedData, you can translate the operation into this :
Loop over each object of selectedNodes, and return only the property data of the current object. Push the results in a new array.
This is especially useful when you want to work on different arrays that serve different purposes. For example, imagine you have a service that contains a list of objects. A backend service will provide you an array of numbers representing the IDs of the objects you have in your service.
You can then use the map() function to transform the array of IDs into an array of your Objects stored in your service easily.
Darn #Alex Beugnet(upvote) beat me to the punch! I'm going to post anyway as I was in the middle of writing it all.
Firstly I'm not sure how much of TypeScript you already know, I apologize if much of these becomes trivial, the purpose is only to ensure maximum clarification to the question in understanding the logic.
In the Enable Selection portion of the guide, you are essentially enabling multiple row selection in the grid and having the button return the data from those selected rows.
In order to see what's happening with the getMultipleRows() function, it would be best to visualize it via the Debugger provided in browsers, I'm using Chrome Developer Tools (hit F12), I would highly recommend it for understanding what is happening in the logic.
const selectedNodes
Let's start by selecting say 2 rows, I have selected the Porsche Boxster 72000 and Ford Mondeo 32000. After selecting them I click on the 'Get Selected Rows' button which triggers the getSelectedRows() function:
const selectedNodes = this.agGrid.api.getSelectedNodes();
The above line is assigning the constant variable 'selectedNodes' the RowNodes from AgGrid. Here you are using the AgGridNg2 method getSelectedNodes() to return the selected node data, which you would be returned an array in the form of:
[RowNode, RowNode] //(each for the row we have selected)
Looking into a RowNode we get:
These are all the RowNode properties provided by the AgGrid framework, you can ignore all of these object properties as you are only concerned with the 'data' property as you'll see in the next line of code!
const SelectedData
const selectedData = selectedNodes.map(node => node.data);
Here we are setting 'selectedData' as an array of RowNode.data, basically trying to get the data property from the RowNodes into an array.
The above line can basically be assumed as:
let selectedData = [];
for (let i = 0; i <= selectedNodes.length - 1; i++){
selectedData[i] = selectedNodes[i].data;
}
In which we are just trying to get the data property into a new constant variable 'selectedData'. Look at the documentation in order to better understand this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const selectedData would be returned as:
[
{
make: "Porsche",
model: "Boxster",
price: 72000,
},
{
make: "Ford",
model: "Mondeo",
price: 32000
}
]
const selectedDataStringPresentation
const selectedDataStringPresentation = selectedData.map( node => node.make + ' ' + node.model).join(', ');
We take the selectedData array and concatenate the Make and Model as a single element for the array and adding a comma in the end. We would get "Porsche Boxter, Ford Mondeo".
let selectedData = [
{
make: "Porsche",
model: "Boxster",
price: 72000,
},
{
make: "Ford",
model: "Mondeo",
price: 32000
}
]
let selectedDataStringPresentation = [];
for (let i = 0; i <= selectedData.length - 1; i++){
selectedDataStringPresentation[i] = selectedData[i].make + ' ' + selectedData[i].model;
}
selectedDataStringPresentation = selectedDataStringPresentation.join(', ');
console.log(selectedDataStringPresentation);
alert()
And the last line,
alert('Selected nodes: ${selectedDataStringPresentation}');
You are going to send an alert in the browser that will display the selectedDataStringPresentation array.
I am working on a nested js application that is attempting to create a clean json schema - from more raw data.
So as I loop through the data - I want to pick up the label value which is nested like this.
label: data[x]["title"]["values"]["en"]
I want to create in the parent a pointer - to this data so
parentLabelPointer : "title.values.en"
but I can not simply do a get of this variable (using ember) like this
label: data[x][this.get("parentLabelPointer")]
if it was just one level - this would work. e.b. "parentLabelPointer : "title""
is there a clean way to pick this data up -- without having to try and drill down to the data in nested arrays like ["title"]["values"]["en"]?
this is so I can make the module more modular -- if its to work with different data sets and different nesting levels.
my example
getLabel: function(prefix, pointer){
var trail = pointer.split(".");
var label = prefix;
trail.forEach(function(element) {
label = label[element];
});
return label;
}
usage
this.getLabel(data[x], this.get('parentLabelPointer'))
data[x] is like the known level of the branch -- but to find the label in the raw data --
parentLabelPointer - is like "title.values.en"
I think you want something like this:
import { computed } from '#ember/object';
...
label:computed('parentLabelPointer', 'data.[]', 'x', function(){
let x = this.get('x');
let data = this.get('data');
let parentLabelPointer = this.get('parentLabelPointer');
return data[x].get(parentLabelPointer);
}
That all said it's pretty unclear what x, data, etc is or how they're supposed to work, etc. This does feels like an XY problem
This is the JSON I'm working with:
https://data.cityofnewyork.us/resource/xx67-kt59.json?$where=camis%20=%2230112340%22
I'd be dynamically making the queries using different data, so it'll possibly change.
What I'm essentially trying to do is to somehow organize the elements within this array into different arrays based on inspection_date.
So for each unique inspection_date value, those respective inspections would be put into its own collection.
If I knew the dates beforehand, I could easily iterate through each element and just push into an array.
Is there a way to dynamically create the arrays?
My end goal is to be able to display each group of inspections (based on inspection date) using Angular 5 on a webpage. I already have the site up and working and all of the requests being made.
So, I'm trying to eventually get to something like this. But of course, using whatever dates in the response from the request.
2016-10-03T00:00:00
List the inspections
2016-04-30T00:00:00
List the inspections
2016-04-12T00:00:00
List the inspections
Just for reference, here's the code I'm using:
ngOnInit() {
this.route.params.subscribe(params => {
this.title = +params['camis']; // (+) converts string 'id' to a number
this.q.getInpectionsPerCamis(this.title).subscribe((res) => {
this.inspectionList = res;
console.log(res);
});
// In a real app: dispatch action to load the details here.
});
}
I wish I could give you more info, but at this point, I'm just trying to get started.
I wrote this in jQuery just because it was faster for me, but it should translate fairly well to Angular (I just don't want to fiddle with an angular app right now)
Let me know if you have any questions.
$(function() {
let byDateObj = {};
$.ajax({
url: 'https://data.cityofnewyork.us/resource/xx67-kt59.json?$where=camis%20=%2230112340%22'
}).then(function(data) {
//probably do a check to make sure the data is an array, im gonna skip that
byDateObj = data.reduce(function(cum, cur) {
if (!cum.hasOwnProperty(cur.inspection_date)) cum[cur.inspection_date] = [];
//if the cumulative array doesn't have the inspection property already, add it as an empty array
cum[cur.inspection_date].push(cur);
//push to inspection_date array.
return cum;
//return cumulatie object
}, byDateObj);
//start with an empty object by default;
console.log(byDateObj);
}, console.error);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I'm just getting started with JavaScript objects. I'm trying to store catalog inventory by id and locations. I think a single object would look something like this:
var item = {
id: number,
locations: ["location1", "location2"]
};
I've started to read a bit about it but am still trying to wrap my head around it. Not sure what is the fastest way add new items to a list with a location, add a new location to an existing item, all while checking for dupes. Performance of getting the locations later isn't as critical. This is part of a process that is running thousands of checks to eventually get items by id and location, so performance is key.
Final question, I'm not even sure if it's possible to store this in local storage. From another similar question, I'm not sure.
Using lodash, something like this should work to determine if an item id exists and append either a new item to the array, or just add a new location:
var item = [{
id: 1,
locations: ["location1", "location2"]
},{
id: 2,
locations: ["location2", "location4"]
}];
function findItem(id){
return _.findIndex(item, function(chr) {
return chr.id == id;
});
}
function addItem(id,locations) {
var position = findItem(id);
if (position<0) {
item.push({
id: id,
locations: locations
})
} else {
item[position].locations = _.uniq(item[position].locations.concat(locations));
}
}
addItem(2,['location292']);
addItem(3,['location23']);
console.log(item);
What it basically does is to search the array of objects (item) for an id as the one we are passing to the addItem() function, if it is found we add the new locations array to the existing item, if not it's creating a new object with a new id and location.
You've asked a question that contains some tradeoffs:
The simplest and fastest way to retrieve a list of locations is to store them in an array.
The fastest way to check something for a duplicates is not an array, but rather a map object that maintains an index of the key.
So, you'd have to discuss more about which set of tradeoffs you want. Do you want to optimize for performance of adding a non-duplicate or optimize for performance of retrieving the list of locations. Pick one or the other.
As for localStorage, you can store any string in LocalStorage and you can convert simply non-reference objects to a string with JSON.stringify(), so yes this type of structure can be stored in LocalStorage.
For example, if you want to use the array for optimized retrieval, then you can check for duplicates like this before adding:
function addLocation(item, newLocation) {
if (item.locations.indexOf(newLocation) === -1) {
item.locations.push(newLocation);
}
}
Also, you can store an array of items in LocalStorage like this:
localStorage.setItem("someKey", JSON.stringify(arrayOfItems));
And, then some time later, you can retrieve it like this:
var arrayOfItems = JSON.parse(localStorage.getItem("someKey"));
Prepending that a solution only needs to work in the latest versions of Chrome, Firefox, and Safari as a bonus.
-
I am trying to use an associative array for a large data set with knockout. My first try made it a true associative array:
[1: {Object}, 3: {Object},...,n:{Object}]
but knockout was not happy with looping over that. So I tried a cheating way, hoping that:
[undefined, {Object}, undefined, {Object},...,{Object}]
where the location in the array is the PK ID from the database table. This array is about 3.2k items large, and would be iterated over around every 10 seconds, hence the need for speed. I tried doing this with a splice, e.g.
$.each(data, function (index, item) {
self.myArray.splice(item.PKID, 0, new Object(item));
}
but splice does not create indices, so since my first PKID is 1, it is still inserted at myArray[0] regardless. If my first PK was 500, it would start at 0 still.
My second thought is to initialize the array with var myArray = new Array(maxSize) but that seems heavy handed. I would love to be able to use some sort of map function to do this, but I'm not really sure how to make the key value translate into an index value in javascript.
My third thought was to keep two arrays, one for easy look up and the other to store the actual values. So it combines the first two solutions, almost, by finding the index of the object in the first example and doing a lookup with that in the second example. This seems to be how many people manage associative arrays in knockout, but with the array size and the fact that it's a live updating app with a growing data set seems memory intensive and not easily manageable when new information is added.
Also, maybe I'm hitting the mark wrong here? We're putting these into the DOM via knockout and managing with a library called isotope, and as I mentioned it updates about every 10 seconds. That's why I need the fast look up but knockout doesn't want to play with my hash table attempts.
--
clarity edits:
so on initial load the whole array is loaded up (which is where the new Array(maxLength) would go, then every 10 seconds anything that has changed is loaded back. That is the information I'm trying to quickly update.
--
knockout code:
<!-- ko foreach: {data: myArray(), afterRender: setInitialTileColor } -->
<div class="tile" data-bind="attr: {id: 'tileID' + $data.PKID()}">
<div class="content">
</div>
</div>
<!-- /ko -->
Then on updates the hope is:
$.each(data.Updated, function (index, item) {
var obj = myModel.myArray()[item.PKID];
//do updates here - need to check what kind of change, how long it's been since a change, etc
}
Here is a solution how to populate array items with correct indexes, so it doesn't start from the first one (0 (zero) I meant)
just use in loop
arr[obj.PKID] = obj;
and if your framework is smart (to use forEach but not for) it will start from your index (like 500 in case below)
http://jsfiddle.net/0axo9Lgp/
var data = [], new_data = [];
// Generate sample array of objects with index field
for (var i = 500; i < 3700; i++) {
data.push({
PKID: i,
value: '1'
});
}
data.forEach(function(item) {
new_data[item.PKID] = item;
});
console.log(new_data);
console.log(new_data.length); // 3700 but real length is 3200 other items are undefined
It's not an easy problem to solve. I'm assuming you've tried (or can't try) the obvious stuff like reducing the number of items per page and possibly using a different framework like React or Mithril.
There are a couple of basic optimizations I can suggest.
Don't use the framework's each. It's either slower than or same as the native Array method forEach, either way it's slower than a basic for loop.
Don't loop over the array over and over again looking for every item whose data has been updated. When you send your response of data updates, send along an array of the PKIds of the updated item. Then, do a single loop:
.
var indexes = []
var updated = JSON.parse(response).updated; // example array of updated pkids.
for(var i=0;i<allElements.length;i++){
if(updated.indexOf(allElements[i].pkid)>-1)
indexes.push(i);
}
So, basically the above assumes you have a simple array of objects, where each object has a property called pkid that stores its ID. When you get a response, you loop over this array once, storing the indexes of all items that match a pk-id in the array of updated pk-ids.
Then you only have to loop over the indexes array and use its elements as indexes on the allElements array to apply the direct updates.
If your indexes are integers in a reasonable range, you can just use an array. It does not have to be completely populated, you can use the if binding to filter out unused entries.
Applying updates is just a matter of indexing the array.
http://jsfiddle.net/0axo9Lgp/2/
You may want to consider using the publish-subscribe pattern. Have each item subscribe to its unique ID. When an item needs updating it will get the event and update itself. This library may be helpful for this. It doesn't depend upon browser events, just arrays so it should be fairly fast.