How to Search and Manipulate Complex JavaScript Objects in a jQuery fashion - javascript

In the context of a web app, I have a server which sends or receives JSON strings based on the input from the client. On client consumption, these JSON strings are immediately converted into JavaScript objects where they will live out their lives as objects. These objects are not arrays; they represent complex, arbitrary data models, each property of which can have an arbitrary number of unique subproperties or objects.
var myView = {
name: 'root'
id: 'root_0'
children: {
child_1: {
arbitraryid: 'root_0_child_1',
type: 'Department',
name: 'Produce',
quadrant: 1,
children: {
child_1: {
arbitraryid: 'root_0_child_1_child_1',
type: 'Aisle',
number: 3,
length: 12,
children: { }
}
}
},
child_2: {
arbitraryid: 'root_0_child_2',
name: 'West',
type: 'Region',
children: {
child_1: {
arbitraryid: 'root_0_child_2_child_1',
name: 'Wegmans',
type: 'Store',
children: {
child_1: {
arbitraryid: 'root_0_child_2_child_1_child_1',
type: 'Department',
children: { }
}
}
}
}
}
}
};
When I build the JSON string server side, I guarantee that all objects will have 'children' and 'arbitraryid' properties; but everything else is dynamically generated and the properties and values are completely arbitrary.
If this were XML, I could use jQuery to var someChild = myView.find('#root_0_child_1_child_1'). This would get me a jQuery object with the results of the find AND not only a reference to myView but a position from which to move omnidirectionally through the object: var someChild = myView.find('#root_0_child_1_child_1').parent().
Does a utility exist to solve this problem for native, JavaScript objects or is there a preferable way/methodology to do this? I'd like to avoid writing a bunch of this type of code to simply get at my property and then potentially loop again to update the parent object.
while (obj.hasOwnProperty('children')) {
for (var child in obj) {
//..etc, etc
}
}
Most of the SO questions I see on this subject deal with searching arrays, frequently with predictable data table style construction.
Mapping is possible, but these objects quickly become deep and that option seems little better than dumb looping.
Ideas?
Edit: rolling my own utility class.
I'm still exploring other libraries/utilities, but I wrote a generic helper class to do searches:
ObjectHelper
While useful, I think it illustrates some of the difficulty with getting at other jQuery-like functionality. Not only would I like to search, but I'd like to be able to crawl up/down the object property structure similarly to the way you can chain .parent().children().find() operators together. Doable, but complicated.

