PageRenderTime 102ms CodeModel.GetById 39ms RepoModel.GetById 0ms 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. var uriUtils = Namespace("org.aksw.ssb.utils.uris");
  3. var ns = Namespace("org.aksw.utils.backbone");
  4. /**
  5. * This class enables syncing data from (an array of) promises
  6. * into the target collection
  7. *
  8. * Obtained data is related to the parameter index of the promise.
  9. * If sync is called again with new promises, each
  10. * partition of the prior promises is updated.
  11. *
  12. * Constraints:
  13. * - The data returned by the promises should have proper id-properties set
  14. *
  15. * @param collection
  16. * @returns {ns.CollectionCombine}
  17. */
  18. ns.CollectionCombine = function(collection) {
  19. this.collection = collection ? collection : new Backbone.Collection();
  20. this.state = [];
  21. this.syncId = 0;
  22. };
  23. ns.CollectionCombine.prototype = {
  24. getCollection: function() {
  25. return this.collection;
  26. },
  27. sync: function(promises) {
  28. var state = this.state;
  29. var collection = this.collection;
  30. // Add
  31. {
  32. var delta = promises.length - state.length;
  33. for(var i = 0; i < delta; ++i) {
  34. state[i] = [];
  35. }
  36. }
  37. // Remove
  38. {
  39. var delta = state.length - promises.length;
  40. for(var i = state.length - 1; i > promises.length; --i) {
  41. var tmp = state[i];
  42. collection.remove(tmp);
  43. }
  44. state.splice(promises.length, delta);
  45. }
  46. var self = this;
  47. var syncId = ++this.syncId;
  48. //var dataProviders = this.dataProviders;
  49. var handleData = function(data, i) {
  50. if(syncId != self.syncId) {
  51. return;
  52. }
  53. //console.log("Syncing with data: ", data);
  54. var tmp = self.state[i];
  55. self.collection.remove(tmp);
  56. state[i] = data;
  57. if(data) { // FIXME only reject null and undefined.
  58. self.collection.add(data);
  59. }
  60. };
  61. _.each(promises, function(promise, i) {
  62. promise.done(function(data) {
  63. handleData(data, i);
  64. }).fail(function(json) {
  65. // TODO Factor this out into error handling code
  66. var data = {
  67. id: "error" + i,
  68. type: "error",
  69. data: json
  70. };
  71. handleData(data, i);
  72. });
  73. });
  74. }
  75. };
  76. ns.DefaultModel = Backbone.Model.extend({
  77. defaults: {
  78. //value: null,
  79. /*label: "" // FIXME As there could be many ways for crafting constraint labels,
  80. //associating a label only makes sense for view-models;*/
  81. }
  82. });
  83. ns.DefaultCollection = Backbone.Collection.extend({
  84. model: ns.DefaultModel
  85. });
  86. ns.fnDefaultId = function(item, row, offset) {
  87. return offset + row;
  88. };
  89. /**
  90. * Returns a key from the model based on the binding.
  91. *
  92. *
  93. */
  94. ns.getModelValue = function(model, key, binding) {
  95. var b = binding ? binding[key] : null;
  96. var result;
  97. if (b) {
  98. if (typeof b === 'function') {
  99. result = b(model);
  100. } else {
  101. result = model.get(b);
  102. }
  103. } else {
  104. result = model.get(key);
  105. }
  106. return result;
  107. };
  108. /**
  109. * A seemingly useful routing approach for separating configuration and
  110. * behaviour of routers.
  111. *
  112. * Usage Example:
  113. *
  114. * var appMethods = {...};
  115. *
  116. * var AppRouter = backboneUtils.AppRouter.extend({
  117. * routes: {
  118. * ...
  119. * }
  120. * });
  121. *
  122. * var appRouter = new AppRouter({app: app});
  123. *
  124. * Backbone.history.start();
  125. *
  126. *
  127. * Source:
  128. * http://lostechies.com/derickbailey/2012/01/02/reducing-backbone-routers-to-nothing-more-than-configuration/
  129. *
  130. */
  131. ns.AppRouter = Backbone.Router.extend({
  132. constructor : function(options) {
  133. Backbone.Router.prototype.constructor.call(this, options);
  134. if (this.routes) {
  135. this.processAppRoutes(options.app, this.routes);
  136. }
  137. },
  138. processAppRoutes : function(app, appRoutes) {
  139. var method, methodName;
  140. var route, routesLength;
  141. var routes = [];
  142. var router = this;
  143. for (route in appRoutes) {
  144. routes.unshift([ route, appRoutes[route] ]);
  145. }
  146. routesLength = routes.length;
  147. for ( var i = 0; i < routesLength; i++) {
  148. route = routes[i][0];
  149. methodName = routes[i][1];
  150. method = app[methodName];
  151. router.route(route, methodName, method);
  152. }
  153. }
  154. });
  155. ns.ControllerSlaveCollection = function(masterCollection, slaveCollection, fnTransform) {
  156. this.masterCollection = masterCollection;
  157. this.slaveCollection = slaveCollection;
  158. this.fnTransform = fnTransform;
  159. this.bind();
  160. };
  161. ns.ControllerSlaveCollection.prototype = {
  162. bind: function() {
  163. _.bindAll(this);
  164. this.masterCollection.on('add', this.onAdd);
  165. this.masterCollection.on('remove', this.onRemove);
  166. this.masterCollection.on('reset', this.onReset);
  167. },
  168. onAdd: function(model) {
  169. var newModel = fnTransform(model);
  170. this.slaveCollection.add(newModel);
  171. },
  172. onRemove: function(model) {
  173. var newModel = fnTransform(model);
  174. this.slaveCollection.remove(newModel.id);
  175. },
  176. onReset: function(collection) {
  177. var self = this;
  178. var newModels = collection.map(function(model) {
  179. var newModel = self.fnTransform(model);
  180. return newModel;
  181. });
  182. this.slaveCollection.reset(newModels);
  183. }
  184. };
  185. /**
  186. *
  187. * @param fnPromise A function that returns a promise that upon completion return the new state
  188. */
  189. ns.slaveCollection = function(masterCollection, slaveCollection, fnPromise) {
  190. masterCollection.on("add", function(model) {
  191. var clone = jQuery.extend(true, {}, model.attributes);
  192. var promise = fnPromise(clone);
  193. promise.done(function(newState) {
  194. // TODO Treat request order properly
  195. slaveCollection.add(newState);
  196. //var newState = fn(model.attributes);
  197. });
  198. });
  199. masterCollection.on("remove", function(model) {
  200. // TODO Delete by id AND/OR cid
  201. slaveCollection.remove(model.id);
  202. });
  203. masterCollection.on('reset', function(collection, options) {
  204. slaveCollection.reset();
  205. });
  206. };
  207. /**
  208. * fnId(item, row, offset)
  209. *
  210. */
  211. ns.BackboneSyncQuery = function(sparqlService, collection, fnPostProcess) {
  212. this.sparqlService = sparqlService;
  213. this.collection = collection ? collection : new ns.DefaultCollection();
  214. //this.fnId = fnId ? fnId : ns.fnDefaultId; // A function that returns the Id of items delivered by the tableModel
  215. this.fnPostProcess = fnPostProcess;
  216. this.taskCounter = 0;
  217. };
  218. ns.BackboneSyncQuery.prototype = {
  219. getCollection: function() {
  220. return this.collection;
  221. },
  222. sync: function(query) {
  223. var result = $.Deferred();
  224. this.taskCounter++;
  225. var queryExecution = this.sparqlService.executeSelect(query);
  226. var self = this;
  227. var tmp = self.taskCounter;
  228. queryExecution.success(function(jsonRs) {
  229. if(self.taskCounter != tmp) {
  230. result.fail();
  231. return;
  232. }
  233. var postProcessTask;
  234. if(self.fnPostProcess) {
  235. postProcessTask = self.fnPostProcess(jsonRs);
  236. } else {
  237. postProcessTask = queryExecution;
  238. }
  239. postProcessTask.success(function(jsonRs) {
  240. if(self.taskCounter != tmp) {
  241. result.fail();
  242. return;
  243. }
  244. self.processResult(jsonRs);
  245. //console.log("Rosult", jsonRs);
  246. result.resolve(jsonRs);
  247. //var resolveData = self.processResult(jsonRs);
  248. //result.resolve(resolveData);
  249. }).fail(function() {
  250. result.fail();
  251. });
  252. }).fail(function() {
  253. result.fail();
  254. });
  255. return result.promise();
  256. },
  257. processResult: function(jsonRs) {
  258. var offset = jsonRs.offset ? jsonRs.offset : 0;
  259. var bindings = jsonRs.results.bindings; //data;
  260. var destroyModels = [];
  261. this.collection.each(function(model) {
  262. destroyModels.push(model);
  263. });
  264. // The browsing experience is better, if first the new models are added
  265. // and then the old ones removed:
  266. // Removal of models may cause the page to shrink, and therefore change the location the user is viewing
  267. // before the new models are added
  268. //self.collection.reset();
  269. for(var i = 0; i < bindings.length; ++i) {
  270. var binding = bindings[i];
  271. //var id = self.fnId(binding, i, offset);
  272. var id = offset + i;
  273. binding.id = id;
  274. this.collection.add(binding);
  275. }
  276. for(var i = 0; i < destroyModels.length; ++i) {
  277. var model = destroyModels[i];
  278. model.destroy();
  279. }
  280. }
  281. };
  282. /**
  283. * Deprecated: Resource labels are set on the DOM level using SpanI18N
  284. *
  285. *
  286. * --
  287. * Returns a function that processes a json ResultSet:
  288. * - parses all plain Json Nodes to sparql.Node objects
  289. * - associates the label with each result set binding
  290. *
  291. * Note: Having the labels at the resources is convenient;
  292. * we could however store the labels in a model. This would allow adding
  293. * arbitrary information to resources.
  294. */
  295. ns.createDefaultPostProcessor = function(labelFetcher) {
  296. var fn = function(plainJsonRs) {
  297. //console.log("plainJsonRs", plainJsonRs);
  298. //var before = JSON.stringify(plainJsonRs);
  299. var jsonRs = uriUtils.parseJsonRs(plainJsonRs);
  300. //var after = JSON.stringify(plainJsonRs);
  301. /*
  302. if(before !== after) {
  303. console.log("Before: " + before);
  304. console.log("After: " + after);
  305. throw "Modification exception";
  306. }
  307. console.log("JSON RS IS NOW", jsonRs);
  308. */
  309. var uris = uriUtils.extractUrisFromParsedJsonRs(jsonRs);
  310. var result = $.Deferred();
  311. var task = labelFetcher.fetch(uris);
  312. task.done(function(labelInfo) {
  313. var transformed = uriUtils.transformJsonRs(jsonRs, function(node) {
  314. var result = {node: node};
  315. //console.log("Node", jsonRs, node);
  316. if(node && node.isUri()) {
  317. //if(node && (node.type === "uri" || (node instanceof sparql.Node && node.isUri()))) {
  318. var label = labelInfo.uriToLabel[node.value];
  319. if(!label) {
  320. var str = uriUtils.extractLabelFromUri(node.value);
  321. label = {value: str};
  322. }
  323. //console.log("Label for node " + node + " is " + label.value);
  324. result.label = label;
  325. }
  326. return result;
  327. });
  328. result.resolve(transformed); //jsonRs);
  329. }).fail(function() {
  330. result.fail();
  331. });
  332. return result.promise();
  333. };
  334. // Assign an id for debug reasons
  335. if(!ns.createDefaultPostProcessor.id) {
  336. ns.createDefaultPostProcessor.id = 0;
  337. }
  338. fn.id = ++ns.createDefaultPostProcessor.id;
  339. return fn;
  340. };
  341. ns.BackboneCollectionRdf = Backbone.Collection.extend({
  342. initialize: function(models, options) {
  343. this.options = options;
  344. //console.log("Collection:", sparqlService, postProcessor);
  345. this.syncer = new ns.BackboneSyncQuery(options.sparqlService, this, options.postProcessor);
  346. },
  347. sync: function(jsonRs) {
  348. if(!query) {
  349. query = this.options.query;
  350. }
  351. if(!query) {
  352. throw "No query specified";
  353. }
  354. this.syncer.sync(query);
  355. }
  356. });
  357. /**
  358. * fnId(item, row, offset)
  359. *
  360. */
  361. ns.SyncerRdfCollection = function(collection, fnPostProcess) {
  362. this.collection = collection ? collection : new ns.DefaultCollection();
  363. //this.fnId = fnId ? fnId : ns.fnDefaultId; // A function that returns the Id of items delivered by the tableModel
  364. this.fnPostProcess = fnPostProcess;
  365. this.taskCounter = 0;
  366. if(!ns.SyncerRdfCollection.id) {
  367. ns.SyncerRdfCollection.id = 0;
  368. }
  369. this.id = ++ns.SyncerRdfCollection.id;
  370. };
  371. ns.SyncerRdfCollection.prototype = {
  372. getCollection: function() {
  373. return this.collection;
  374. },
  375. setPostProcessFn: function(postProcessFn) {
  376. this.fnPostProcess = postProcessFn;
  377. },
  378. sync: function(jsonRs, offset) {
  379. //console.log("Sync [Start] ", this.id, "with " + JSON.stringify(jsonRs));
  380. var result = $.Deferred();
  381. ++this.taskCounter;
  382. var tmp = this.taskCounter;
  383. var self = this;
  384. if(this.fnPostProcess) {
  385. var postProcessTask = this.fnPostProcess(jsonRs);
  386. //console.log("Post processor for ", this.id, " is ", this.fnPostProcess.id);
  387. postProcessTask.done(function(procJsonRs) {
  388. //console.log("Sync [PostProcess] ", self.id, JSON.stringify(procJsonRs));
  389. if(self.taskCounter != tmp) {
  390. result.fail();
  391. console.log("Action was superseded by another update - Fail");
  392. return;
  393. }
  394. result.resolve(procJsonRs);
  395. }).fail(function() {
  396. result.fail();
  397. });
  398. } else {
  399. result.resolve(jsonRs, offset);
  400. }
  401. var last = result.pipe(function(resultJsonRs) {
  402. self.processResult(resultJsonRs);
  403. });
  404. return last; //result.promise();
  405. },
  406. processResult: function(jsonRs, offset) {
  407. //var offset = jsonRs.offset ? jsonRs.offset : 0;
  408. if(!offset) {
  409. offset = 0;
  410. }
  411. var bindings = jsonRs.results.bindings; //data;
  412. var newModels = [];
  413. for(var i = 0; i < bindings.length; ++i) {
  414. var binding = bindings[i];
  415. var id = offset + i;
  416. binding.id = id;
  417. newModels.push(binding);
  418. }
  419. //console.log("New models", JSON.stringify(newModels));
  420. //console.log("Sync [Reset] ", this.id, " with " + JSON.stringify(jsonRs));
  421. this.collection.reset(newModels);
  422. }
  423. };
  424. ns.BackboneSyncQueryCollection = Backbone.Collection.extend({
  425. initialize: function(models, options) {
  426. this.options = options;
  427. //console.log("Collection:", sparqlService, postProcessor);
  428. this.syncer = new ns.BackboneSyncQuery(options.sparqlService, this, options.postProcessor);
  429. },
  430. sync: function(query) {
  431. if(!query) {
  432. query = this.options.query;
  433. }
  434. if(!query) {
  435. throw "No query specified";
  436. }
  437. this.syncer.sync(query);
  438. }
  439. });
  440. })();