PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/assets/js/app.js

https://bitbucket.org/gkarthik_92/cured3
JavaScript | 427 lines | 398 code | 7 blank | 22 comment | 18 complexity | a00b9c533970f97420d1638604c7c5e3 MD5 | raw file
  1. //
  2. //-- Defining the App
  3. //
  4. Cure = new Backbone.Marionette.Application();
  5. //
  6. //-- Defining our collections
  7. //
  8. NodeCollection = Backbone.Collection.extend({
  9. model : Node,
  10. initialize: function() {
  11. //This add is for the seed node alone.
  12. this.on("add",function(){
  13. Cure.updatepositions();
  14. Cure.render_network(this.toJSON()[0]);
  15. });
  16. this.on("remove",function() {
  17. Cure.updatepositions();
  18. Cure.render_network(this.toJSON()[0]);
  19. });
  20. }
  21. });
  22. //
  23. //-- Defining our models
  24. //
  25. Node = Backbone.RelationalModel.extend({
  26. defaults : {
  27. 'name' : '',
  28. 'options' : {
  29. content:'Hello World!',
  30. value: '0'
  31. },
  32. edit : 0
  33. },
  34. url: "./",
  35. initialize: function() {
  36. this.bind("add:children", function() {
  37. Cure.updatepositions();
  38. Cure.render_network(Cure.NodeCollection.toJSON()[0]);
  39. });
  40. this.bind("change", function() {
  41. Cure.render_network(Cure.NodeCollection.toJSON()[0]);
  42. });
  43. Cure.NodeCollection.add(this);
  44. },
  45. relations: [{
  46. type: Backbone.HasMany,
  47. key: 'children',
  48. relatedModel: 'Node',
  49. reverseRelation: {
  50. key: 'parentNode',
  51. includeInJSON: false
  52. }
  53. }]
  54. });
  55. //
  56. //-- Defining our views
  57. //
  58. NodeView = Backbone.Marionette.ItemView.extend({
  59. //-- View to manipulate each single node
  60. tagName: 'div',
  61. ui: {
  62. input: ".edit"
  63. },
  64. template: "#nodeTemplate",
  65. events: {
  66. 'click button.addchildren' : 'addChildren',
  67. 'click button.delete' : 'remove',
  68. 'dblclick .name': 'edit',
  69. 'keypress .edit' : 'updateOnEnter',
  70. 'blur .edit' : 'close'
  71. },
  72. initialize: function(){
  73. _.bindAll(this, 'remove', 'addChildren');
  74. this.model.bind('change', this.render);
  75. this.model.bind('remove', this.remove);
  76. this.$el.addClass("node");
  77. },
  78. onBeforeRender: function(){
  79. if(this.model.get('x0')!=undefined)
  80. {
  81. $(this.el).css({'margin-left': this.model.get('x0')+"px",
  82. 'margin-top': this.model.get('y0')+"px"});
  83. }
  84. $(this.el).stop(true,false).animate({'margin-left': (this.model.get('x')-(($(this.el).width())/2))+"px",
  85. 'margin-top': (this.model.get('y')+10)+"px"},500);
  86. },
  87. updateOnEnter: function(e){
  88. if(e.which == 13){
  89. this.close();
  90. }
  91. },
  92. close: function(){
  93. var value = this.ui.input.val().trim();
  94. if(value) {
  95. this.model.set('name', value);
  96. }
  97. this.$el.removeClass('editing');
  98. },
  99. edit: function(){
  100. this.$el.addClass('editing');
  101. this.ui.input.focus();
  102. },
  103. remove: function(){
  104. if(Cure.NodeCollection.length > 1) {
  105. $(this.el).remove();
  106. Cure.delete_all_children(this.model);
  107. this.model.destroy();
  108. }
  109. },
  110. addChildren: function(){
  111. var name = 0;
  112. if(this.model.parentNode==null) {
  113. name = Cure.branch;
  114. Cure.branch++;
  115. } else {
  116. name = this.model.get('name')+"."+this.model.get('children').length;
  117. }
  118. var newNode = new Node({'name' : name, 'id':'node'+name});
  119. newNode.parentNode = this.model;
  120. this.model.get('children').add(newNode);
  121. }
  122. });
  123. NodeCollectionView = Backbone.Marionette.CollectionView.extend({
  124. //-- View to manipulate and display list of all nodes in collection
  125. itemView: NodeView,
  126. initialize: function() {
  127. this.collection.bind('add', this.onModelAdded);
  128. },
  129. onModelAdded: function(addedModel) {
  130. var newNodeview = new NodeView({ model: addedModel });
  131. newNodeview.render();
  132. }
  133. });
  134. var shownode_html = $("#JSONtemplate").html();
  135. var nodeedit_html = $('#Attrtemplate').html();
  136. JSONItemView = Backbone.Marionette.ItemView.extend({
  137. //-- View to render JSON
  138. model: Node,
  139. ui:{
  140. jsondata: ".jsonview_data",
  141. showjson: ".showjson",
  142. attreditwrapper: ".attreditwrapper",
  143. attredit: ".attredit",
  144. input: ".edit",
  145. key: ".attrkey",
  146. },
  147. events:{
  148. 'click .showjson': 'ShowJSON',
  149. 'blur .jsonview_data' : 'HideJSON',
  150. 'click .showattr': 'ShowAttr',
  151. 'dblclick .attredit': 'editAttr',
  152. 'keypress .edit' : 'onEnter',
  153. 'blur .edit' : 'updateAttr',
  154. 'click .editdone': 'doneEdit'
  155. },
  156. tagName: "tr",
  157. initialize: function() {
  158. this.model.bind('add:children', this.render);
  159. this.model.bind('change', this.render);
  160. this.model.on('change:edit', function () {
  161. if (this.model.get('edit') != 0)
  162. {
  163. this.$el.addClass('editnode');
  164. }
  165. else
  166. {
  167. this.$el.removeClass('editnode');
  168. }
  169. }, this);
  170. },
  171. template : function(serialized_model) {
  172. var name = serialized_model.name;
  173. var options = serialized_model.options;
  174. var jsondata = Cure.prettyPrint(serialized_model);
  175. if(serialized_model.edit == 0)
  176. {
  177. return _.template(shownode_html, {name : name,jsondata : jsondata}, {variable: 'args'});
  178. }
  179. else
  180. {
  181. return _.template(nodeedit_html, {name : name,options : options}, {variable: 'args'});
  182. }
  183. },
  184. editAttr: function(e){
  185. var field = $(e.currentTarget);
  186. field.addClass("editing");
  187. $(".edit", field).focus();
  188. },
  189. ShowJSON: function(){
  190. this.ui.showjson.addClass("disabled");
  191. this.ui.jsondata.css({'display':'block'});
  192. this.ui.jsondata.focus();
  193. },
  194. HideJSON: function(){
  195. this.ui.jsondata.css({'display':'none'});
  196. this.ui.showjson.removeClass("disabled");
  197. },
  198. onEnter: function(e){
  199. if(e.which == 13){
  200. this.updateAttr($(e.currentTarget));
  201. }
  202. },
  203. updateAttr: function(field){
  204. if(field instanceof jQuery.Event)
  205. {
  206. field = $(field.currentTarget);
  207. }
  208. console.log(field);
  209. if(field.hasClass("modeloption"))
  210. {
  211. var data = {};
  212. data["options"] = this.model.get('options');
  213. data["options"][field.attr('id')] = field.val();
  214. this.model.set(data);
  215. }
  216. else
  217. {
  218. var data = {};
  219. data[field.attr('id')] = field.val();
  220. this.model.set(data);
  221. }
  222. this.render();
  223. },
  224. ShowAttr: function(){
  225. this.model.set('edit',1);
  226. },
  227. doneEdit: function(){
  228. this.model.set('edit',0);
  229. }
  230. });
  231. JSONCollectionView = Backbone.Marionette.CollectionView.extend({
  232. //-- View to render JSON
  233. itemView: JSONItemView,
  234. collection: NodeCollection,
  235. initialize: function() {
  236. this.collection.bind('remove', this.render);
  237. }
  238. });
  239. //
  240. //-- Utilities / Helpers
  241. //
  242. //-- Pretty Print JSON.
  243. //-- Ref : http://stackoverflow.com/questions/4810841/json-pretty-print-using-javascript
  244. Cure.prettyPrint = function (json) {
  245. if (typeof json != 'string') {
  246. json = JSON.stringify(json, undefined, 2);
  247. }
  248. json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  249. return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
  250. var cls = 'number';
  251. if (/^"/.test(match)) {
  252. if (/:$/.test(match)) {
  253. cls = 'key';
  254. } else {
  255. cls = 'string';
  256. }
  257. } else if (/true|false/.test(match)) {
  258. cls = 'boolean';
  259. } else if (/null/.test(match)) {
  260. cls = 'null';
  261. }
  262. return match;
  263. });
  264. }
  265. //
  266. //-- Get JSON from d3 to BackBone
  267. //
  268. Cure.updatepositions = function ()
  269. {
  270. var d3nodes = Cure.cluster.nodes(Cure.NodeCollection.toJSON()[0]);
  271. d3nodes.forEach(function(d) { d.y = d.depth * 130 ;});
  272. d3nodes.forEach(function(d) {
  273. d.x0 = d.x;
  274. d.y0 = d.y;
  275. });
  276. for(var temp in Cure.NodeCollection["models"])
  277. {
  278. for(var innerTemp in d3nodes)
  279. {
  280. if(String(d3nodes[innerTemp].name)==String(Cure.NodeCollection["models"][temp].get('name')))
  281. {
  282. Cure.NodeCollection["models"][temp].set("x",d3nodes[innerTemp].x);
  283. Cure.NodeCollection["models"][temp].set("y",d3nodes[innerTemp].y);
  284. Cure.NodeCollection["models"][temp].set("x0",d3nodes[innerTemp].x0);
  285. Cure.NodeCollection["models"][temp].set("y0",d3nodes[innerTemp].y0);
  286. }
  287. }
  288. }
  289. }
  290. //
  291. //-- Function to delete all children of a node
  292. //
  293. Cure.delete_all_children = function (seednode)
  294. {
  295. var children = seednode.get('children');
  296. if(seednode.get('children').length>0)
  297. {
  298. for(var temp in children.models)
  299. {
  300. Cure.delete_all_children(children.models[temp]);
  301. children.models[temp].destroy();
  302. }
  303. }
  304. }
  305. //
  306. //-- Render d3 Network
  307. //
  308. Cure.render_network = function(dataset)
  309. {
  310. var nodes = Cure.cluster.nodes(dataset),
  311. links = Cure.cluster.links(nodes);
  312. nodes.forEach(function(d) { d.y = d.depth * 130; });
  313. var link = Cure.svg.selectAll(".link")
  314. .data(links);
  315. link.enter()
  316. .insert("svg:path", "g")
  317. .attr("class", "link")
  318. .attr("d", function(d) {
  319. var o = {x: dataset.x0, y: dataset.y0};
  320. return Cure.diagonal({source: o, target: o});
  321. });
  322. link.transition()
  323. .duration(Cure.duration)
  324. .attr("d", Cure.diagonal);
  325. link.exit().transition()
  326. .duration(Cure.duration)
  327. .attr("d", function(d) {
  328. var o = {x: dataset.x, y: dataset.y};
  329. return Cure.diagonal({source: o, target: o});
  330. })
  331. .remove();
  332. nodes.forEach(function(d) {
  333. d.x0 = d.x;
  334. d.y0 = d.y;
  335. });
  336. }
  337. //
  338. //-- App init!
  339. //
  340. Cure.addInitializer(function(options){
  341. //Declaring D3 Variables
  342. Cure.width = $("#svgwrapper").width(),
  343. Cure.height = $("#svgwrapper").height(),
  344. Cure.duration = 500,
  345. Cure.cluster = d3.layout.tree()
  346. .size([Cure.width, Cure.height]),
  347. Cure.diagonal = d3.svg.diagonal()
  348. .projection(function(d) { return [d.x, d.y]; });
  349. Cure.svg = d3.select("svg").attr("width", Cure.width)
  350. .attr("height", Cure.height)
  351. .append("svg:g")
  352. .attr("transform", "translate(0,40)");
  353. Cure.NodeCollection = new NodeCollection();
  354. Cure.NodeCollectionView = new NodeCollectionView({ collection: Cure.NodeCollection }),
  355. Cure.JSONCollectionView = new JSONCollectionView({ collection: Cure.NodeCollection });
  356. //Assign View to Region
  357. Cure.addRegions({
  358. TreeRegion: "#svgwrapper",
  359. JsonRegion: "#json_structure"
  360. });
  361. Cure.TreeRegion.show(Cure.NodeCollectionView);
  362. Cure.JsonRegion.show(Cure.JSONCollectionView);
  363. //Add Root Node to Collection
  364. Cure.RootNode = new Node({'name':'ROOT'})
  365. Cure.branch = 1;
  366. });
  367. Cure.start();
  368. //-- TASKS / IDEAS:
  369. //-- Might be fun to have a 'autogenerate network with X nodes and X tiers' random function, not exactly
  370. //-- relevant to the exact task at hand but might make you more comertable with how to leverage this collection/model structure
  371. //-- To note, please look at the formatting differences I made, we want to make sure the code says clean (and to help with the fact that I need to read it)
  372. //-- (WILL DO)
  373. //-- (TODO) Question to ask yourself about Cure.NodeCollection.counter, do you really need it? Hint: http://puff.me.uk/ss/B0DvC.png.
  374. //-- (DONE) - Used Cure.NodeCollection.length to monitor number of models in the collection. I just have to ask, what is puff.me.uk? Some ftp server?
  375. //-- ALMOST THERE
  376. //-- (DONE)
  377. //-- (TODO) _ templates not in a big string but using a script
  378. //-- (DONE)
  379. //-- (TODO) - "smarter" name convention to suggest depth level as well
  380. // - also currently parentNode == null so that will have to be fixed to
  381. // - to access parent
  382. //-- (DONE)
  383. //-- (TODO) - keep this.options around on node to act as a storage area for metadata
  384. //-- (DONE)
  385. //-- (TODO) - convert $json_structure.html() into d3 drawing
  386. //-- (DONE)
  387. //-- (TODO) - On click of d3 node, get the model repersentation of that in Backbone collection
  388. //-- (DONE - ItemView Linked with every node)
  389. //-- (TODO) - input event for name update
  390. //-- (TODO) - attributes to literal objects
  391. //-- (DONE) - Most of the d3 removed since nodes are rendered by Marionette.
  392. //-- (TODO) - question of d3 >> search through backbone || backbone with paths to draw networks
  393. //-- (DONE) - D3 renders Paths. BackBone renders the nodes.
  394. //-- (TODO) - Edit List with double click. On double click open up edit panel. Use Boolean value in node to show edit panel.
  395. //-- (TODO) - Make decision tree with light weights.
  396. //-- (TODO) - className to highlight nodes.
  397. //-- (TODO) - .bind to .bondTo
  398. //-- AWESOME START!