Enumerate JSON master detail arrays - javascript

I have a view model that contains 2 related (master / detail) arrays that I want to bind to.
var ViewModel = function (data) {
var self = this;
self.CategoryList = ko.observableArray(data.CategoryList);
self.ItemList = ko.observableArray(data.ItemList);
}
Where Category is an array of { categoryID: 1, name: "Red Items" } etc
and Item is an array of { itemID: 1, categoryID: 1, name: "Red Widget" } etc
I have a handful of categories and possibly several hundred items. I am struggling with how to represent these in my view model in a way that I can enumerate categories and get to associated items or across all items and get to categories.
It doesn't seem efficient to maintain duplicate information on both lists and I don't want to be sending more data than needed over the wire.
One approach: create Item and Category objects and modify the prototype to include filtering (ko.computed) functions. category.Items() or item.Category().Name but I am not 100% how to best accomplish this within knockout.js given my need to load and save data.
I am not wed to arrays of simple JSON objects. Assuming I go the function / prototype route, how do I load and save my arrays of objects?

I think that you require a better data structure than an array, since -as you said- it's not optimal to keep duplicate information. Instead of having an array listing your categories, why don't use a hash table, or as we simply know in javascript, an object?
{
1: "Red Items",
2: "Blue Items",
3: "Green Items",
4: "Yellow Items",
}
(You can use a simple method to create the hash from the array)
var createHashFromArray = function(arr) {
var hash = {}
for (var i = 0, var len = arr.length; i < len; i++)
hash[i] = arr[i];
return hash;
}
From there, you can bind your viewModel according to the categories you need. You can even set up them as dynamic arrays using ko.computed in order to organize them in your view.
<div data-bind="foreach: redItemsOnly">
<span data-bind="text: itemId">ID</span>
<span data-bind="text: name">NAME</span>
</div>
And in your javascript...
self.redItemsOnly = ko.computed(function() {
return ko.utils.arrayFilter(self.ItemList(), function(item)
{
return self.CategoryHash[item.categoryID] === "Red Items"
});
}, self)
I'm not sure if this is what you want, but I hope this approach helps you to find other ways to come up with what you need.

Related

Custom filtering / ordering with ng-table and selectize

One of my ng-table columns contains a selectize dropdown. The value of this column is a foreign key for another table. Instead of displaying this key, I wish to display another attribute (name) of the row this key represents.
My data is in an array called table.
I have the dropdown displaying correctly using another array, fkTable.
<selectize ng-model="row[col.name]"
options='fkTable'
config='fkConfig'">
where fkConfig:
$scope.fkConfig = {
maxItems: 1,
valueField: 'id',
labelField: 'name',
searchField: 'name'
};
Now I want to be able to filter and order this column based on the foreign rows name, not id.
I attempted to go about this mapping the ids to their names:
$scope.foreignRowNames = {
0:"not grouped"
1:"Google"
14:"Youtube"
}
and by creating a custom filter for this specific column:
function filterSelectizeColumn(table, searchTerm) {
for (var i = 0; i < table.length; i++) {
var fkValue = table[i].fk;
var foreignRowName = $scope.foreignRowNames[fkValue]];
if (foreignRowName.indexOf(searchTerm) == -1) {
table = table.splice(i, 1);
}
}
}
But this seems an awkward and inefficient way of going about something I would have thought to be a somewhat common problem due to the popularity of the two libraries.
My question is how can I efficiently create a custom filter for this foreign key column.
The solution I went with:
replace all foreign keys with their corresponding name
when making edits, convert names back to the keys they represent

ng-repeats, showing record while it's value is a part of field of another object

