PageRenderTime 105ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/app/javascript/shared-package/goals.js

https://gitlab.com/gregtyka/server
JavaScript | 742 lines | 595 code | 94 blank | 53 comment | 70 complexity | 91248a6902cfd883652c4d23403dc4ec MD5 | raw file
  1. var Goal = Backbone.Model.extend({
  2. defaults: {
  3. active: false,
  4. complete: false,
  5. progress: 0,
  6. title: "Unnamed goal",
  7. objectives: [],
  8. target: null,
  9. creator: "user"
  10. },
  11. urlRoot: "/api/v1/user/goals",
  12. initialize: function() {
  13. // defaults for new models (e.g. not from server)
  14. // default created and updated values
  15. if (!this.has("created")) {
  16. var now = new Date().toISOString();
  17. this.set({created: now, updated: now});
  18. }
  19. // default progress value for all objectives
  20. _.each(this.get("objectives"), function(o) {
  21. if (o.progress == null) {
  22. o.progress = 0;
  23. o.status = "not-started";
  24. }
  25. if (o.status == null) {
  26. o.status = 'not-started';
  27. }
  28. });
  29. // a bunch of stuff needed to display goals in views. might need to be
  30. // refactored.
  31. this.calcDependents();
  32. this.bind("change", this.fireCustom, this);
  33. },
  34. calcDependents: function() {
  35. var progress = this.calcTotalProgress(this.get("objectives"));
  36. var objectiveWidth = 100 / this.get("objectives").length;
  37. _.each(this.get("objectives"), function(obj) {
  38. Goal.calcObjectiveDependents(obj, objectiveWidth);
  39. });
  40. this.set({
  41. progress: progress,
  42. progressStr: Goal.floatToPercentageStr(progress),
  43. complete: progress >= 1,
  44. // used to display 3/5 in goal summary area
  45. objectiveProgress: _.filter(this.get("objectives"), function(obj) {
  46. return obj.progress >= 1;
  47. }).length,
  48. // used to maintain sorted order in a GoalCollection
  49. updatedTime: parseISO8601(this.get("updated")).getTime()
  50. }, {silent: true});
  51. },
  52. calcTotalProgress: function(objectives) {
  53. objectives = objectives || this.get("objectives");
  54. var progress = 0;
  55. if (objectives.length) {
  56. progress = _.reduce(objectives, function(p, ob) { return p + ob.progress; }, 0);
  57. if (objectives.length > 0) {
  58. progress = progress / objectives.length;
  59. } else {
  60. progress = 0;
  61. }
  62. }
  63. return progress;
  64. },
  65. fireCustom: function() {
  66. this.calcDependents();
  67. if (this.hasChanged("progress")) {
  68. // we want to fire these events after all other listeners to 'change'
  69. // have had a chance to run
  70. var toFire = [];
  71. // check for goal completion
  72. if (this.get("progress") >= 1) {
  73. toFire.push(["goalcompleted", this]);
  74. }
  75. else {
  76. // now look for updated objectives
  77. oldObjectives = this.previous("objectives");
  78. _.each(this.get("objectives"), function(newObj, i) {
  79. var oldObj = oldObjectives[i];
  80. if (newObj.progress > oldObj.progress) {
  81. toFire.push(["progressed", this, newObj]);
  82. if (newObj.progress >= 1) {
  83. toFire.push(["completed", this, newObj]);
  84. }
  85. }
  86. }, this);
  87. }
  88. if (_.any(toFire)) {
  89. // register a callback to execute at the end of the rest of the
  90. // change callbacks
  91. this.collection.bind("change", function callback() {
  92. // this callback should only run once, so immediately unbind
  93. this.unbind("change", callback);
  94. // trigger all change notifications
  95. _.each(toFire, function(triggerArgs) {
  96. this.trigger.apply(this, triggerArgs);
  97. }, this);
  98. }, this.collection);
  99. }
  100. }
  101. }
  102. }, {
  103. calcObjectiveDependents: function(objective, objectiveWidth) {
  104. objective.complete = objective.progress >= 1;
  105. objective.progressStr = Goal.floatToPercentageStr(objective.progress);
  106. objective.iconFillHeight = Goal.calcIconFillHeight(objective);
  107. objective.objectiveWidth = objectiveWidth;
  108. objective.isURL = (objective.type == "GoalObjectiveVisitURL");
  109. objective.isVideo = (objective.type == "GoalObjectiveWatchVideo");
  110. objective.isAnyVideo = (objective.type == "GoalObjectiveAnyVideo");
  111. objective.isExercise = (objective.type == "GoalObjectiveExerciseProficiency");
  112. objective.isAnyExercise = (objective.type == "GoalObjectiveAnyExerciseProficiency");
  113. },
  114. calcIconFillHeight: function(objective) {
  115. var height = objective.type.toLowerCase().indexOf("exercise") >= 1 ? 13 : 12;
  116. var offset = objective.type.toLowerCase().indexOf("exercise") >= 1 ? 4 : 6;
  117. return Math.ceil(objective.progress * height) + offset;
  118. },
  119. floatToPercentageStr: function(progress) {
  120. return (progress * 100).toFixed(0);
  121. },
  122. objectiveUrlForType: {
  123. GoalObjectiveVisitURL: function(objective) {
  124. return objective.url;
  125. },
  126. GoalObjectiveWatchVideo: function(objective) {
  127. return "/video/" + objective.internal_id;
  128. },
  129. GoalObjectiveAnyVideo: function(objective) {
  130. return "/";
  131. },
  132. GoalObjectiveExerciseProficiency: function(objective) {
  133. return "/exercise/" + objective.internal_id;
  134. },
  135. GoalObjectiveAnyExerciseProficiency: function(objective) {
  136. return "/#browse";
  137. }
  138. },
  139. objectiveUrl: function(objective) {
  140. return Goal.objectiveUrlForType[objective.type](objective);
  141. },
  142. defaultVideoProcessGoalAttrs_: {
  143. title: "צפה בחמישה סרטונים",
  144. objectives: [
  145. { description: "סרטון כלשהו", type: "GoalObjectiveAnyVideo" },
  146. { description: "סרטון כלשהו", type: "GoalObjectiveAnyVideo" },
  147. { description: "סרטון כלשהו", type: "GoalObjectiveAnyVideo" },
  148. { description: "סרטון כלשהו", type: "GoalObjectiveAnyVideo" },
  149. { description: "סרטון כלשהו", type: "GoalObjectiveAnyVideo" }
  150. ]
  151. },
  152. defaultExerciseProcessGoalAttrs_: {
  153. title: "השלם חמישה תרגילים",
  154. objectives: [
  155. { description: "תרגיל כלשהו", type: "GoalObjectiveAnyExerciseProficiency" },
  156. { description: "תרגיל כלשהו", type: "GoalObjectiveAnyExerciseProficiency" },
  157. { description: "תרגיל כלשהו", type: "GoalObjectiveAnyExerciseProficiency" },
  158. { description: "תרגיל כלשהו", type: "GoalObjectiveAnyExerciseProficiency" },
  159. { description: "תרגיל כלשהו", type: "GoalObjectiveAnyExerciseProficiency" }
  160. ]
  161. },
  162. // TODO: investigate if this is still used.
  163. exidToExerciseName: function(exid) {
  164. return exid[0].toUpperCase() + exid.slice(1).replace(/_/g, ' ');
  165. },
  166. // TODO: investigate if this is still used.
  167. GoalObjectiveExerciseProficiency: function(exid) {
  168. var obj = {
  169. type: "GoalObjectiveExerciseProficiency",
  170. internal_id: exid,
  171. description: Goal.exidToExerciseName(exid)
  172. };
  173. obj.url = Goal.objectiveUrl(obj);
  174. return obj;
  175. }
  176. });
  177. var GoalCollection = Backbone.Collection.extend({
  178. model: Goal,
  179. initialize: function() {
  180. this.updateActive();
  181. // ensure updateActive is called whenever the collection changes
  182. this.bind("add", this.updateActive, this);
  183. this.bind("remove", this.updateActive, this);
  184. this.bind("reset", this.updateActive, this);
  185. },
  186. url: "/api/v1/user/goals",
  187. comparator: function(goal) {
  188. // display most recently updated goal at the top of the list.
  189. // http://stackoverflow.com/questions/5636812/sorting-strings-in-reverse-order-with-backbone-js/5639070#5639070
  190. return -goal.get("updatedTime");
  191. },
  192. active: function(goal) {
  193. var current = this.find(function(g) {return g.get("active");}) || null;
  194. if (goal && goal !== current) {
  195. // set active
  196. if (current !== null) {
  197. current.set({active: false});
  198. }
  199. goal.set({active: true});
  200. current = goal;
  201. }
  202. return current;
  203. },
  204. updateActive: function() {
  205. this.active(this.findActiveGoal());
  206. },
  207. incrementalUpdate: function(updatedGoals) {
  208. _.each(updatedGoals, function(newGoal) {
  209. oldGoal = this.get(newGoal.id) || null;
  210. if (oldGoal !== null) {
  211. oldGoal.set(newGoal);
  212. }
  213. else {
  214. // todo: remove this, do something better
  215. KAConsole.log("Error: brand new goal appeared from somewhere", newGoal);
  216. }
  217. }, this);
  218. },
  219. findGoalWithObjective: function(internalId, specificType, generalType) {
  220. return this.find(function(goal) {
  221. // find a goal with an objective for this exact entity
  222. return _.find(goal.get("objectives"), function(ob) {
  223. return ob.type == specificType && internalId == ob.internal_id;
  224. });
  225. }) || this.find(function(goal) {
  226. // otherwise find a goal with any entity proficiency
  227. return _.find(goal.get("objectives"), function(ob) {
  228. return ob.type == generalType;
  229. });
  230. }) || null;
  231. },
  232. // find the most appriate goal to display for a given URL
  233. findActiveGoal: function() {
  234. var matchingGoal = null;
  235. if (window.location.pathname.indexOf("/exercise") === 0 && window.userExerciseName) {
  236. matchingGoal = this.findGoalWithObjective(userExerciseName,
  237. "GoalObjectiveExerciseProficiency",
  238. "GoalObjectiveAnyExerciseProficiency");
  239. } else if (window.location.pathname.indexOf("/video") === 0 &&
  240. typeof Video.readableId !== "undefined") {
  241. matchingGoal = this.findGoalWithObjective(Video.readableId,
  242. "GoalObjectiveWatchVideo", "GoalObjectiveAnyVideo");
  243. }
  244. // if we're not on a matching exercise or video page, just show the
  245. // most recently upated one
  246. if (matchingGoal === null) {
  247. matchingGoal = this.at(0); // comparator is most recently updated
  248. }
  249. return matchingGoal;
  250. },
  251. processGoalContext: function() {
  252. return {
  253. hasExercise: this.any(function(goal) {
  254. return _.any(goal.get("objectives"), function(obj) {
  255. return obj.type === "GoalObjectiveAnyExerciseProficiency";
  256. });
  257. }),
  258. hasVideo: this.any(function(goal) {
  259. return _.any(goal.get("objectives"), function(obj) {
  260. return obj.type === "GoalObjectiveAnyVideo";
  261. });
  262. })
  263. };
  264. }
  265. });
  266. var GoalBookView = Backbone.View.extend({
  267. template: Templates.get("shared.goalbook"),
  268. isVisible: false,
  269. needsRerender: true,
  270. initialize: function() {
  271. $(this.el)
  272. .delegate(".close-button", "click", $.proxy(this.hide, this))
  273. // listen to archive button on goals
  274. .delegate(".goal.recently-completed", "mouseenter mouseleave", function(e) {
  275. var el = $(e.currentTarget);
  276. if (e.type == "mouseenter") {
  277. el.find(".goal-description .summary-light").hide();
  278. el.find(".goal-description .goal-controls").show();
  279. } else {
  280. el.find(".goal-description .goal-controls").hide();
  281. el.find(".goal-description .summary-light").show();
  282. }
  283. })
  284. .delegate(".archive", "click", $.proxy(function(e) {
  285. var jel = $(e.target).closest(".goal");
  286. var goal = this.model.get(jel.data("id"));
  287. this.animateGoalToHistory(jel).then($.proxy(function() {
  288. this.model.remove(goal);
  289. }, this));
  290. }, this))
  291. .delegate(".new-goal", "click", $.proxy(function(e) {
  292. e.preventDefault();
  293. this.hide();
  294. newGoalDialog.show();
  295. }, this))
  296. .delegate(".goal-history", "click",
  297. $.proxy(this.goalHistoryButtonClicked, this));
  298. this.model.bind("change", this.render, this);
  299. this.model.bind("reset", this.render, this);
  300. this.model.bind("remove", this.render, this);
  301. this.model.bind("add", this.added, this);
  302. this.model.bind("goalcompleted", this.show, this);
  303. },
  304. show: function() {
  305. this.isVisible = true;
  306. // render if necessary
  307. if (this.needsRerender) {
  308. this.render();
  309. }
  310. var that = this;
  311. // animate on the way down
  312. return $(this.el).slideDown("fast", function() {
  313. // listen for escape key
  314. $(document).bind("keyup.goalbook", function(e) {
  315. if (e.which == 27) {
  316. that.hide();
  317. }
  318. });
  319. // close the goalbook if user clicks elsewhere on page
  320. $("body").bind("click.goalbook", function(e) {
  321. if ($(e.target).closest("#goals-nav-container").length === 0) {
  322. that.hide();
  323. }
  324. });
  325. });
  326. },
  327. hide: function() {
  328. this.isVisible = false;
  329. $(document).unbind("keyup.goalbook");
  330. $("body").unbind("click.goalbook");
  331. // if there are completed goals, move them to history before closing
  332. var completed = this.model.filter(function(goal) { return goal.get("complete"); });
  333. var completedEls = this.$(".recently-completed");
  334. if (completedEls.length > 0) {
  335. this.animateThenHide(completedEls);
  336. } else {
  337. return $(this.el).slideUp("fast");
  338. }
  339. },
  340. goalHistoryButtonClicked: function(e) {
  341. // Stay on the page if the user is already looking at her profile
  342. // TODO: if we care about metaKey / control key / middle key working,
  343. // then there is probably more to test here.
  344. if (typeof Profile !== "undefined" && !e.metaKey) {
  345. e.preventDefault();
  346. this.hide();
  347. Profile.router.navigate("/goals", true);
  348. }
  349. },
  350. added: function(goal, options) {
  351. this.needsRerender = true;
  352. this.show();
  353. // add a highlight to the new goal
  354. $(".goal[data-id=" + goal.get("id") + "]").effect("highlight", {}, 2500);
  355. },
  356. animateThenHide: function(els) {
  357. var goals = _.map(els, function(el) {
  358. return this.model.get($(el).data("id"));
  359. }, this);
  360. // wait for the animation to complete and then close the goalbook
  361. this.animateGoalToHistory(els).then($.proxy(function() {
  362. $(this.el).slideUp("fast").promise().then($.proxy(function() {
  363. this.model.remove(goals);
  364. }, this));
  365. }, this));
  366. },
  367. render: function() {
  368. var jel = $(this.el);
  369. // delay rendering until the view is actually visible
  370. if (!this.isVisible) {
  371. this.needsRerender = true;
  372. }
  373. else {
  374. KAConsole.log("rendering GoalBookView", this);
  375. this.needsRerender = false;
  376. var json = _.pluck(this.model.models, "attributes");
  377. jel.html(this.template({
  378. goals: json,
  379. profileRoot: KA.profileRoot
  380. }));
  381. }
  382. return this;
  383. },
  384. animateGoalToHistory: function(els) {
  385. var btnGoalHistory = this.$("a.goal-history");
  386. var promises = $(els).map(function(i, el) {
  387. var dfd = $.Deferred();
  388. var jel = $(el);
  389. jel .children()
  390. .each(function() {
  391. $(this).css("overflow", "hidden").css("height", $(this).height());
  392. })
  393. .end()
  394. .delay(500)
  395. .animate({
  396. width: btnGoalHistory.width(),
  397. left: btnGoalHistory.position().left
  398. })
  399. .animate({
  400. top: btnGoalHistory.position().top - jel.position().top,
  401. height: "0",
  402. opacity: "toggle"
  403. },
  404. "easeInOutCubic",
  405. function() {
  406. $(this).remove();
  407. dfd.resolve();
  408. }
  409. );
  410. return dfd.promise();
  411. }).get();
  412. // once all the animations are done, make the history button glow
  413. var button = $.Deferred();
  414. $.when.apply(null, promises).then(function() {
  415. btnGoalHistory
  416. .animate({backgroundColor: "orange"})
  417. .animate({backgroundColor: "#ddd"}, button.resolve);
  418. });
  419. // return a promise that the history button is done animating
  420. return button.promise();
  421. }
  422. });
  423. var GoalSummaryView = Backbone.View.extend({
  424. template: Templates.get("shared.goal-summary-area"),
  425. initialize: function(args) {
  426. $(this.el).delegate("#goals-drawer", "click",
  427. $.proxy(args.goalBookView.show, args.goalBookView));
  428. this.model.bind("change", this.render, this);
  429. this.model.bind("reset", this.render, this);
  430. this.model.bind("remove", this.render, this);
  431. this.model.bind("add", this.render, this);
  432. this.model.bind("completed", this.justFinishedObjective, this);
  433. },
  434. render: function() {
  435. KAConsole.log("rendering GoalSummaryView", this);
  436. var active = this.model.active() || null;
  437. if (active !== null) {
  438. $(this.el).html(this.template(active.attributes));
  439. }
  440. else {
  441. // todo: put create a goal button here?
  442. $(this.el).empty();
  443. }
  444. return this;
  445. },
  446. justFinishedObjective: function(newGoal, newObj) {
  447. this.render();
  448. this.$("#goals-drawer").effect("highlight", {}, 2500);
  449. }
  450. });
  451. function finishLoadingMapsPackage() {
  452. KAConsole.log("Loaded Google Maps.");
  453. dynamicLoadPackage_maps(function(status, progress) {
  454. if (status == "complete") {
  455. KAConsole.log("Loaded maps package.");
  456. } else if (status == "failed") {
  457. KAConsole.log("Failed to load maps package.");
  458. setTimeout(finishLoadingMapsPackage, 5000); // Try again in 5 seconds
  459. } else if (status == "progress") {
  460. KAConsole.log("Maps package " + (progress * 100).toFixed(0) + "% loaded.");
  461. if (newCustomGoalDialog) {
  462. newCustomGoalDialog.$(".progress-bar")
  463. .progressbar("value", progress * 100);
  464. }
  465. }
  466. });
  467. }
  468. var NewGoalView = Backbone.View.extend({
  469. template: Templates.get("shared.goal-new"),
  470. events: {
  471. "click .newgoal.custom": "createCustomGoal",
  472. "click .newgoal.five_exercises": "createExerciseProcessGoal",
  473. "click .newgoal.five_videos": "createVideoProcessGoal"
  474. },
  475. initialize: function() {
  476. this.render();
  477. },
  478. render: function() {
  479. var context = this.model.processGoalContext();
  480. $(this.el).html(this.template(context));
  481. this.hookup();
  482. return this;
  483. },
  484. hookup: function() {
  485. var that = this;
  486. this.$(".newgoal").hoverIntent(
  487. function hfa(evt) {
  488. if ($(this).hasClass("disabled")) {
  489. return;
  490. }
  491. that.$(".newgoal").not(this).not(".disabled").hoverFlow(
  492. evt.type, { opacity: 0.2},
  493. 750, "easeInOutCubic");
  494. $(".info.pos-left", this).hoverFlow(
  495. evt.type, { left: "+=30px", opacity: "show" },
  496. 350, "easeInOutCubic");
  497. $(".info.pos-right, .info.pos-top", this).hoverFlow(
  498. evt.type, { right: "+=30px", opacity: "show" },
  499. 350, "easeInOutCubic");
  500. },
  501. function hfo(evt) {
  502. if ($(this).hasClass("disabled")) {
  503. return;
  504. }
  505. that.$(".newgoal").not(this).not(".disabled").hoverFlow(
  506. evt.type, { opacity: 1}, 175, "easeInOutCubic");
  507. $(".info.pos-left", this).hoverFlow(
  508. evt.type, { left: "-=30px", opacity: "hide" },
  509. 150, "easeInOutCubic");
  510. $(".info.pos-right, .info.pos-top", this).hoverFlow(
  511. evt.type, { right: "-=30px", opacity: "hide" },
  512. 150, "easeInOutCubic");
  513. }
  514. );
  515. },
  516. createVideoProcessGoal: function(e) {
  517. e.preventDefault();
  518. if ($(e.currentTarget).hasClass("disabled")) return;
  519. var goal = new Goal(Goal.defaultVideoProcessGoalAttrs_);
  520. this.createSimpleGoal(goal);
  521. },
  522. createExerciseProcessGoal: function(e) {
  523. e.preventDefault();
  524. if ($(e.currentTarget).hasClass("disabled")) return;
  525. var goal = new Goal(Goal.defaultExerciseProcessGoalAttrs_);
  526. this.createSimpleGoal(goal);
  527. },
  528. createSimpleGoal: function(goal) {
  529. this.model.add(goal);
  530. goal.save().fail($.proxy(function() {
  531. KAConsole.log("Error while saving new custom goal", goal);
  532. this.model.remove(goal);
  533. }, this));
  534. this.trigger("creating");
  535. },
  536. createCustomGoal: function(e) {
  537. this.trigger("creating");
  538. e.preventDefault();
  539. newCustomGoalDialog.show();
  540. }
  541. });
  542. var NewGoalDialog = Backbone.View.extend({
  543. template: Templates.get("shared.goal-new-dialog"),
  544. initialize: function() {
  545. this.render();
  546. },
  547. render: function() {
  548. var context = this.model.processGoalContext();
  549. // As we're assigning to this.el, event handlers need to be rebound
  550. // after each render.
  551. this.el = $(this.template(context)).appendTo(document.body).get(0);
  552. this.newGoalView = new NewGoalView({
  553. el: this.$(".viewcontents"),
  554. model: this.model
  555. });
  556. this.newGoalView.bind("creating", this.hide, this);
  557. return this;
  558. },
  559. show: function() {
  560. // rerender every time we show this in case some process goals should
  561. // be disabled
  562. this.newGoalView.render();
  563. return $(this.el).modal({
  564. keyboard: true,
  565. backdrop: true,
  566. show: true
  567. });
  568. },
  569. hide: function() {
  570. // hide all hover effects so they don't show up next time we show
  571. this.$(".info").hide();
  572. // now hide the dialog
  573. return $(this.el).modal("hide");
  574. }
  575. });
  576. var NewCustomGoalDialog = Backbone.View.extend({
  577. template: Templates.get("shared.goal-new-custom-dialog"),
  578. loaded: false,
  579. render: function() {
  580. // As we're assigning to this.el, event handlers need to be rebound
  581. // after each render.
  582. this.el = $(this.template()).appendTo(document.body).get(0);
  583. this.innerEl = this.$(".modal-body").get(0);
  584. // turn on fading just before we animate so that dragging is fast
  585. var $el = $(this.el);
  586. $el.bind("shown", function() { $el.removeClass("fade"); });
  587. $el.bind("hide", function() { $el.addClass("fade"); });
  588. return this;
  589. },
  590. _show: function() {
  591. return $(this.el).modal({
  592. keyboard: false,
  593. backdrop: true,
  594. show: true
  595. });
  596. },
  597. show: function(target) {
  598. if (!this.innerEl) {
  599. this.render();
  600. }
  601. $(this.innerEl).data("target", target);
  602. // if we haven't yet loaded the contents of this dialog, do it
  603. if (!this.loaded) {
  604. this.loaded = true;
  605. this.load().error($.proxy(function() {
  606. this.loaded = false;
  607. }));
  608. this.$(".progress-bar").progressbar({value: 10}).slideDown("fast");
  609. }
  610. this._show();
  611. },
  612. load: function() {
  613. return $.ajax({url: "/goals/new", type: "GET", dataType: "html"})
  614. .done($.proxy(function(html) {
  615. KAConsole.log("Loaded /goals/new.");
  616. $(this.innerEl).html(html);
  617. createGoalInitialize();
  618. }, this))
  619. .error($.proxy(function() {
  620. KAConsole.log(Array.prototype.slice.call(arguments));
  621. $(this.innerEl).text("Page load failed. Please try again.");
  622. }, this));
  623. },
  624. hide: function() {
  625. $(this.el).modal("hide");
  626. },
  627. });
  628. $(function() {
  629. window.GoalBook = new GoalCollection(window.GoalsBootstrap || []);
  630. APIActionResults.register("updateGoals",
  631. $.proxy(GoalBook.incrementalUpdate, window.GoalBook));
  632. window.myGoalBookView = new GoalBookView({
  633. el: "#goals-nav-container",
  634. model: GoalBook
  635. });
  636. window.myGoalSummaryView = new GoalSummaryView({
  637. el: "#goals-container",
  638. model: GoalBook,
  639. goalBookView: myGoalBookView
  640. });
  641. myGoalSummaryView.render();
  642. window.newGoalDialog = new NewGoalDialog({model: GoalBook});
  643. window.newCustomGoalDialog = new NewCustomGoalDialog();
  644. });
  645. // todo: should we do this globally?
  646. Handlebars.registerPartial("goal-objectives", Templates.get("shared.goal-objectives"));
  647. Handlebars.registerPartial("goalbook-row", Templates.get("shared.goalbook-row"));
  648. Handlebars.registerPartial("goal-new", Templates.get("shared.goal-new"));