Multilevel grouping in linq js - javascript

I have this json Format
var personArray =
[
{name:"person1",code:"101011",mainDept:"mainD 1",dept:"dept1",SubDept:"Sub01"},
{name:"person2",code:"201012",mainDept:"mainD 1",dept:"dept1",SubDept:"Sub11"},
{name:"person3",code:"301013",mainDept:"mainD 1",dept:"dept2",SubDept:"Sub12"},
{name:"person4",code:"401014",mainDept:"mainD 1",dept:"dept2",SubDept:"Sub12"},
{name:"person5",code:"501015",mainDept:"mainD 1",dept:"dept2",SubDept:"Sub13"},
{name:"person6",code:"601116",mainDept:"mainD 1",dept:"dept3",SubDept:"Sub21"},
{name:"person7",code:"701117",mainDept:"mainD 1",dept:"dept3",SubDept:"Sub21"},
{name:"person8",code:"801118",mainDept:"mainD 1",dept:"dept4",SubDept:"Sub22"},
{name:"person9",code:"901119",mainDept:"mainD 2",dept:"dept12",SubDept:"Sub23"},
{name:"person10",code:"101111",mainDept:"mainD 2",dept:"dept12",SubDept:"Sub24"},
{name:"person12",code:"121012",mainDept:"mainD 2",dept:"dept13",SubDept:"Sub25"},
{name:"person13",code:"131013",mainDept:"mainD 2",dept:"dept131",SubDept:"Sub26"},
{name:"person14",code:"141014",mainDept:"mainD 3",dept:"dept132",SubDept:"Sub27"},
{name:"person15",code:"151015",mainDept:"mainD 3",dept:"dept132",SubDept:"Sub27"},
{name:"person16",code:"161116",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person17",code:"171117",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person18",code:"181118",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person21",code:"211012",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub1"},
{name:"person22",code:"221013",mainDept:"mainD 4",dept:"dept141",SubDept:"Sub001"},
{name:"person23",code:"231014",mainDept:"mainD 4",dept:"dept151",SubDept:"Sub002"},
{name:"person24",code:"241015",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person25",code:"251116",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person26",code:"261117",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person27",code:"271118",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person28",code:"281119",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"},
{name:"person29",code:"291119",mainDept:"mainD 5",dept:"dept161",SubDept:"Sub003"}];
and i want to build data for jsTree (https://www.jstree.com/docs/json/)
multi level grouping will be like this i.e mainDept -> dept - > SubDept - > person
i tried this to get one level grouping but cant figure out how to get multi level grouping.
var linq = Enumerable.From(personArray);
var grp = linq.GroupBy("$.mainDept","{text:$.dept}","{name:$,children:$$.ToArray()}").ToArray()

There really isn't a nice way to an arbitrarily deeply nested grouping, particularly if you need to do something different at each level. Doing some sort of recursion makes this simple. Fortunately Linq.js has a Let() function to allow for this. With some specially crafted functions, this could be done rather nicely.
function grouper(propertyName, selector) {
return function (e) {
return e.GroupBy("$." + propertyName, null, function (k, g) {
return {
text: k,
children: g.Let(selector).ToArray()
};
});
};
}
var items = Enumerable.From(personArray)
.Let(grouper('mainDept', function (g1) {
return g1.Let(grouper('dept', function (g2) {
return g2.Let(grouper('SubDept', function (g3) {
return g3.Select("$.name").ToArray();
}));
}));
}))
.ToArray();
fiddle
For a different approach, you utilize jstree's alternate form of coming up with the parent/child relationships. You don't need to nest anything anymore, just come up with a flat list of the config nodes.
var items = Enumerable.From(personArray)
.Let(function (e) {
var roots = { '#': {}, mainDept: {}, dept: {}, SubDept: {} };
e.ForEach(function (p) {
roots['#'][p.mainDept] = '#';
roots['mainDept'][p.dept] = p.mainDept;
roots['dept'][p.SubDept] = p.dept;
roots['SubDept'][p.name] = p.SubDept;
});
function makeNode(root) {
return Enumerable.From(roots[root]).Select("{ parent: $.Value, id: $.Key, text: $.Key }");
}
return makeNode('#').Concat(makeNode('mainDept')).Concat(makeNode('dept')).Concat(makeNode('SubDept'));
})
.ToArray();
fiddle

First you have to parse your personArray into json acceptable by jsTree and then feed it to jsTree initialisation. I can't help you with linq, but with plain javascript it could work like in this demo - Fiddle.

Related

How to convert arrays to objects in javascript?

How could I rewrite this code to object javascript. Since Array usage is prohibed, I can only use objects here. Insted of pushing values to array, I would like to push this values into objects.
var container = [];
document.addEventListener("submit", function(e){
e.preventDefault();
});
window.addEventListener("load",function(){
var submit = document.getElementsByClassName("btn-primary");
submit[0].addEventListener("click",add,false);
document.getElementById("pobrisi").addEventListener("click",deleteAll,false);
var dateElement = document.getElementById('datum');
dateElement.valueAsDate = new Date();
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1;
var yyyy = today.getFullYear();
if(dd<10){
dd='0'+dd
}
if(mm<10){
mm='0'+mm
}
today = yyyy+'-'+mm+'-'+dd;
dateElement.setAttribute("min",today);
});
function add() {
var title = document.getElementById("title").value;
var type = document.getElementById("type").value;
var datum = document.getElementById("datum").value.split("-");
datum = datum[2]+". "+datum[1]+". "+datum[0];
var data = new Book(title,type,datum);
container.push(data.add());
display();
}
function display(data) {
var destination = document.getElementById("list");
var html = "";
for(var i =0;i <container.length; i++) {
html +="<li>"+container[i]+"</li>";
}
destination.innerHTML = html;
}
function deleteAll(){
container=[];
document.getElementById("list").innerHTML="";
}
Wondering if is possible to write this code whitout any array usage.
initial remarks
The problem here, in my estimation, is that you haven't learned the fundamentals of data abstraction yet. If you don't know how to implement an array, you probably shouldn't be depending on one quite yet. Objects and Arrays are so widespread because they're so commonly useful. However, if you don't know what a specific data type is affording you (ie, what convenience does it provide?), then it's probable you will be misusing the type
If you take the code here but techniques like this weren't covered in your class, it will be obvious that you received help from an outside source. Assuming the teacher has a curriculum organized in a sane fashion, you should be able to solve problems based on the material you've already covered.
Based on your code, it's evident you really have tried much, but why do you think that people here will come up with an answer that your teacher will accept? How are we supposed to know what you can use?
a fun exercise nonetheless
OK, so (we think) we need an Array, but let's pretend Arrays don't exist. If we could get this code working below, we might not exactly have an Array, but we'd have something that works like an array.
Most importantly, if we could get this code working below, we'd know what it takes to make a data type that can hold a dynamic number of values. Only then can we begin to truly appreciate what Array is doing for us.
// make a list
let l = list(1) // (1)
// push an item on the end
l = push(l, 2) // (1 2)
// push another item on the end
l = push(l, 3) // (1 2 3)
// display each item of the list
listeach(l, function (x) {
console.log(x)
})
// should output
// 1
// 2
// 3
runnable demo
All we have to do is make that bit of code (above) work without using any arrays. I'll restrict myself even further and only use functions, if/else, and equality test ===. I see these things in your code, so I'm assuming it's OK for me to use them too.
But am I supposed to believe your teacher would let you write code like this? It works, of course, but I don't think it brings you any closer to your answer
var empty = function () {}
function isEmpty (x) {
return x === empty
}
function pair (x,y) {
return function (p) {
return p(x,y)
}
}
function head (p) {
return p(function (x,y) {
return x
})
}
function tail (p) {
return p(function (x,y) {
return y
})
}
function push (l, x) {
if (isEmpty(l))
return list(x)
else
return pair(head(l), push(tail(l), x))
}
function list (x) {
return pair(x, empty)
}
function listeach (l, f) {
if (isEmpty(l))
return null
else
(f(head(l)), listeach(tail(l), f))
}
// make a list
let l = list(1) // (1)
// push an item on the end
l = push(l, 2) // (1 2)
// push another item on the end
l = push(l, 3) // (1 2 3)
// display each item of the list
listeach(l, function (x) {
console.log(x)
})
closing remarks
It appears as tho you can use an Object in lieu of an Array. The accepted answer (at this time) shows a very narrow understanding of how an object could be used to solve your problem. After this contrived demonstration, are you confident that you are using Objects properly and effectively?
Do you know how to implement an object? Could you fulfill this contract (below)? What I mean by that, is could you write the functions object, set, and get such that the following expressions evaluated to their expected result?
In case it's not obvious, you're not allowed to use Object to make it happen. The whole point of the exercise is to make a new data type that you don't already have access to
m = object() // m
set(m, key, x) // m
get(m, key) // x
set(m, key2, y) // m
get(m, key2) // y
set(m, key3, set(object(), key4, z)) // m
get(get(m, key3), key4) // z
I'll leave this as an exercise for you and I strongly encourage you to do it. I think you will learn a lot in the process and develop a deep understanding and appreciation for what higher-level data types like Array or Object give to you
Since this is a homework I feel like I shouldn't solve it for you, but rather help you in the right direction.
Like Slasher mentioned you can use objects
With JavaScript object one book would look something like
const book = {
title: 'my awesome title',
type: 'novel'
};
book is the object
title is a property with a value 'my awesome title'
type is a property with a value 'novel'
But objects can also have other objects as values. Something like
const BookShelf= {
Book1: {
Title: 'my awesome title',
Type: 'novel'
},
Book2: {
Title: 'my horrible title',
Type: 'sci-fi'
}
};
You can reference the books in the bookshelf in two ways
const book1 = BookShelf.Book1 // Returns the book1 object
const title1 = Book1.Title; // Get the title
const sametitle = BookShelf.Book1.Title // Returns title for book1, same as above.
You can also use brackets:
const book1 = BookShelf['Book1'];
const title1 = BookShelf['Book1']['Title];
You can even make new properties on a object like this:
const Book3 = {
Title: 'running out of ideas'
Type: 'memoir'
};
BookShelf['Book3'] = Book3;
Now the BookShelf has a Book3 property. So your BookShelf object looks like
const BookShelf= {
Book1: {
Title: 'my awesome title',
Type: 'novel'
},
Book2: {
Title: 'my horrible title',
Type: 'sci-fi'
},
Book3 = {
Title: 'running out of ideas'
Type: 'memoir'
};
};
That should get you started :)
JavaScript Objects is a good way to go
1- define a new object:
var myVar = {};
or
var myVar = new Object();
2- usage
// insert a new value, it doesn't matter if the value is a string or int or even another object
// set a new value
myVar.myFirstValue="this is my first value";
// get existing value and do what ever you want with it
var value = myVar.myFirstValue

Accessing a javascript object in D3.js

A javascript data object (JSON notation) has been created with the following content:
"[
{"range":"Shape","values":[{"idx":0,"val":"Random"},{"idx":1,"val":"Line"},{"idx":2,"val":"Square"},{"idx":3,"val":"Circle"},{"idx":4,"val":"Oval"},{"idx":5,"val":"Egg"}]},
{"range":"Color","values":[{"idx":0,"val":"Red"},{"idx":1,"val":"Blue"},{"idx":2,"val":"Yellow"},{"idx":3,"val":"Green"},{"idx":4,"val":"Cyan"}]}
]"
In a next step the index of an ordinal value has to be found in this object. The function should find the index of the value 'Blue' in the range 'Color'.
So the function should have the meta scripting form
f("Color")("Blue")=1
What is the most elegant form to create such a function in the context of D3 and javascript?
Depending on your use case, it might make sense to convert the data structure to a different structure more suitable for direct access. E.g. you could convert your structure to
var data = {
Shape: ['Random', 'Line', ...],
// ...
};
and access it with
data['Shape'].indexOf('Line') // or data.Shape.indexOf('Line')
Or go even one step further and convert to
var data = {
Shape: {
Random: 0,
Line: 1,
// ...
},
// ...
};
and access it with
data['Shape']['Line'] // or data.Shape.Line
What the best solution is depends on the actual use case.
Converting the structure dynamically is pretty straight forward. Here is an example to convert it to the first suggestion:
var newData = {};
data.forEach(function(item) {
newData[item.range] =
item.values.map(function(value) { return value.val; });
});
This would also reduce redundancy (e.g. idx seems to correspond with the element index).
Would this work for you ?
var dataJson = '[ \
{"range":"Shape","values":[{"idx":0,"val":"Random"},{"idx":1,"val":"Line"},{"idx":2,"val":"Square"},{"idx":3,"val":"Circle"},{"idx":4,"val":"Oval"},{"idx":5,"val":"Egg"}]},\
{"range":"Color","values":[{"idx":0,"val":"Red"},{"idx":1,"val":"Blue"},{"idx":2,"val":"Yellow"},{"idx":3,"val":"Green"},{"idx":4,"val":"Cyan"}]}\
]';
var data = JSON.parse(dataJson);
for (each in data){
if ( (data[each].range) === 'Color'){
for (eachVal in data[each].values){
if (data[each].values[eachVal].val === 'Blue'){
alert(data[each].values[eachVal].idx);
}
}
} ;
}
And here is the JSFiddle for you too.

Grouping / counting in javascript using underscore.js

I am new to javascript (and to Stack Overflow) and I've encountered a problem I can't seem to solve. I am trying to generate a simple pie chart that shows the number of Projects for each value of Technology in my data. This is the kind of data I am working with:
[Project1, Java]
[Project2, Excel]
[Project3, SAS]
[Project4, Java]
The pie ratio in the example above would be 2:1:1.
The first part of my code loads the data and pushes it to an array, "techArray", that contains [project, tech]. This part works ok - I've verified it in a simplified version of the code.
I then want to group the array "techArray" and count the instances of each tech. To do so I'm using the Underscore library, as follows:
var chartData = [];
var techData = _.groupBy(techArray, 'tech');
_.each(techData, function(row) {
var techCount = row.length;
chartData = push( {
name: row[0].tech,
y: techCount
});
});
The script then renders the chartData array using highcharts. Again, I have verified that this section works using a simplified (ungrouped) version.
There must be an issue with the grouping/counting step outlined above because I am seeing no output, but I simply can't find where. I am basing my solution on the following worked example: Worked example.
If anyone can spot the error in what I've written, or propose another way of grouping the array, I'd be very grateful. This seems like it should be a simpler task than it's proving to be.
countBy could be used instead of groupBy:
var techArray = [
{ project: 'Project1', tech: 'Java'},
{ project: 'Project2', tech: 'Excel'},
{ project: 'Project3', tech: 'SAS'},
{ project: 'Project4', tech: 'Java'},
];
var counts = _.countBy(techArray,'tech');
This will return an object with the tech as properties and their value as the count:
{ Java: 2, Excel: 1, SAS: 1 }
To get the data in the form for highcharts use map instead of each:
var data = _.map(counts, function(value, key){
return {
name: key,
y: value
};
});
This should work
var techArray = [['Project1','Java'], ['Project2', 'excel'], ['Project3', 'Java']];
var chartData = [];
var techData = _.groupBy(techArray, function(item) {
return item[1];
});
_.each(techData, function(value, key) {
var techCount = value.length;
chartData.push({
name: key,
y: techCount
});
});
_.groupBy needs to either get a property name, or a function that returns the value being grouped. There is no tech property of an array, so you cant group by it. But, as our techArray is an array of tuples, we can pass a function _.groupBy that returns the value that we want to groupBy, namely the second item in each tuple.
chartData now looks like this:
[{
name: 'Java',
y: 2
}, {
name: 'excel',
y: 1
}]

Sorting grouped items in listview

I need to sort individual items in groups.
The Listview api lets me to create sorted groups or sort whole list.
I need to sort item in the respective groups.
Is it possible?
I was struggling with the same issue and neither found a useful example, nor a hint. Then I started to combine SortedListProjection and GroupedSortedListProjection, and by that, finally made it work.
My use case was to have groups in alphabetical, ascending order, but within a group, have the items ordered by a timestamp, descending.
Here is how I set it up in the JavaScript file:
var list = new WinJS.Binding.List(); // list gets populated later in the code
var sortedList = list.createSorted(compareItems);
var groupedList = list.createGrouped(getGroupKey, getGroupData, compareGroups);
WinJS.Namespace.define("my.stuff", {
sortedList: sortedList,
groupedList: groupedList
});
The important thing was to keep the item sorting in sync with the grouping. Therefore the item sorting function is calling grouping functions.
Below are all four functions used for sorting and grouping:
compareItems = function (leftItem, rightItem) {
let leftKey = getGroupKey(leftItem);
let rightKey = getGroupKey(rightItem);
let compare = compareGroups(leftKey, rightKey);
if (compare == 0) { // if keys are equal compare timestamps
return leftItem.timestamp < rightItem.timestamp ? 1
: leftItem.timestamp > rightItem.timestamp ? -1 : 0;
}
return compare;
};
getGroupKey = function (item) {
return item.name + item.category;
};
getGroupData = function (item) {
return {
name: item.name + " (" + item.category + ")"
};
};
compareGroups = function (leftKey, rightKey) {
return leftKey.toUpperCase().localeCompare(rightKey);
};
Finally, the combined declaration of both lists for the ListView:
<div id="dataDeliveriesList" class="hidden"
data-win-control="WinJS.UI.ListView"
data-win-options="{
itemDataSource: my.stuff.sortedList.dataSource,
itemTemplate: select('#itemTemplate'),
groupDataSource: my.stuff.groupedList.groups.dataSource,
groupHeaderTemplate: select('#groupHeaderTemplate'),
layout: {
type: WinJS.UI.ListLayout
}
}">
</div>
It's possible. ObservableCollection doesn't provide a sorting option, and therefore you'll have to create a new collection that's sorted on a selected property. See sample code below. For other sorting options, read the blog post here and another stackoverflow thread.
// TODO: Create an appropriate data model for your problem domain to replace the sample data
var group = SampleDataSource.GetGroup((String)navigationParameter);
this.DefaultViewModel["Group"] = group;
//this.DefaultViewModel["Items"] = group.Items;
// Sort items by creating a new collection
ObservableCollection<SampleDataItem> grpSorted = new ObservableCollection<SampleDataItem>(
group.Items.OrderBy(grp => grp.Title));
this.DefaultViewModel["Items"] = grpSorted;