I've got objects 'ing' with a field named 'id' and another one called 'fObj' with a field named 'contain'.
By using ng-repeat i'd like to show only these 'ing' objects where ing.id is a part of fObj.contain
e.g.
ing=[{id: 1,field: value},{id:2, field: othervalue},{id:3, field: cat}];
fObj={field1: value1, field: value2, contain: ':1:3:'};
By having this contain value I'd like to show only ing's with id=1 and id=3
Yeah, I know there are two types of data (number and string) but even if i changed numbers to strings it still didn't work
I just dont't know how to make it works. It's probably some kind of custom filter, but I've tried couples and nothing happend.
I would be glad if you suggest me a solution.
Thanks
In your controller,
var ids = fObj.contain.split(':');
// the array for your ng-repeat
var displayIng = [];
// loop the objects, see if the id exists in the list of id's
// retrieved from the split
for(i = 0; i < ing.length; i++) {
if(ids.indexOf(ing.id.toString()) displayIng.push(ing[i]);
}
I would split the numbers out of fObj.contain; and use them as hashmap object keys for simple filtering of the array
var ing=[{id: 1},{id:2},{id:3}];
var fObj={contain: ':1:3:'};
var IDs = fObj.contain.split(':').reduce(function(a,c){
a[c]=true;
return a;
},{});
// produces {1:true,3:true}
var filtered = ing.filter(function(item){
return IDs[item.id];
});
console.log(filtered)

How to implement bi-directional connection between two arrays?

In my app I have an ItemsService which gets Items from Server and stores them as JSON objects in its cache variable. Items can be present in many places, e.g. in table or in graph/chart and so on.
For example, when I initialize a table - I need to pick only specific Items from cache, e.g. 1st, 3rd, 7th.
How to implement bi-directional connection between them? Basically I want table to contain references to specific items from cache so when I change an Item either in cache or in table - its state will be in sync all the time because it's the same Item.
Also when I delete Item from table - it needs to be removed from cache.
Here's example of table and cache structures:
Table:
table: {
"36": { // it's a name of a row
"72": [items], // it's a name of a column with corresponding items
"73": [items],
"74": [items]
},
"37": {
"72": [],
"73": [items],
"74": [items]
},
"38": {
"72": [],
"73": [],
"74": []
}
}
ItemsService cache (simplified version):
ItemsService = {
cache: [items]
};
Item structure:
{
id: 3,
parent_id: 1,
name: 'First Item',
siblings: [1,2,3],
active_users: [{user_id: 1, avatar_url: 'http://...'}, ...],
// 50 more fields :)
}
Also need to point out that I use angular-ui-sortable plugin to allow dragging of Items between columns/rows and I need to provide ng-model with array(I think). Here's how it looks right now:
<td ui-sortable="vm.sortableOptions"
ng-model="vm.table[row.id][column.id]">
<sb-item itemid={{item.id}}
ng-repeat="item in vm.table[row.id][column.id]">
</sb-item>
</td>
Unless for some reason you have to use two separate arrays, have you considered using a filter?
Your best bet would be objects. Variables that hold objects in javascript aren't really holding the object but a reference to said object. When you pass that variable into another one, the reference value gets copied so both variables point toward the same object.
var a = { 0: 'Property 0' };
var b = a;
b[0] = 'Property 0!'; //a[0] also the same.
delete b[0]; //a[0] nor b[0] exist anymore.
Using objects (rather than JSON) would work.
Then your cache and your table both point to same item objects. If you change something in the object, it is reflected at both ends.
var cacheArray = [{ item: 1 }, { item: 2}];
table[0][0] = cacheArray[0];
console.log(table[0][0].item); // 1
cacheArray[0].item = 9;
console.log(table[0][0].item); // 9.
Please note that the array and the table have not changed. They still point to the same objects.

sub array and push data

hello I am trying to create a dynamic array, but I am having problems, I have an onclick event to create invoice so when clicked is launched and I have it
var $boletos=new Array();
function onclick(value){
if($invoice.length==0){
$invoice.push({"items":{}});
$invoice[0].items[0]={"ID":this.person.ID,"other":value};
}else{
//here i do a "for" to create new invoice index, or new item
....
}
}
the result I want is something like
Invoice{
0:{ items{
0:{ ID:"123",other:"xxx"}
1:{ ID:"234",other:"xxx"}
2:{ ID:"233",other:"xxx"}
}
}
1:{ items{
0:{ ID:"1323",other:"yyy"}
1:{ ID:"1323",other:"xyyxx"}
2:{ ID:"1213",other:"yyyy"}
}
}
2:{ items{
0:{ ID:"12323",other:"zz"}
1:{ ID:"1223",other:"zz"}
2:{ ID:"1123",other:"zz"}
}
}
}
but i can do only an object, i can't call a push event, and is because is an object, and not an array, so maybe I need to do something like
$invoice[0].items[0].push({"ID":this.person.ID,"other":value}); please help me
I don't entirely understand the logic you're using here, but basically you should be using push() to add new items to the array, and not numeric indices. The items property should also be an array, not an object:
var $boletos = [];
function onclick(value){
if($boletos.length === 0 || shouldStartANewInvoice) {
$boletos.push({ items: [] });
}
$boletos[$boletos.length - 1].items.push({
ID: this.person.ID,
other:value
});
}
Just to add context and example to #JLRishe's answer.
There are really 2 ways you could do this depending on your use case for the Invoice. If I were submitting it server side in C#, for example, I would structure it more like an object (#2). Otherwise, the array approach works just fine (#1).
As he suggested where Items is an array: http://jsfiddle.net/nv76sd9s/1/
An alternative is to have Items be an object with the ID as the key and the value as the value: http://jsfiddle.net/nv76sd9s/3/
The difference here is the structure of the final object and how you plan to use it. In my #2 link, the object is more structured. It also affects how you iterate through them. Example:
var items = Invoice.Items;
for (k in items) {
var item = items[k];
for (k in item) {
console.log(k +': '+item[k]);
}
}

Format JSON from two flat structured array in Javascript

I need to make a nested JSON out of two flat structured arrays which are kind of linked together, I am able to get the first level but not able to nest it in javascript.
Below is the problem statement.
I have 2 arrays:
data = ["HSP - FS", "FS-M", "Lo Co", "GO A", "CQM","whatever"];
type = ["Epic","Sub - Epic","Sub - Epic","Epic","Sub - Epic","Story"];
the type array and data array are linked together and with type and description on the same index like
type[0] which is data[0], so it will be "Epic: HSP-FS", but this does not end here, this Epic has child elements also which are Sub - Epics and their description is placed under the same array element, so type[1] which is "Sub-Epic" has its data in data[1] which is "FS-M" and so on till next "Epic" is found in the type array.
So JSON would be like
{0:{
Epic: "HSP - FS",
Sub-Epic:"FS-M",
Sub-Epic:"Lo Co"
},
1:{
Epic: "GO A",
Sub-Epic:"CQM",
Story:"whatever"
}}
Structure of array and JSON is fixed, and even array can have any number of Epics, Sub Epics or story but the order will be maintained as in all the child of Epics will be there until next occurrence of Epic happens.
Issue is I am not able to write loop to get the output..I have till now tried something like iterating and joining these two arrays
Thank you in advance for help.
You can't have repeated named properties in a JSON object, but you can use arrays instead:
var array = [];
for(var i=0; i < data.length; ++i)
{
var t = type[i];
var d = data[i];
if (t == "Epic")
{
array.push({ "Epic": d});
}
else
{
if(typeof(array[array.length-1][t]) == "undefined")
{
array[array.length-1][t] = [];
}
array[array.length-1][t].push(d)
}
}
This is the resulting JSON:
[{
"Epic": "HSP - FS",
"Sub - Epic": ["FS-M","Lo Co"]
},
{
"Epic": "GO A",
"Sub - Epic": ["CQM"],
"Story": ["whatever"]
}]

Categories

Resources