I found a few JSON Query Languages:
JSONPath
JAQL
LINQ to Javascript
JSINQ
jLinq
SQLike (despite the name, it's used for querying JSON)
TrimQuery (uses a SQL-like language to query JSON objects)
This page sums them up and goes through their pros and cons.
There is a DOJO module for querying JSON as well:
JSONQuery
I'm not sure how stable/standard these are though.

I solved this by rolling my own classes. ShadesJS is pretty basic right now, but it has some methods for crawling JavaScript objects and working with Web Storage. On the to-do is implement parent/child methods to get some JQuery-esque flexibility. It's not hard to do, but it's tricky to get it right and performant.

Related

Kendo UI TreeListViews with complex data

Background Information
In short i'm looking to achieve "mostly" whats shown here ...
http://demos.telerik.com/kendo-ui/treelist/remote-data-binding
... except it's a bit of a mind bender and the data comes from more than base endpoint url in my case.
I am trying to build a generic query building page that allows users to pick a context, then a "type" (or endpoint) and then from there build a custom query on that endpoint.
I have managed to get to the point where I do this for a simple query but now i'm trying to handle more complex scenarios where I retrieve child,or deeper data items from the endpoint in question.
With this in mind ...
The concept
I have many endpoints not all of which OData but follow mostly OData v4 rules, and so I am trying to build a "TreeGrid" based having selected an endpoint that will expose the expansion options available to the query.
All my endpoints have a custom function on it called GetMetadata() which describes the type information for that endpoint, where an endpoint is for the most part basically a REST CRUD<T> implementation which may or may not have some further custom functions on it to handle a few other business scenarios.
So, given a HTTP get request to something like ...
~/SomeContext/SomeType/GetMetadata()
... I would get back an object that looks much like an MVC / WebAPI Metadata container.
that object has a property called "Properties" some of which are scalar and some of which are complex (as defined in the data).
I am trying to build a TreeListDataSource or a HierarchicalDataSource object that I can use to bind to the Kendo treeList control for only the complex properties, that dynamically builds the right get url for the meta and lists out the complex properties for that type based on the property information from the parent type with the root endpoint being defined in other controls on the page.
The Problem
I can't seem to figure out how to configure the kendo datasource object for the TreeGrid to get the desired output, I think for possibly one of two reasons ...
The TreeListDataSource object as per the demo shown here: http://demos.telerik.com/kendo-ui/treelist/local-data-binding seems to imply that the hierarchy based control wants a flat data source.
I can't figure out how to configure the datasource in such a way that I could pass in the parent meta information (data item from the source) in order to build the right endpoint url for the get request.
function getDatasource(rootEndpoint) {
return {
pageSize: 100,
filter: { logic: 'and', filters: [{ /* TODO:possibly filter properties in here? */ }] },
type: 'json',
transport: {
read: {
url: function (data) {
//TODO: figure out how to set this based on parent
var result = my.api.rootUrl + endpoint + "/GetMetadata()";
return result;
},
dataType: 'json',
beforeSend: my.api.beforeSend
}
},
schema: {
model: {
id: 'Name',
fields: {
Type: { field: 'Type', type: 'string' },
Template: { field: 'Template', type: 'string' },
DisplayName: { field: 'DisplayName', type: 'string' },
ShortDisplayName: { field: 'ShortDisplayName', type: 'string' },
Description: { field: 'Description', type: 'string' },
ServerType: { field: 'ServerType', type: 'string' }
}
}
parse: function (data) {
// the object "data" passed in here will be a meta container, a single object that contains a property array.
$.each(data.Properties, function (idx, item) {
item.ParentType = data;
item.Parent = ??? where do I get this ???
});
return data.Properties;
}
}
};
}
Some of my problem may be down to the fact that metadata inherently doesn't have primary keys, I wondered if perhaps using parse to attach a generated guid as the key might be an idea, but then I think Kendo uses the id for the question on the API when asking for children.
So it turns out that kendo is just not geared up to do anything more than serve up data from a single endpoint and the kind of thing i'm doing here is a little bit more complex than that, and further more due to the data being "not entity type data" I don't have common things like keys and foreign keys.
With that in mind I chose take the problem away from kendo altogether and simply handle the situation with a bit of a "hack that behaves like a normal kendo expand but not really" ...
In a treegrid, when kendo shows an expandable row it renders something like this in the first cell ...
With no expanded data or a data source that is bound to a server this cell is not rendered.
so I faked it in place and added an extra class to my version .not-loaded.
that meant I could hook up a custom block of js on click of my "fake expand", to build the right url, do my custom stuff, fake / create some id's, then hand the data to the data source.
expandList.on('click', '.k-i-expand.not-loaded', function (e) {
var source = expandList.data("kendoTreeList");
var cell = $(e.currentTarget).closest('td');
var selectedItem = source.dataItem($(e.currentTarget).closest('tr'));
my.type.get(selectedItem.ServerType, ctxList.val(), function (meta) {
var newData = JSLINQ(meta.Properties)
.Select(function (i) {
i.id = selectedItem.id + "/" + i.Name;
i.parentId = selectedItem.id;
i.Selected = my.type.ofProperty.isScalar(i);
i.TemplateSource = buildDefaultTemplateSourceFor(i);
return i;
})
.ToArray();
for (var i in newData) {
source.dataSource.add(newData[i]);
}
$(e.currentTarget).remove();
source.expand(selectedItem);
buildFilterGrid();
generate();
});
});
This way, kendo gets given what it epects for a for a treeviewlist "a flat set with parent child relationships" and I do all the heavy lifting.
I used a bit of JSLINQ magic to make the heavy lifting a bit more "c# like" (i prefer c# after all), but in short all it does is grab the parent item that was expanded on and uses the id from that as the parent then generates a new id for the current item as parent.id + "/" + current.name, this way everything is unique as 2 properties on an object can't have the same name, and where two objects are referenced by the same parent the prefix of the parents property name makes the reference unique.
It's not the ideal solution, but this is how things go with telerik, a hack here, a hack there and usually it's possible to make it work!
Something tells me there's a smarter way to do this though!

Ember find model by Id

In my Ember app, I have a complex model that looks like below (kind of contains 2-dimensional array)
[
[
{
id: 'Section1_123',
label: 'abc'
},
{
id: 'Section1_456',
label: 'xyz'
}
]
],
[
[
{
id: 'Section2_123',
label: 'abc'
},
{
id: 'Section2_456',
label: 'xyz'
}
]
]
There are a lot of other attributes, but this is the overall structure.
Now my question is can I drill-down & find a specific object. It has unique ids (as shown in the example above)
So I need something like model.findBy(Id)
I then need to change/set some values for that object. Say I want to change the obj.label from 'abc' to 'abc_NEW'
Just to add, The main model is actually a simple JS array...but the inside objects (e.g. those with id: 'Section1_123', etc) are actually Ember objects
Most common approach to work with data in Ember is EmberData. And because the main credo of Ember is "convention over configuration" then a common way in Ember is the best way, in my opinion.
There are many ways how to deal with your data format. I would recommend to create model for each item:
import DS from 'ember-data';
export default DS.Model.extend({
label: DS.attr()
// other properties
});
Then you can make a custom serializer according this article. The goal is to convert your arrays to list of EmberData models.
After this you can use standard EmberData functions to work with data (including access by object id, of course).

Dojo JsonRestStore with array not at root-level of JSON response

Is there a way to configure a JsonRestStore to work with an existing web service that returns an array of objects which is not at the root-level of the JSON response?
My JSON response is currently similar to this:
{
message: "",
success: true,
data: [
{ name: "Bugs Bunny", id: 1 },
{ name: "Daffy Duck", id: 2 }
],
total: 2
}
I need to tell the JsonRestStore that it will find the rows under "data", but I can't see a way to do this from looking at the documentation. Schema seems like a possibility but I can't make sense of it through the docs (or what I find in Google).
My web services return data in a format expected by stores in Ext JS, but I can't refactor years worth of web services now (dealing with pagination via HTTP headers instead of query string values will probably be fun, too, but that's a problem for another day).
Thanks.
While it's only barely called out in the API docs, there is an internal method in dojox/data/JsonRestStore named _processResults that happens to be easily overridable for this purpose. It receives the data returned by the service and the original Deferred from the request, and is expected to return an object containing items and totalCount.
Based on your data above, something like this ought to work:
var CustomRestStore = declare(JsonRestStore, {
_processResults: function (results) {
return {
items: results.data,
totalCount: results.total
};
}
});
The idea with dojo/store is that reference stores are provided, but they are intended to be customized to match whatever data format you want. For example, https://github.com/sitepen/dojo-smore has a few additional stores (e.g. one that handles Csv data). These stores provide good examples for how to handle data that is offered under a different structure.
There's also the new dstore project, http://dstorejs.io/ , which is going to eventually replace dojo/store in Dojo 2, but works today against Dojo 1.x. This might be easier for creating a custom store to match your data structure.

Feasible way to map server response into Backbone models?

The server responds with something on the form:
{'dates':
{'2013.05-17':
{'activities':
{'activity 1':
{time: 0, 'synced': false},
'activity 2':
{time: 5, 'synced': false},
'activity 3':...
},...
},
'2013.05-18':
{ ...}, ...},
'id': id}
I currently put everything in a single Backbone model, which doesn't seem like the proper way to do it. The examples I've read around the web all use very simple models where there's no nestled structures and the mapping is pretty simple e.g. {x: 1, y: 2} being mapped to a coordinate model and so on.
What's the "correct" way to map the above JSON structure to Backbones models/collections?
As Protostome mentions, Backbone Relational is good for this kind of thing.
However looking at the JSON data that you have used as an example, from my viewpoint you have only one model and collection as follows:
Activity Model
Activities Collection
Even though you have a nested set of data you could look at it in a different way which gives you a simple backbone model, for example:
var activity = {
id: "Activity 1"
time: 0,
synced: false,
date: "2013.05-17"
batchId: id // this corresponds to the id property in your example
}
This represent all of the data contained in your complex nested hierarchy more simply, and more importantly suited to the way Backbone works.
If you cannot alter what the server provides you, you could use the Underscore library functions (from memory _.map()) to map the JSON you receive into simple JSON objects ready for use with Backbone.
I am hoping that maybe you can simplify your design by thinking outside the the context of sticking with a hierarchy?

DOM manipulation and Javascript Data Structures: Flat or Structured?

I have a business requirement (which is not going to change) to display about 10,000 items on a page (I don't like it). The items are related via a parent child relationship much like folders, sub folders and files on a disk drive.
I want to build the page in such a way that it is driven by a javascript data structure (internal application, javascript will always be enabled).
Is it generally better to have a flat data structure where each record contains a link to its parent, or is it generally better to have a structured data structure where each "folder" contains references to "sub folders" and "files"?
Ie:
var items = { //flat array
{id:1, parentId:0},
{id:2, parentId:1},
{id:3, parentId:1},
{id:4, parentId:2},
{id:5, parentId:2}
};
var items = { //structured
{ id: 1, children: {
{ id: 2, children: {
{ id: 4 },
{ id: 5 },
} },
{ id: 3 },
} }
};
Off the bat the flat format seems easier on the eyes, but the server will be generating that list so honestly that isn't such a big deal. How would performance compare? With flat, there would need to be much more DOM lookups, ie find element with id=1 and insert DOM element for id=2. The structured format has all the relationships defined so as the javascript code is navigating the structure it can create all the DOM elements needed without additional lookups. So which method would generally be more performant?
Structured. It's more scalable and will be easier to manipulate nodes in the DOM with JQuery.

Categories