data loading pattern for knockoutjs
-
27-10-2019 - |
Question
I am trying to understand if KnockoutJS will work for my application. My data model (simplified) is as follows:
function topic(data) {
this.id = data.id;
this.queries = ko.observableArray([]);
}
function query(data) {
this.id = data.id;
this.text = data.text;
this.searcher = data.searcherId;
this.postings = ko.observableArray([]);
}
function posting(data, query) {
this.documentId = data.docid;
this.rank = data.rank;
this.snippet = data.snippet;
this.score = data.score;
this.query = query;
this.document = null;
}
function document(data, topic) {
this.id = data.id;
this.url = data.url;
this.topic = topic;
}
For a given topic
, I have one or more query
instances. Each query contains a list of posting
instances. Each posting
refers to a document. More than one posting
can refer to a given document
as long as the posting
instances belong to different query
instances.
If a posting
refers to a new document (one not yet retrieved by any query
) I would like to create a new instance; if the document
already exists (ids are unique), I would like to re-use it.
I can see some possible alternatives for structuring the JSON data returned by the server:
- When serializing postings, first serialize a list of all documents, and update the master document list with them. Then, send postings with references to document ids.
- Serialize each document completely as a property of a posting, and then figure out if that entry is redundant. Add non-redundant entries to the master list.
What is a reasonable pattern for serializing the data? Is there some mapping plugin magic that would express this succinctly? I have control over the server that's generating the JSON, and can structure that in any way that makes sense.
Thanks,
Gene
Solution
Here's what I wound up doing to implement option 1:
function idField(data) {
return ko.utils.unwrapObservable(data.id);
}
function createMapping(type, context) {
return {
key: idField,
create: constructor(type, context)
}
}
function constructor(type, context) {
return function(options) {
return new type(options.data, context);
}
}
function createReferenceMapping(collection) {
return {
key: idField,
create: lookup(collection)
}
}
function lookup(collectionOrClosure) {
return function(options) {
var collection = (typeof collectionOrClosure == 'function') ? collectionOrClosure() : collectionOrClosure;
var object = collection.findById(options.data.idref);
if (object == null)
console.log("Error: Could not find object with id " + options.data.idref + " in ", collection);
return object;
}
}
I call this code as follows:
var mapping = {
people: createMapping(Searcher),
topics: createMapping(Topic, this),
activeTopic: createReferenceMapping(function(){return self.topics();})
};
this.dataChannel.loadModel(function(data) {
ko.mapping.fromJS(data, mapping, this);
}
This takes care both of creating new instances (via the constructor
function) and looking up existing ones via lookup
.
OTHER TIPS
Checkout entityspaces.js, there's a video you can watch, it supports full hierarchcial data models and will even generate your WCF JSON service, it supports REST API's as well.
A Javascript ORM (Data Access) Framework that uses Knockout