How do you observe JavaScript hashtables in Knockout?

In my Knockout viewmodel, I've got some properties where I'm trying to make a hash observable. So instead of my pre-Knockout code of
self.MyHash = {};
I am now using:
self.MyHash = ko.observable({});
In other parts of my code, I am manipulating the hash with statements like these:
// add an entry
self.MyHash()["test"] = "My Value";
// remove an entry
delete self.MyHash()["test"];
The code works, in that the entries are added and removed properly. However, the changes to the hashtable don't seem to be detected by areas of the code that are observing it. For example, this computed observable never runs when I am changing the hashtable:
self.Querystring = ko.computed(function ()
{
var f = [];
$.each(self.MyHash(), function (k, v)
{
f.push(encodeURIComponent(k) + '=' + encodeURIComponent(v));
});
return (f.length > 0) ? f.join("&") : "";
});
I am going to guess that this is because Knockout observables are required to be simple variables (or observableArrays), and that it's not detecting the underlying changes to my hashtable.
If so, are there other options? Why isn't there an observableHash type in Knockout?
For what it's worth, my workaround is to have an observableArray of keys, and a regular JavaScript hashtable to lookup the values. Then I changed my computed method to observe the array of keys rather than the other hashtable variable I had before. I just want to make sure I'm not missing "The Right Way" to do it in Knockout.
self.MyHashKeys = ko.observableArray();
self.MyHash = {};
self.Querystring = ko.computed(function ()
{
var f = [];
$.each(self.MyHashKeys(), function (index, value)
{
f.push(encodeURIComponent(value) + '=' + encodeURIComponent(self.MyHash[value]));
});
return (f.length > 0) ? f.join("&") : "";
});
See the second example on the observable array page. Just make an array of key/value pairs:
// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);
In your examples you are only iterating (except for deletion), so there is no real need to use an actual dictionary. It would be easy enough to just search for the key. I think the use of map is kind of a premature optimization to some extent. Its also not entirely in line with the ability of query strings to support the same key multiple times.
Edit: if you want to observe the key or value changing in this example, you would also have to make those properties observable:
var anotherObservableArray = ko.observableArray([
{ name: ko.observable("Bungle"), type: ko.observable("Bear") }
]);

Categories

Resources