PageRenderTime 61ms CodeModel.GetById 40ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/facete-client/src/main/webapp/resources/js/org/aksw/ssb/utils/BackboneUtils.js

https://github.com/GeoKnow/Facete
JavaScript | 631 lines | 328 code | 190 blank | 113 comment | 35 complexity | 6ec2b821e60d4d452552089ec52d4cbf MD5 | raw file
  1(function() {
  2
  3	var uriUtils = Namespace("org.aksw.ssb.utils.uris");
  4
  5	var ns = Namespace("org.aksw.utils.backbone");
  6
  7
  8
  9	/**
 10	 * This class enables syncing data from (an array of) promises
 11	 * into the target collection
 12	 *
 13	 * Obtained data is related to the parameter index of the promise.
 14	 * If sync is called again with new promises, each
 15	 * partition of the prior promises is updated.
 16	 * 
 17	 * Constraints:
 18	 * - The data returned by the promises should have proper id-properties set
 19	 * 
 20	 * @param collection
 21	 * @returns {ns.CollectionCombine}
 22	 */
 23	ns.CollectionCombine = function(collection) {
 24		this.collection = collection ? collection : new Backbone.Collection();			
 25		this.state = [];
 26		this.syncId = 0;
 27	};
 28	
 29	ns.CollectionCombine.prototype = {
 30
 31		getCollection: function() {
 32			return this.collection;
 33		},				
 34	
 35		sync: function(promises) {
 36		
 37			var state = this.state;
 38			var collection = this.collection;
 39			
 40			// Add
 41			{
 42				var delta = promises.length - state.length;
 43				for(var i = 0; i < delta; ++i) {
 44					state[i] = [];
 45				}
 46			}
 47			
 48			// Remove
 49			{
 50				var delta = state.length - promises.length;
 51				
 52				for(var i = state.length - 1; i > promises.length; --i) {
 53					var tmp = state[i];
 54					collection.remove(tmp);					
 55				}
 56				state.splice(promises.length, delta);				
 57			} 
 58		
 59			var self = this;
 60			var syncId = ++this.syncId;
 61		
 62			//var dataProviders = this.dataProviders;
 63			
 64			var handleData = function(data, i) {
 65				if(syncId != self.syncId) {
 66					return;
 67				}
 68				
 69				//console.log("Syncing with data: ", data);
 70			
 71				var tmp = self.state[i];
 72				self.collection.remove(tmp);
 73
 74				state[i] = data;
 75				if(data) { // FIXME only reject null and undefined.
 76					self.collection.add(data);
 77				}				
 78			};
 79			
 80			_.each(promises, function(promise, i) {
 81	
 82				promise.done(function(data) {
 83					handleData(data, i);
 84				}).fail(function(json) {
 85					// TODO Factor this out into error handling code
 86					var data = {
 87						id: "error" + i,
 88						type: "error",
 89						data: json
 90					};
 91					
 92					handleData(data, i);
 93				});
 94									
 95			});
 96		}
 97	};
 98
 99	
100	
101	
102	ns.DefaultModel = Backbone.Model.extend({
103		defaults: {
104			//value: null,
105			/*label: "" // FIXME As there could be many ways for crafting constraint labels, 
106				//associating a label only makes sense for view-models;*/  
107	    }
108	});
109	
110	ns.DefaultCollection = Backbone.Collection.extend({
111		model: ns.DefaultModel
112	});
113
114	
115	ns.fnDefaultId = function(item, row, offset) {
116		return offset + row;
117	};
118	
119
120	
121	
122	/**
123	 * Returns a key from the model based on the binding.
124	 * 
125	 * 
126	 */
127	ns.getModelValue = function(model, key, binding) {
128		var b = binding ? binding[key] : null;
129		var result;
130
131		if (b) {
132			if (typeof b === 'function') {
133				result = b(model);
134			} else {
135				result = model.get(b);
136			}
137		} else {
138			result = model.get(key);
139		}
140		
141		return result;
142	};
143
144	/**
145	 * A seemingly useful routing approach for separating configuration and
146	 * behaviour of routers.
147	 * 
148	 * Usage Example:
149	 * 
150	 * var appMethods = {...};
151	 * 
152	 * var AppRouter = backboneUtils.AppRouter.extend({
153	 *     routes: {
154	 *         ...
155	 *     }     
156	 * });
157	 * 
158	 * var appRouter = new AppRouter({app: app});
159	 * 
160	 * Backbone.history.start();
161	 * 
162	 * 
163	 * Source:
164	 * http://lostechies.com/derickbailey/2012/01/02/reducing-backbone-routers-to-nothing-more-than-configuration/
165	 * 
166	 */
167	ns.AppRouter = Backbone.Router.extend({
168
169		constructor : function(options) {
170			Backbone.Router.prototype.constructor.call(this, options);
171
172			if (this.routes) {
173				this.processAppRoutes(options.app, this.routes);
174			}
175		},
176
177		processAppRoutes : function(app, appRoutes) {
178			var method, methodName;
179			var route, routesLength;
180			var routes = [];
181			var router = this;
182
183			for (route in appRoutes) {
184				routes.unshift([ route, appRoutes[route] ]);
185			}
186
187			routesLength = routes.length;
188			for ( var i = 0; i < routesLength; i++) {
189
190				route = routes[i][0];
191				methodName = routes[i][1];
192				method = app[methodName];
193				router.route(route, methodName, method);
194
195			}
196		}
197
198	});
199
200	
201	ns.ControllerSlaveCollection = function(masterCollection, slaveCollection, fnTransform) {
202		this.masterCollection = masterCollection;
203		this.slaveCollection = slaveCollection;
204		this.fnTransform = fnTransform;
205		
206		this.bind();
207	};
208	
209	ns.ControllerSlaveCollection.prototype = {
210		bind: function() {
211			_.bindAll(this);
212			this.masterCollection.on('add', this.onAdd);
213			this.masterCollection.on('remove', this.onRemove);
214			this.masterCollection.on('reset', this.onReset);
215		},
216		
217		onAdd: function(model) {
218			var newModel = fnTransform(model);
219			
220			this.slaveCollection.add(newModel);			
221		},
222		
223		onRemove: function(model) {
224			var newModel = fnTransform(model);
225			
226			this.slaveCollection.remove(newModel.id);
227		},
228		
229		onReset: function(collection) {
230			var self = this;
231			var newModels = collection.map(function(model) {
232				var newModel = self.fnTransform(model);
233				return newModel;
234			});
235
236			this.slaveCollection.reset(newModels);
237		}
238	};
239	
240	/**
241	 * 
242	 * @param fnPromise A function that returns a promise that upon completion return the new state
243	 */
244	ns.slaveCollection = function(masterCollection, slaveCollection, fnPromise) {
245		masterCollection.on("add", function(model) {
246			
247			var clone = jQuery.extend(true, {}, model.attributes);
248
249			
250			var promise = fnPromise(clone);
251			promise.done(function(newState) {
252				// TODO Treat request order properly
253				slaveCollection.add(newState);
254				//var newState = fn(model.attributes);
255			});
256		});
257		
258		masterCollection.on("remove", function(model) {
259			// TODO Delete by id AND/OR cid
260			slaveCollection.remove(model.id);			
261		});
262		
263		masterCollection.on('reset', function(collection, options) {
264			
265			slaveCollection.reset();
266		});
267	};
268	
269	
270	
271	
272	
273	
274	
275	
276	
277	
278	
279	
280	
281	
282	
283	
284	/**
285	 * fnId(item, row, offset)
286	 * 
287	 */
288	ns.BackboneSyncQuery = function(sparqlService, collection, fnPostProcess) {
289		this.sparqlService = sparqlService;
290		this.collection = collection ? collection : new ns.DefaultCollection();
291		//this.fnId = fnId ? fnId : ns.fnDefaultId; // A function that returns the Id of items delivered by the tableModel
292		this.fnPostProcess = fnPostProcess;
293		
294		this.taskCounter = 0;
295	};
296	
297	ns.BackboneSyncQuery.prototype = {
298			getCollection: function() {
299				return this.collection;
300			},
301	
302			sync: function(query) {
303				var result = $.Deferred();
304		
305				this.taskCounter++;
306				
307				var queryExecution = this.sparqlService.executeSelect(query);
308				var self = this;
309				
310				var tmp = self.taskCounter;
311				
312				
313				queryExecution.success(function(jsonRs) {
314
315					if(self.taskCounter != tmp) {
316						result.fail();
317						return;
318					}
319					
320					var postProcessTask;
321					if(self.fnPostProcess) {
322						postProcessTask = self.fnPostProcess(jsonRs);
323					} else {
324						postProcessTask = queryExecution;
325					}
326						
327					postProcessTask.success(function(jsonRs) {
328						if(self.taskCounter != tmp) {
329							result.fail();
330							return;
331						}
332
333						self.processResult(jsonRs);
334						//console.log("Rosult", jsonRs);
335						result.resolve(jsonRs);
336						//var resolveData = self.processResult(jsonRs);
337						
338						//result.resolve(resolveData);
339					}).fail(function() {
340						result.fail();
341					});
342																
343				}).fail(function() {
344					result.fail();
345				});
346				
347				return result.promise();
348			},
349			
350			
351			processResult: function(jsonRs) {
352				var offset = jsonRs.offset ? jsonRs.offset : 0;
353				var bindings = jsonRs.results.bindings; //data;
354				
355				var destroyModels = [];
356				
357				this.collection.each(function(model) {
358					destroyModels.push(model);
359				});
360				
361				// The browsing experience is better, if first the new models are added
362				// and then the old ones removed:
363				// Removal of models may cause the page to shrink, and therefore change the location the user is viewing
364				// before the new models are added
365				
366				//self.collection.reset();
367				
368				for(var i = 0; i < bindings.length; ++i) {
369					var binding = bindings[i];
370					
371					//var id = self.fnId(binding, i, offset);
372					var id = offset + i;
373					
374					binding.id = id;
375	
376					this.collection.add(binding);
377				}
378
379				for(var i = 0; i < destroyModels.length; ++i) {
380					var model = destroyModels[i];
381					model.destroy();
382				}
383			}
384			
385			
386	};
387	
388	/**
389	 * Deprecated: Resource labels are set on the DOM level using SpanI18N
390	 * 
391	 * 
392	 * --
393	 * Returns a function that processes a json ResultSet:
394	 * - parses all plain Json Nodes to sparql.Node objects
395	 * - associates the label with each result set binding
396	 * 
397	 * Note: Having the labels at the resources is convenient;
398	 * we could however store the labels in a model. This would allow adding
399	 * arbitrary information to resources.
400	 */
401	ns.createDefaultPostProcessor = function(labelFetcher) {
402				
403		var fn = function(plainJsonRs) {
404			
405			//console.log("plainJsonRs", plainJsonRs);
406			
407			//var before = JSON.stringify(plainJsonRs);
408			
409			var jsonRs = uriUtils.parseJsonRs(plainJsonRs);
410			//var after = JSON.stringify(plainJsonRs);
411
412			 /*
413			if(before !== after) {
414				console.log("Before: " + before);
415				console.log("After: " + after);
416				throw "Modification exception";
417			}
418			
419			console.log("JSON RS IS NOW", jsonRs);
420			*/
421			var uris = uriUtils.extractUrisFromParsedJsonRs(jsonRs);
422			
423			
424			var result = $.Deferred();
425			
426			var task = labelFetcher.fetch(uris);
427	
428			
429			task.done(function(labelInfo) {
430			
431				var transformed = uriUtils.transformJsonRs(jsonRs, function(node) {
432						
433					var result = {node: node};
434					
435					//console.log("Node", jsonRs, node);
436					
437					if(node && node.isUri()) {
438					//if(node && (node.type === "uri" || (node instanceof sparql.Node && node.isUri()))) {
439
440						var label = labelInfo.uriToLabel[node.value];
441						
442						if(!label) {
443							var str = uriUtils.extractLabelFromUri(node.value);
444							label = {value: str};
445						}
446						
447						//console.log("Label for node " + node + " is " + label.value);
448						
449						
450						result.label = label;
451					}
452					
453					
454					return result;
455				});
456	
457				result.resolve(transformed); //jsonRs);
458			}).fail(function() {					
459				result.fail();
460			});
461			
462			return result.promise();
463		};
464		
465		
466		// Assign an id for debug reasons 
467		if(!ns.createDefaultPostProcessor.id) {
468			ns.createDefaultPostProcessor.id = 0;
469		} 
470		fn.id = ++ns.createDefaultPostProcessor.id; 
471
472		
473		return fn;
474	};
475	
476	
477	
478	ns.BackboneCollectionRdf = Backbone.Collection.extend({
479		initialize: function(models, options) {
480
481			this.options = options;
482			
483			//console.log("Collection:", sparqlService, postProcessor);
484			
485			this.syncer = new ns.BackboneSyncQuery(options.sparqlService, this, options.postProcessor);			
486		},
487
488		sync: function(jsonRs) {
489			
490			if(!query) {
491				query = this.options.query;
492			}
493			
494			if(!query) {
495				throw "No query specified";
496			}
497			
498			this.syncer.sync(query);
499			
500		}		
501	});
502	
503	/**
504	 * fnId(item, row, offset)
505	 * 
506	 */
507	ns.SyncerRdfCollection = function(collection, fnPostProcess) {
508		this.collection = collection ? collection : new ns.DefaultCollection();
509		//this.fnId = fnId ? fnId : ns.fnDefaultId; // A function that returns the Id of items delivered by the tableModel
510		this.fnPostProcess = fnPostProcess;
511		
512		this.taskCounter = 0;
513	
514		if(!ns.SyncerRdfCollection.id) {
515			ns.SyncerRdfCollection.id = 0;
516		}
517		
518		this.id = ++ns.SyncerRdfCollection.id; 
519	};
520	
521	
522	ns.SyncerRdfCollection.prototype = {
523			getCollection: function() {
524				return this.collection;
525			},
526			
527			setPostProcessFn: function(postProcessFn) {
528				this.fnPostProcess = postProcessFn;
529			},
530	
531			sync: function(jsonRs, offset) {
532				//console.log("Sync [Start] ", this.id, "with " + JSON.stringify(jsonRs));
533				
534				var result = $.Deferred();
535
536				++this.taskCounter;
537				var tmp = this.taskCounter;
538				
539				
540				var self = this;
541				if(this.fnPostProcess) {
542					var postProcessTask = this.fnPostProcess(jsonRs);
543	
544					
545					//console.log("Post processor for ", this.id, " is ", this.fnPostProcess.id);
546					
547					postProcessTask.done(function(procJsonRs) {
548						//console.log("Sync [PostProcess] ", self.id, JSON.stringify(procJsonRs));
549						
550						if(self.taskCounter != tmp) {
551							result.fail();
552							console.log("Action was superseded by another update - Fail");
553							return;
554						}
555						
556						result.resolve(procJsonRs);
557					}).fail(function() {
558						result.fail();
559					});
560				} else {
561					result.resolve(jsonRs, offset);
562				}
563
564				
565				var last = result.pipe(function(resultJsonRs) {
566					self.processResult(resultJsonRs);
567				});
568				
569				return last; //result.promise();
570			},
571			
572			
573			processResult: function(jsonRs, offset) {
574
575				//var offset = jsonRs.offset ? jsonRs.offset : 0;
576				if(!offset) {
577					offset = 0;
578				}
579				
580				var bindings = jsonRs.results.bindings; //data;
581				
582				var newModels = [];
583				for(var i = 0; i < bindings.length; ++i) {
584					var binding = bindings[i];
585					
586					var id = offset + i;
587					
588					binding.id = id;
589	
590					newModels.push(binding);
591				}
592
593	
594				//console.log("New models", JSON.stringify(newModels));
595				//console.log("Sync [Reset] ", this.id, " with " + JSON.stringify(jsonRs));
596				this.collection.reset(newModels);
597			}			
598	};
599	
600	
601	
602	ns.BackboneSyncQueryCollection = Backbone.Collection.extend({
603		initialize: function(models, options) {
604
605			this.options = options;
606			
607			//console.log("Collection:", sparqlService, postProcessor);
608			
609			this.syncer = new ns.BackboneSyncQuery(options.sparqlService, this, options.postProcessor);			
610		},
611
612		sync: function(query) {
613			
614			if(!query) {
615				query = this.options.query;
616			}
617			
618			if(!query) {
619				throw "No query specified";
620			}
621			
622			this.syncer.sync(query);
623			
624		}
625	
626	});
627
628	
629			
630	
631})();