├── .gitignore ├── js ├── collections │ ├── edge-collection.js │ └── node-collection.js ├── models │ ├── edge-model.js │ ├── node-model.js │ └── graph-model.js ├── tests │ ├── tests-master.js │ └── tests.js ├── main.js ├── views │ ├── concept-list-item.js │ └── concept-list-view.js └── lib │ ├── backbone.touch.js │ ├── underscore-min.js │ ├── require-min.js │ └── backbone-min.js ├── tests.html ├── css ├── demo.css ├── mocha.css ├── list.css └── graph.css ├── LICENSE ├── demo ├── demojs │ ├── dev.js │ └── khan.js ├── dev.html └── khan.html ├── README.md └── data └── metacademy_demo.json /.gitignore: -------------------------------------------------------------------------------- 1 | .tern-port 2 | .DS_Store 3 | demo_kmap.json 4 | -------------------------------------------------------------------------------- /js/collections/edge-collection.js: -------------------------------------------------------------------------------- 1 | /*global define */ 2 | define(["backbone", "../models/edge-model"], function(Backbone, DirectedEdge){ 3 | return Backbone.Collection.extend({ 4 | model: DirectedEdge 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /css/demo.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow: hidden; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | width: 100%; 9 | height: 100%; 10 | font-size: 1.0em; 11 | overflow: hidden; 12 | font-family: "Lato", Arial, sans-serif; 13 | } 14 | 15 | #graph-view-wrapper{ 16 | height: inherit; 17 | width: 100%; 18 | } 19 | 20 | #main-display-view{ 21 | height: inherit; 22 | overflow: auto; 23 | } 24 | -------------------------------------------------------------------------------- /js/collections/node-collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the node collection 3 | * it is a basic collection that should not depend on aux 4 | */ 5 | 6 | /*global define */ 7 | define(['backbone', 'underscore', 'jquery', '../models/node-model'], function(Backbone, _, $, NodeModel){ 8 | "use strict"; 9 | 10 | /** 11 | * Collection of all node models in graph 12 | */ 13 | return Backbone.Collection.extend({ 14 | model: NodeModel 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /js/models/edge-model.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | define(["backbone"], function(Backbone){ 3 | /** 4 | * general directed edge model 5 | */ 6 | return Backbone.Model.extend({ 7 | 8 | /** 9 | * default values -- underscore attribs used to match data from server 10 | */ 11 | defaults: function () { 12 | return { 13 | from_tag: "", 14 | to_tag: "", 15 | reason: "" 16 | }; 17 | }, 18 | 19 | /** 20 | * Initialize the DE (currently sets the id properly) 21 | */ 22 | initialize: function(inp){ 23 | this.id = inp.id || inp.from_tag + inp.to_tag; 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2018 Colorado Reed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /js/tests/tests-master.js: -------------------------------------------------------------------------------- 1 | 2 | /*global require*/ 3 | require.config({ 4 | baseUrl: "/js", 5 | paths: { 6 | jquery:"lib/jquery-1.10.2.min", 7 | underscore: "lib/underscore-min", 8 | backbone: "lib/backbone-min", 9 | d3: "lib/d3", 10 | "dagre": "lib/dagre", 11 | "btouch": "lib/backbone.touch", 12 | "chai": "lib/chai", 13 | "mocha": "lib/mocha" 14 | }, 15 | 16 | shim: { 17 | 'underscore': { 18 | exports: '_' 19 | }, 20 | dagre: { 21 | exports: "dagre" 22 | }, 23 | 'jquery': { 24 | exports: '$' 25 | }, 26 | 'backbone': { 27 | deps: ['underscore', 'jquery'], 28 | exports: 'Backbone' 29 | }, 30 | "btouch": { 31 | deps: ["jquery", "underscore", "backbone"] 32 | }, 33 | 'mocha': { 34 | init: function () { 35 | this.mocha.setup('bdd'); 36 | return this.mocha; 37 | } 38 | } 39 | }, 40 | urlArgs: 'bust=' + (new Date()).getTime() 41 | }); 42 | 43 | require(['require', 'chai', 'mocha', 'jquery'], function(require, chai, mocha, $){ 44 | 45 | require([ 46 | 'tests/tests' 47 | ], function(require) { 48 | if (window.mochaPhantomJS) { window.mochaPhantomJS.run(); } 49 | else { mocha.run(); } 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic kmap demonstration 3 | */ 4 | 5 | /*global requirejs, define*/ 6 | 7 | // configure requirejs 8 | requirejs.config( { 9 | // TODO use min files here since we're not using a compressor 10 | paths: { 11 | jquery:"lib/jquery-1.10.2.min", 12 | backbone: "lib/backbone-min", 13 | underscore: "lib/underscore-min", 14 | d3: "lib/d3", 15 | "dagre": "lib/dagre", 16 | "btouch": "lib/backbone.touch" 17 | }, 18 | shim: { 19 | d3: { 20 | exports: "d3" 21 | }, 22 | dagre: { 23 | exports: "dagre" 24 | }, 25 | underscore: { 26 | exports: "_" 27 | }, 28 | backbone: { 29 | deps: ['underscore', 'jquery'], 30 | exports: 'Backbone' 31 | }, 32 | "btouch": { 33 | deps: ["jquery", "underscore", "backbone"] 34 | } 35 | } 36 | }); 37 | 38 | //main execution 39 | define(["models/graph-model", "views/graph-view", "views/concept-list-view", "jquery", "btouch"], function(GraphModel, GraphView, ListView){ 40 | var KMap = {}; 41 | KMap.GraphModel = GraphModel; 42 | KMap.GraphView = GraphView; 43 | KMap.GraphListView = ListView; 44 | /* make the KMap object global 45 | this hack provides a library-esk mode for kmap 46 | and preserves metacademy integration */ 47 | window.KMap = KMap; 48 | return KMap; 49 | }); 50 | -------------------------------------------------------------------------------- /demo/demojs/dev.js: -------------------------------------------------------------------------------- 1 | // development testing script - not used for library 2 | 3 | require.config({ 4 | baseUrl: "../js" 5 | }); 6 | 7 | 8 | /*global require, KMap, $*/ 9 | require(["main"], function(KMap){ 10 | // create the model/settings so we can pass it into the views 11 | var graphModel = new KMap.GraphModel(), 12 | settings = {model: graphModel, useWisps: false, graphDirection: "TB", showTransEdgesWisps: true}; 13 | 14 | var graphView = new KMap.GraphView(settings), 15 | graphListView = new KMap.GraphListView({model: graphModel}); 16 | 17 | var handleDataFun = function (data) { 18 | 19 | // add the data to the graph model 20 | graphModel.addJsonNodesToGraph(data); 21 | 22 | // set the graph placement (don't use if "x" and "y" are specified in the data) 23 | graphView.optimizeGraphPlacement(false, false); 24 | 25 | // render the views 26 | graphView.render(); 27 | graphListView.render(); 28 | 29 | // insert them into the html 30 | $("body").prepend(graphListView.$el); 31 | $("#graph-view-wrapper").append(graphView.$el); 32 | 33 | // TODO integrate this into the view 34 | var $wrap = $(document.body); 35 | $wrap.height($(window).height()); 36 | $(window).resize(function () { 37 | $wrap.height($(window).height()); 38 | }); 39 | 40 | // center on a leaf element 41 | var topoSortList = graphModel.getTopoSort(); 42 | graphView.centerForNode(graphModel.getNode(topoSortList[topoSortList.length -1])); 43 | 44 | }; 45 | 46 | // fetch some graph data 47 | $.getJSON("../data/metacademy_demo.json", handleDataFun); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /demo/dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 | 20 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /demo/khan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | loading... (check dev console for errors if this takes too long) 16 |
17 | 18 |
19 | 20 | 21 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demo/demojs/khan.js: -------------------------------------------------------------------------------- 1 | // demo: khan academy data 2 | 3 | require.config({ 4 | baseUrl: "../js" 5 | }); 6 | 7 | 8 | /*global require, KMap, $*/ 9 | require(["main"], function(KMap){ 10 | // create the model and pass it into the views 11 | var graphModel = new KMap.GraphModel({allowCycles: true}), 12 | graphListView = new KMap.GraphListView({model: graphModel}), 13 | graphView, 14 | settings = {model: graphModel}; 15 | 16 | var handleDataFun = function (data) { 17 | 18 | // check if we're doing targeted learning 19 | var splitHref = window.location.href.split("#"); 20 | if (splitHref.length > 1) { 21 | var target = splitHref.pop(); 22 | // only add the subgraph to the graph 23 | graphModel.addJsonSubGraphToGraph(data, target); 24 | settings.includeShortestOutlink = true; 25 | settings.minWispLenPx = 500; 26 | } else { 27 | // not targeted: add all the data to the graph model 28 | graphModel.addJsonNodesToGraph(data); 29 | settings.includeShortestDep = true; 30 | settings.minWispLenPx = 300; 31 | } 32 | 33 | // set some basic settings 34 | settings.useWisps = true; 35 | settings.showTransEdgesWisps = true; 36 | settings.showEdgeSummary = false; 37 | graphView = new KMap.GraphView(settings); 38 | 39 | // set the graph placement (don't use if "x" and "y" are specified in the data) 40 | graphView.optimizeGraphPlacement(false, false); 41 | 42 | // render the views 43 | graphView.render(); 44 | graphListView.render(); 45 | 46 | // insert them into the html 47 | $("body").prepend(graphListView.$el); 48 | $("#graph-view-wrapper").html(graphView.$el); 49 | 50 | 51 | // allows correct x-browser list scrolling/svg when the window size changes 52 | // TODO integrate this into the view 53 | var $wrap = $(document.body); 54 | $wrap.height($(window).height()); 55 | $(window).resize(function () { 56 | $wrap.height($(window).height()); 57 | }); 58 | 59 | var topoSortList = graphModel.getTopoSort(); 60 | graphView.centerForNode(graphModel.getNode(topoSortList[topoSortList.length -1])); 61 | 62 | }; 63 | 64 | // fetch some graph data (multiple fetches since demo_kmap is not stored in repo yet 65 | $.getJSON("../data/khan_formatted.json", handleDataFun); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /js/views/concept-list-item.js: -------------------------------------------------------------------------------- 1 | // This view contains the list item view for the concept list 2 | 3 | /*global define*/ 4 | define(["backbone", "underscore"], function (Backbone, _) { 5 | 6 | /** 7 | * Display the concepts as an item in the node list 8 | */ 9 | return (function(){ 10 | // define private variables and methods 11 | var pvt = {}; 12 | 13 | pvt.consts = { 14 | viewClass: "learn-title-display", 15 | viewIdPrefix: "node-title-view-", // must also change in parent 16 | clickedItmClass: "clicked-title" // must also change in parent 17 | }; 18 | 19 | // return public object for node list item view 20 | return Backbone.View.extend({ 21 | id: function(){ return pvt.consts.viewIdPrefix + this.model.id;}, 22 | 23 | tagName: "li", 24 | 25 | className: pvt.consts.viewClass, 26 | 27 | // TODO handle click event correctly 28 | events: { 29 | "click": function(evt){ 30 | var thisView = this, 31 | modelId = thisView.model.id; 32 | 33 | if (!thisView.$el.hasClass(pvt.consts.clickedItmClass)) { 34 | // set focus if not currently focused 35 | thisView.model.trigger("setFocusNode", modelId); 36 | } else { 37 | // else, if currently focused, toggle scope 38 | thisView.model.trigger("toggleNodeScope", modelId); 39 | } 40 | // change url parameters if using a router 41 | this.appRouter && this.appRouter.changeUrlParams({focus: thisView.model.get("tag")}); 42 | } 43 | }, 44 | 45 | /** override in subclass */ 46 | preinitialize: function () {}, 47 | 48 | /** 49 | * Initialize the view with appropriate listeners 50 | * NOTE: this should not be overridden in a subclass - use post/preinitialize 51 | */ 52 | initialize: function(inp){ 53 | var thisView = this; 54 | thisView.preinitialize(inp); 55 | thisView.postinitialize(inp); 56 | }, 57 | 58 | /** override in subclass */ 59 | postinitialize: function (inp) {}, 60 | 61 | /** override in subclass */ 62 | prerender: function (inp) {}, 63 | 64 | /** 65 | * Render the learning view given the supplied model 66 | */ 67 | render: function(){ 68 | var thisView = this; 69 | thisView.prerender(); 70 | 71 | thisView.postrender(); 72 | return thisView; 73 | }, 74 | 75 | /** override in subclass */ 76 | postrender: function () { 77 | var thisView = this; 78 | thisView.$el.html(thisView.model.get("title")); 79 | }, 80 | 81 | /** 82 | * Change the title display properties given by prop 83 | */ 84 | changeTitleClass: function(classVal, status){ 85 | if (status){ 86 | this.$el.addClass(classVal); 87 | } 88 | else{ 89 | this.$el.removeClass(classVal); 90 | } 91 | }, 92 | 93 | /** 94 | * return a shallow copy of the private consts for this view 95 | */ 96 | getConstsClone: function () { 97 | return _.clone(pvt.consts); 98 | } 99 | }); 100 | })(); 101 | }); 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kmap 2 | ==== 3 | 4 | Knowledge map visualization library (work in progress) 5 | 6 | 7 | ## Demos 8 | The knowledge map library is the core component for Metacademy's interactive knowledge map, e.g. [metacademy graph demo](http://metacademy.org/graphs/concepts/deep_belief_networks#focus=deep_belief_networks&mode=explore) 9 |

10 | 11 | Metacademy Logo 12 | 13 |

14 | 15 | 16 | See the html and associated javascript files in [/demo](/demo) 17 | 18 | [Demo: Khan Academy Content](http://obphio.us/kmap/demo/khan.html#implicit-differentiation) 19 | 20 | [Demo: Metacademy Content](http://obphio.us/kmap/demo/dev.html) 21 | 22 | 23 | 24 | 25 | 26 | ## Installation 27 | 28 | 1. Clone this repository 29 | 30 | git clone https://github.com/cjrd/kmap.git 31 | 32 | 1. Run a local server, e.g. 33 | 34 | python -m SimpleHTTPServer 35 | 36 | 1. Run the tests by navigating to `http://localhost:8000/tests.html` 37 | 38 | 1. See a simple demo at `http://localhost:8000/demo/dev.html` 39 | 40 | 41 | ## Data Format 42 | 43 | 44 | The minimal expected data format is as follows (only ids are required): 45 | 46 | [node1, node2, ...] 47 | 48 | nodeX = 49 | { 50 | id: "node_id" // this is the only required attribute for nodes 51 | title: "display title for node" 52 | summary: "summary of node" 53 | dependencies: [dep1, dep2, ...] 54 | } 55 | 56 | depX = 57 | { 58 | source: "source_id", // this is the only require attribute for deps 59 | reason: "reason for dep" 60 | } 61 | 62 | 63 | ## Graph View settings 64 | You can change the following settings by passing an object into the GraphView constructor with the appropriate settings field specified, e.g. from the `demo/demojs/khan.js` demo: `graphView = new KMap.GraphView(settings);` 65 | 66 | settings.useWisps {boolean}: show wisp edges instead of long edges (default: true) 67 | settings.minWispLenPx {number > 0}: the minimum length to make an edge a wisp edge (default: 285) 68 | settings.includeShortestDep {boolean}: always show the shortest inlink for each node, regardless of its length (default: false) 69 | settings.includeShortestOutlink {boolean}: always show the shortest outlink for each node, regardless of its length (default: false) 70 | settings.showEdgeSummary {boolean}: show the edge summary field on hover (default: true) 71 | settings.showNodeSummar {boolean}: show the node summary field on hover (default: true) 72 | settings.graphDirection {"BT", "TB", "LR", "RL"}: direction of graph edges, e.g. TB = "top-to-bottom" (default: "TB") 73 | settings.showTransEdgesWisps {boolean}: show transitive edges as wisps (default: true) 74 | 75 | 76 | ## Graph Model settings 77 | You can change the following settings by passing an object into the GraphModel constructor with the appropriate settings field specified, e.g. from the `demo/demojs/khan.js` demo: `var graphModel = new KMap.GraphModel({allowCycles: true});` 78 | 79 | settings.addCycles {boolean}: allows cycles in the graph if set to true (default: false) 80 | 81 | ## Notes 82 | The knowledge map visualization library is a work in progress -- it was extracted from the [metacademy application codebase](https://github.com/metacademy/metacademy-application). I am currently working on making this code easy to use by itself while still maintaining compatability with metacademy. Please contact me (coloradoNospam at berkeley dot edu, remove the no spam part) if you want to use this code for your own project but are having trouble figuring out the code. 83 | -------------------------------------------------------------------------------- /js/lib/backbone.touch.js: -------------------------------------------------------------------------------- 1 | // (c) 2012 Raymond Julin, Keyteq AS 2 | // Backbone.touch may be freely distributed under the MIT license. 3 | (function (factory) { 4 | 5 | "use strict"; 6 | 7 | if (typeof define === 'function' && define.amd) { 8 | // AMD. Register as an anonymous module. 9 | define(['underscore', 'backbone'], factory); 10 | } else { 11 | // Browser globals 12 | factory(_, Backbone); 13 | } 14 | }(function (_, Backbone) { 15 | 16 | "use strict"; 17 | 18 | // The `getValue` and `delegateEventSplitter` is copied from 19 | // Backbones source, unfortunately these are not available 20 | // in any form from Backbone itself 21 | var getValue = function(object, prop) { 22 | if (!(object && object[prop])) return null; 23 | return _.isFunction(object[prop]) ? object[prop]() : object[prop]; 24 | }; 25 | var delegateEventSplitter = /^(\S+)\s*(.*)$/; 26 | 27 | _.extend(Backbone.View.prototype, { 28 | _touching : false, 29 | 30 | touchPrevents : true, 31 | 32 | touchThreshold : 10, 33 | 34 | isTouch : 'ontouchstart' in document && !('callPhantom' in window), 35 | 36 | // Drop in replacement for Backbone.View#delegateEvent 37 | // Enables better touch support 38 | // 39 | // If the users device is touch enabled it replace any `click` 40 | // event with listening for touch(start|move|end) in order to 41 | // quickly trigger touch taps 42 | delegateEvents: function(events) { 43 | if (!(events || (events = getValue(this, 'events')))) return; 44 | this.undelegateEvents(); 45 | var suffix = '.delegateEvents' + this.cid; 46 | _(events).each(function(method, key) { 47 | if (!_.isFunction(method)) method = this[events[key]]; 48 | if (!method) throw new Error('Method "' + events[key] + '" does not exist'); 49 | var match = key.match(delegateEventSplitter); 50 | var eventName = match[1], selector = match[2]; 51 | var boundHandler = _.bind(this._touchHandler,this); 52 | method = _.bind(method, this); 53 | if (this._useTouchHandlers(eventName, selector)) { 54 | this.$el.on('touchstart' + suffix, selector, boundHandler); 55 | this.$el.on('touchend' + suffix, selector, 56 | {method:method}, 57 | boundHandler 58 | ); 59 | } 60 | else { 61 | eventName += suffix; 62 | if (selector === '') { 63 | this.$el.bind(eventName, method); 64 | } else { 65 | this.$el.on(eventName, selector, method); 66 | } 67 | } 68 | }, this); 69 | }, 70 | 71 | // Detect if touch handlers should be used over listening for click 72 | // Allows custom detection implementations 73 | _useTouchHandlers : function(eventName, selector) 74 | { 75 | return this.isTouch && eventName === 'click'; 76 | }, 77 | 78 | // At the first touchstart we register touchevents as ongoing 79 | // and as soon as a touch move happens we set touching to false, 80 | // thus implying that a fastclick will not happen when 81 | // touchend occurs. If no touchmove happened 82 | // inbetween touchstart and touchend we trigger the event 83 | // 84 | // The `touchPrevents` toggle decides if Backbone.touch 85 | // will stop propagation and prevent default 86 | // for *button* and *a* elements 87 | _touchHandler : function(e) { 88 | if (!('changedTouches' in e.originalEvent)) return; 89 | var touch = e.originalEvent.changedTouches[0]; 90 | var x = touch.clientX; 91 | var y = touch.clientY; 92 | switch (e.type) { 93 | case 'touchstart': 94 | this._touching = [x, y]; 95 | break; 96 | case 'touchend': 97 | var oldX = this._touching[0]; 98 | var oldY = this._touching[1]; 99 | var threshold = this.touchThreshold; 100 | if (x < (oldX + threshold) && x > (oldX - threshold) && 101 | y < (oldY + threshold) && y > (oldY - threshold)) { 102 | this._touching = false; 103 | if (this.touchPrevents) { 104 | var tagName = e.currentTarget.tagName; 105 | if (tagName === 'BUTTON' || 106 | tagName === 'A') { 107 | e.preventDefault(); 108 | e.stopPropagation(); 109 | } 110 | } 111 | e.data.method(e); 112 | } 113 | break; 114 | } 115 | } 116 | }); 117 | return Backbone; 118 | })); 119 | -------------------------------------------------------------------------------- /css/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, #mocha li { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | #mocha ul { 18 | list-style: none; 19 | } 20 | 21 | #mocha h1, #mocha h2 { 22 | margin: 0; 23 | } 24 | 25 | #mocha h1 { 26 | margin-top: 15px; 27 | font-size: 1em; 28 | font-weight: 200; 29 | } 30 | 31 | #mocha h1 a { 32 | text-decoration: none; 33 | color: inherit; 34 | } 35 | 36 | #mocha h1 a:hover { 37 | text-decoration: underline; 38 | } 39 | 40 | #mocha .suite .suite h1 { 41 | margin-top: 0; 42 | font-size: .8em; 43 | } 44 | 45 | #mocha .hidden { 46 | display: none; 47 | } 48 | 49 | #mocha h2 { 50 | font-size: 12px; 51 | font-weight: normal; 52 | cursor: pointer; 53 | } 54 | 55 | #mocha .suite { 56 | margin-left: 15px; 57 | } 58 | 59 | #mocha .test { 60 | margin-left: 15px; 61 | overflow: hidden; 62 | } 63 | 64 | #mocha .test.pending:hover h2::after { 65 | content: '(pending)'; 66 | font-family: arial, sans-serif; 67 | } 68 | 69 | #mocha .test.pass.medium .duration { 70 | background: #C09853; 71 | } 72 | 73 | #mocha .test.pass.slow .duration { 74 | background: #B94A48; 75 | } 76 | 77 | #mocha .test.pass::before { 78 | content: '✓'; 79 | font-size: 12px; 80 | display: block; 81 | float: left; 82 | margin-right: 5px; 83 | color: #00d6b2; 84 | } 85 | 86 | #mocha .test.pass .duration { 87 | font-size: 9px; 88 | margin-left: 5px; 89 | padding: 2px 5px; 90 | color: white; 91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -webkit-border-radius: 5px; 95 | -moz-border-radius: 5px; 96 | -ms-border-radius: 5px; 97 | -o-border-radius: 5px; 98 | border-radius: 5px; 99 | } 100 | 101 | #mocha .test.pass.fast .duration { 102 | display: none; 103 | } 104 | 105 | #mocha .test.pending { 106 | color: #0b97c4; 107 | } 108 | 109 | #mocha .test.pending::before { 110 | content: '◦'; 111 | color: #0b97c4; 112 | } 113 | 114 | #mocha .test.fail { 115 | color: #c00; 116 | } 117 | 118 | #mocha .test.fail pre { 119 | color: black; 120 | } 121 | 122 | #mocha .test.fail::before { 123 | content: '✖'; 124 | font-size: 12px; 125 | display: block; 126 | float: left; 127 | margin-right: 5px; 128 | color: #c00; 129 | } 130 | 131 | #mocha .test pre.error { 132 | color: #c00; 133 | max-height: 300px; 134 | overflow: auto; 135 | } 136 | 137 | #mocha .test pre { 138 | display: block; 139 | float: left; 140 | clear: left; 141 | font: 12px/1.5 monaco, monospace; 142 | margin: 5px; 143 | padding: 15px; 144 | border: 1px solid #eee; 145 | border-bottom-color: #ddd; 146 | -webkit-border-radius: 3px; 147 | -webkit-box-shadow: 0 1px 3px #eee; 148 | -moz-border-radius: 3px; 149 | -moz-box-shadow: 0 1px 3px #eee; 150 | border-radius: 3px; 151 | } 152 | 153 | #mocha .test h2 { 154 | position: relative; 155 | } 156 | 157 | #mocha .test a.replay { 158 | position: absolute; 159 | top: 3px; 160 | right: 0; 161 | text-decoration: none; 162 | vertical-align: middle; 163 | display: block; 164 | width: 15px; 165 | height: 15px; 166 | line-height: 15px; 167 | text-align: center; 168 | background: #eee; 169 | font-size: 15px; 170 | -moz-border-radius: 15px; 171 | border-radius: 15px; 172 | -webkit-transition: opacity 200ms; 173 | -moz-transition: opacity 200ms; 174 | transition: opacity 200ms; 175 | opacity: 0.3; 176 | color: #888; 177 | } 178 | 179 | #mocha .test:hover a.replay { 180 | opacity: 1; 181 | } 182 | 183 | #mocha-report.pass .test.fail { 184 | display: none; 185 | } 186 | 187 | #mocha-report.fail .test.pass { 188 | display: none; 189 | } 190 | 191 | #mocha-report.pending .test.pass, 192 | #mocha-report.pending .test.fail { 193 | display: none; 194 | } 195 | #mocha-report.pending .test.pass.pending { 196 | display: block; 197 | } 198 | 199 | #mocha-error { 200 | color: #c00; 201 | font-size: 1.5em; 202 | font-weight: 100; 203 | letter-spacing: 1px; 204 | } 205 | 206 | #mocha-stats { 207 | position: fixed; 208 | top: 15px; 209 | right: 10px; 210 | font-size: 12px; 211 | margin: 0; 212 | color: #888; 213 | z-index: 1; 214 | } 215 | 216 | #mocha-stats .progress { 217 | float: right; 218 | padding-top: 0; 219 | } 220 | 221 | #mocha-stats em { 222 | color: black; 223 | } 224 | 225 | #mocha-stats a { 226 | text-decoration: none; 227 | color: inherit; 228 | } 229 | 230 | #mocha-stats a:hover { 231 | border-bottom: 1px solid #eee; 232 | } 233 | 234 | #mocha-stats li { 235 | display: inline-block; 236 | margin: 0 5px; 237 | list-style: none; 238 | padding-top: 11px; 239 | } 240 | 241 | #mocha-stats canvas { 242 | width: 40px; 243 | height: 40px; 244 | } 245 | 246 | #mocha code .comment { color: #ddd } 247 | #mocha code .init { color: #2F6FAD } 248 | #mocha code .string { color: #5890AD } 249 | #mocha code .keyword { color: #8A6343 } 250 | #mocha code .number { color: #2F6FAD } 251 | 252 | @media screen and (max-device-width: 480px) { 253 | #mocha { 254 | margin: 60px 0px; 255 | } 256 | 257 | #mocha #stats { 258 | position: absolute; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /css/list.css: -------------------------------------------------------------------------------- 1 | /* Concept list */ 2 | #concept-list-panel{ 3 | height: inherit; 4 | display: inline; 5 | } 6 | 7 | #concept-list-wrapper{ 8 | top: 5em; 9 | width: 20%; 10 | min-width: 200px; 11 | display: block; 12 | float: left; 13 | position:relative; 14 | -webkit-box-sizing: border-box; 15 | -moz-box-sizing: border-box; 16 | -ms-box-sizing: border-box; /* IE */ 17 | box-sizing: border-box; 18 | overflow: auto; 19 | height: inherit; 20 | border-right: 1px solid #CCCCCC; 21 | } 22 | 23 | #concept-list-wrapper h1, #concept-title-header h1{ 24 | text-align: center; 25 | font-weight: bold; 26 | color: #333; 27 | font-size: 0.9em; 28 | padding-bottom: 0.3em; 29 | margin-top: 0.5em; 30 | } 31 | 32 | #concept-list{ 33 | padding: 0; 34 | padding-bottom: 5em; 35 | margin: 0; 36 | } 37 | 38 | #concept-list-search{ 39 | text-align: center; 40 | } 41 | 42 | #concept-list-search input{ 43 | width: 85%; 44 | -ms-box-sizing: border-box; /* IE */ 45 | -moz-box-sizing: border-box; 46 | -webkit-box-sizing: border-box; 47 | box-sizing: border-box; 48 | background: white; 49 | background-color: #fafafa; 50 | font-size: 10px; 51 | height: 2.2em; 52 | outline: 0; 53 | padding: 0 0.2em; 54 | display: inline-block; 55 | border: 1px solid #4e0142; 56 | border-radius: 4px; 57 | cursor: text; 58 | } 59 | 60 | #concept-list-search input:hover{ 61 | cursor: text; 62 | } 63 | 64 | #concept-list-search #cancel-search-input{ 65 | background-color: #F3F3F3; 66 | cursor: pointer; 67 | width: 15%; 68 | border-radius: 0 4px 4px 0; 69 | position: absolute; 70 | right: 7%; 71 | display: none; 72 | } 73 | 74 | #concept-title-header{ 75 | position: fixed; 76 | background-color: rgb(249, 249, 255); 77 | z-index: 5; 78 | width: 20%; 79 | min-width: 200px; 80 | height: 5em; 81 | border: 1px #8B8383 solid; 82 | border-top: none; 83 | box-sizing: border-box; 84 | -moz-box-sizing: border-box; 85 | -webkit-box-sizing: border-box; 86 | } 87 | 88 | #concept-list-hide-button{ 89 | position: absolute; 90 | z-index: 100; 91 | right: 0; 92 | top: 0.1em; 93 | cursor: pointer; 94 | } 95 | 96 | #concept-list-hide-button{ 97 | border: 1px solid #7E7E7E; 98 | border-radius: 4px 0 0 4px; 99 | font-size: 1em; 100 | border-right: 0; 101 | color: #7e7e7e; 102 | } 103 | 104 | #concept-list-hide-button:hover{ 105 | border-color: #690259; 106 | color: #690259; 107 | } 108 | 109 | #concept-list-show-button{ 110 | display: none; 111 | left: 0; 112 | bottom: 40%; 113 | border-radius: 0 0 5px 5px; 114 | border-width: 0 1px 1px 1px; 115 | -webkit-transform: rotate(-90deg); 116 | -webkit-transform-origin: 0 0; 117 | -moz-transform: rotate(-90deg); 118 | -moz-transform-origin: 0 0; 119 | -ms-transform: rotate(-90deg); 120 | -o-transform: rotate(-90deg); 121 | -o-transform-origin: 0 0; 122 | -ms-transform: rotate(-90deg); 123 | -ms-transform-origin: 0 0; 124 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 125 | } 126 | 127 | #concept-list-show-button.expanded{ 128 | left: 55%; 129 | } 130 | 131 | .learn-title-display{ 132 | text-align: center; 133 | display: block; 134 | cursor: pointer; 135 | text-decoration: none; 136 | color: #3E4018; 137 | padding: 1.4em 0.5em; 138 | line-height: 1.2em; 139 | font-size: 0.9em; 140 | border-bottom: 1px solid #CCCCCC; 141 | position: relative; 142 | } 143 | .learn-title-display:hover{ 144 | background-color: rgb(250, 242, 248); 145 | } 146 | .learn-title-display.clicked-title{ 147 | font-weight: bold; 148 | color: #333333; 149 | background-color: rgb(245, 232, 242); 150 | } 151 | 152 | .hide-clist #concept-list-wrapper, .hide-clist #concept-title-header{ 153 | display: none; 154 | } 155 | 156 | .hide-clist #concept-list-show-button{ 157 | display: block; 158 | } 159 | 160 | /* button for hiding/showing side objects */ 161 | .small-vp-button{ 162 | display: none; 163 | cursor: pointer; 164 | position: absolute; 165 | text-align: center; 166 | font-family: sans-serif; 167 | background: rgb(102, 102, 102); 168 | z-index: 10; 169 | color: rgb(236, 236, 236); 170 | border: #302C2C solid; 171 | padding: 0.2em 1em; 172 | } 173 | 174 | 175 | @media only screen and (max-width: 760px){ 176 | #concept-list-wrapper, #concept-title-header{ 177 | display: none; 178 | } 179 | .show-clist #concept-list-wrapper, .show-clist #concept-title-header{ 180 | display: block; 181 | } 182 | 183 | .show-clist #concept-list-show-button{ 184 | display: none; 185 | } 186 | 187 | .hide-clist #concept-list-wrapper, .hide-clist #concept-title-header{ 188 | display: none; 189 | } 190 | 191 | #concept-list-show-button{ 192 | display: block; 193 | } 194 | 195 | #learning-plan-button{ 196 | display: block; 197 | } 198 | 199 | .learn-title-buttons{ 200 | position: relative; 201 | display: block; 202 | margin-top: 0.2em; 203 | } 204 | 205 | .learn-title-buttons span{ 206 | padding: 0 0.2em; 207 | } 208 | 209 | #concept-list-wrapper, #concept-title-header{ 210 | display: none; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /css/graph.css: -------------------------------------------------------------------------------- 1 | /* main container */ 2 | #graph-view{ 3 | overflow: hidden; 4 | position: relative; 5 | height: inherit; 6 | width: inherit; 7 | } 8 | 9 | svg{ 10 | height: inherit; 11 | width: 100%; 12 | } 13 | 14 | #graph-summary-el{ 15 | z-index: 10; 16 | max-width: 1100px; 17 | margin-left: auto; 18 | margin-right: auto; 19 | bottom: 0; 20 | position: absolute; 21 | text-align: center; 22 | pointer-events: none; 23 | background-color: #F7F7F7; 24 | box-sizing: border-box; 25 | border: 1px solid #333; 26 | border-radius: 5px 5px 0 0; 27 | left: 1em; 28 | padding: 0.8em; 29 | right: 1em; 30 | line-height: 1.3em; 31 | display: none; 32 | } 33 | 34 | #graph-summary-el h1{ 35 | pointer-events: none; 36 | font-size: 1em; 37 | font-weight: bold; 38 | margin: 0 0 0.2em 0; 39 | } 40 | 41 | #graph-summary-el ul{ 42 | margin: 0; 43 | list-style: none; 44 | } 45 | 46 | #graph-summary-el li{ 47 | margin-top: 0.1em; 48 | font-size: 0.9em; 49 | font-style: italic; 50 | } 51 | 52 | /* Zoom In / Zoom Out */ 53 | #graph-zoom-div{ 54 | position: absolute; 55 | z-index: 10; 56 | top: 1.5em; 57 | right: 1.5em; 58 | border: 1px solid #8A8484; 59 | border-radius: 5px; 60 | padding: 0; 61 | margin: 0; 62 | background: white; 63 | overflow: hidden; 64 | } 65 | 66 | .graph-zoom-in-button, .graph-zoom-out-button{ 67 | display: block; 68 | font-size: 1.2em; 69 | padding: 3px; 70 | margin: 0; 71 | width: 1.4em; 72 | line-height: 1.2em; 73 | border: none; 74 | text-align: center; 75 | background: inherit; 76 | cursor: pointer; 77 | color: #8A8484; 78 | outline: none; 79 | } 80 | 81 | .graph-zoom-in-button{ 82 | border-bottom: 1px solid; 83 | } 84 | 85 | .graph-zoom-in-button:hover, .graph-zoom-out-button:hover{ 86 | color: #333; 87 | font-weight: bold; 88 | } 89 | 90 | /* Concept Gs */ 91 | .concept-g{ 92 | cursor: default; 93 | } 94 | 95 | .concept-g text{ 96 | pointer-events: none; 97 | font-family: "Lato", Arial, sans-serif; 98 | font-weight: bold; 99 | font-size: 0.8em; 100 | stroke-width: 0; 101 | fill: #333 102 | -webkit-touch-callout: none; 103 | -webkit-user-select: none; 104 | -khtml-user-select: none; 105 | -moz-user-select: none; 106 | -ms-user-select: none; 107 | user-select: none; 108 | } 109 | 110 | .concept-g.reduce-node-title text{ 111 | font-size: 0.6em; 112 | } 113 | 114 | .concept-g circle{ 115 | stroke: #333333; 116 | stroke-width: 1.5px; 117 | fill: #F6FBFF; /* light shade of alice blue see http://encycolorpedia.com/f6fbff for color scheme */ 118 | } 119 | 120 | .concept-g:hover circle{ 121 | fill: #ECF6FF; 122 | } 123 | 124 | .selected circle{ 125 | fill: rgb(250, 232, 255); 126 | } 127 | .selected:hover circle{ 128 | fill: rgb(250, 232, 255); 129 | } 130 | 131 | 132 | /* Path Gs */ 133 | marker{ 134 | fill: #333; 135 | } 136 | 137 | path.link.hidden{ 138 | stroke-width: 0; 139 | } 140 | 141 | .selected path.link { 142 | stroke: rgb(229, 172, 247); 143 | } 144 | 145 | 146 | path.link { 147 | fill: transparent; 148 | stroke: #333; 149 | stroke-width: 2px; 150 | cursor: default; 151 | } 152 | 153 | path.link-wrapper, path.short-link-wrapper{ 154 | fill: none; 155 | stroke: #8282e5; 156 | stroke-width: 24px; 157 | opacity: 0; 158 | } 159 | 160 | path.link-wrapper:hover{ 161 | opacity: 0.15; 162 | } 163 | 164 | .dep-icon-g rect, .ol-icon-g rect{ 165 | fill: #333; 166 | stroke-width: 0; 167 | stroke: #333; 168 | display: none; 169 | } 170 | .dep-icon-g rect:hover, .ol-icon-g rect:hover{ 171 | stroke-width: 5px; 172 | } 173 | 174 | .hovered .dep-icon-g rect, .hovered .ol-icon-g rect { 175 | display: block; 176 | } 177 | 178 | .dep-icon-g polygon, .ol-icon-g polygon{ 179 | stroke-width: 0px; 180 | stroke: #333; 181 | fill: #333; 182 | } 183 | .dep-icon-g polygon:hover, .ol-icon-g polygon:hover{ 184 | stroke-width: 3px; 185 | } 186 | 187 | 188 | /* dep and ol circles */ 189 | .concept-g .ol-circle{ 190 | fill: #fbeded; 191 | } 192 | .concept-g .dep-circle{ 193 | fill: #e6e6fa; 194 | } 195 | 196 | .scoped .concept-g .ol-circle{ 197 | fill: #F6FBFF; /* light shade of alice blue see http://encycolorpedia.com/f6fbff for color scheme */ 198 | } 199 | .scoped .concept-g .dep-circle{ 200 | fill: #F6FBFF; /* light shade of alice blue see http://encycolorpedia.com/f6fbff for color scheme */ 201 | } 202 | 203 | g.concept-g.scoped-circle-g > circle, g.concept-g.scoped-circle-g .dep-circle, g.concept-g.scoped-circle-g .ol-circle, g.concept-g.focused-circle-g > circle{ 204 | fill: rgb(255, 244, 253); 205 | } 206 | 207 | /* (long) edges */ 208 | .start-wisp, .end-wisp{ 209 | fill: none; 210 | stroke: #333; 211 | stroke-width: 2px; 212 | } 213 | 214 | .link-wrapper-hover .start-wisp, .link-wrapper-hover .end-wisp{ 215 | display: none; 216 | } 217 | 218 | .long-edge{ 219 | display: none; 220 | } 221 | 222 | .link-wrapper-hover path{ 223 | display: block; 224 | } 225 | 226 | .link-wrapper-hover path.link-wrapper{ 227 | opacity: 0.15; 228 | } 229 | 230 | .ol-show path.link-wrapper{ 231 | stroke: #e48989; 232 | } 233 | 234 | .scoped .link-wrapper-hover path.link-wrapper{ 235 | opacity: 0; 236 | } 237 | 238 | 239 | @media only screen and (max-width: 350px){ 240 | #graph-summary-el{ 241 | opacity: 0; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /js/models/node-model.js: -------------------------------------------------------------------------------- 1 | /*global define 2 | This file contains the node model, which contains the data for each concept TODO should this be renamed "concept-model"? 3 | */ 4 | 5 | define(["backbone", "underscore", "../collections/edge-collection"], function(Backbone, _, DirectedEdgeCollection){ 6 | 7 | // Private methods and variables 8 | var pvt = {}; 9 | 10 | /** 11 | * determines whether the node should be contracted given the edges state 12 | */ 13 | pvt.nodeShouldBeContracted = function(edgeType){ 14 | var edges = this.get(edgeType); 15 | return edges.length && edges.every(function(edge){ 16 | return edge.get("isContracted"); 17 | }); 18 | }; 19 | 20 | // expand from a node FIXME: DRY with contractFromNode 21 | pvt.expandFromNode = function(notStart, hasContractedEdgesName, edgeType, edgeEnding){ 22 | if (!notStart){ 23 | this.set(hasContractedEdgesName, false); 24 | } 25 | this.get(edgeType) 26 | .each(function(dep) { 27 | dep.set("isContracted", false); 28 | var srcNode = dep.get(edgeEnding); 29 | if (srcNode.get("isContracted")) { 30 | srcNode.set("isContracted", false); 31 | srcNode.set(hasContractedEdgesName, false); 32 | pvt.expandFromNode.call(srcNode, true, hasContractedEdgesName, edgeType, edgeEnding); 33 | } 34 | }); 35 | }; 36 | 37 | // contract from a node 38 | pvt.contractFromNode = function(notStart, hasContractedEdgesName, edgeType, otherEdgeType, edgeEnding){ 39 | if (!notStart){ 40 | this.set(hasContractedEdgesName, true); 41 | } 42 | this.get(edgeType) 43 | .each(function(edge){ 44 | edge.set("isContracted", true); 45 | var srcNode = edge.get(edgeEnding); 46 | if (pvt.nodeShouldBeContracted.call(srcNode, otherEdgeType)){ 47 | srcNode.set("isContracted", true); 48 | srcNode.set(hasContractedEdgesName, false); 49 | pvt.contractFromNode.call(srcNode, true, hasContractedEdgesName, edgeType, otherEdgeType, edgeEnding); 50 | } 51 | }); 52 | }; 53 | 54 | /** 55 | * Node: node model that encompasses several collections and sub-models 56 | */ 57 | return (function(){ 58 | // maintain ancillary/user-specific info and fields in a private object 59 | return Backbone.Model.extend({ 60 | /** 61 | * all possible attributes are present by default 62 | */ 63 | defaults: function() { 64 | return { 65 | title: "", 66 | id: "", 67 | dependencies: new DirectedEdgeCollection(), 68 | outlinks: new DirectedEdgeCollection() 69 | }; 70 | }, 71 | 72 | /** 73 | * Collection fields 74 | */ 75 | collFields: function () { 76 | return ["dependencies", "outlinks"]; 77 | }, 78 | 79 | /** 80 | * Non-collection fields, nominally referred to as text fields 81 | */ 82 | txtFields: function () { 83 | return ["id", "title"]; 84 | }, 85 | 86 | /** 87 | * Returns all outlinks that are not transitive edges 88 | */ 89 | getDirectOutlinks: function () { 90 | return this.get("outlinks").filter(function (edge) { 91 | return !edge.get("isTransitive"); 92 | }); 93 | }, 94 | /** 95 | * Returns all outlinks that are not transitive edges 96 | */ 97 | getDirectDeps: function () { 98 | return this.get("dependencies").filter(function (edge) { 99 | return !edge.get("isTransitive"); 100 | }); 101 | }, 102 | 103 | /** 104 | * @return {boolean} true if the node is visible 105 | */ 106 | isVisible: function(){ 107 | return !this.get("isContracted"); // TODO add learned/hidden properties as well 108 | }, 109 | 110 | /** 111 | * Node's deps should be contracted if it has > 0 outlinks that are all invisible 112 | * 113 | * @return {boolean} whether the node's deps should be contracted 114 | */ 115 | depNodeShouldBeContracted: function(){ 116 | return pvt.nodeShouldBeContracted.call(this, "outlinks"); 117 | }, 118 | 119 | /** 120 | * Node's outlinks should be contracted if it has > 0 deps that are all invisible 121 | * 122 | * @return {boolean} whether the node's outlinks should be contracted 123 | */ 124 | olNodeShouldBeContracted: function(){ 125 | return pvt.nodeShouldBeContracted.call(this, "dependencies"); 126 | }, 127 | 128 | /** 129 | * Contracts the dependencies of the node 130 | */ 131 | contractDeps: function(notStart){ 132 | pvt.contractFromNode.call(this, notStart, "hasContractedDeps", "dependencies", "outlinks", "source"); 133 | }, 134 | 135 | /** 136 | * Expands the dependencies of the node 137 | */ 138 | expandDeps: function(notStart) { 139 | pvt.expandFromNode.call(this, notStart, "hasContractedDeps", "dependencies", "source"); 140 | }, 141 | 142 | /** 143 | * Contract the postrequisites of the node 144 | */ 145 | contractOLs: function(notStart){ 146 | pvt.contractFromNode.call(this, notStart, "hasContractedOLs", "outlinks", "dependencies", "target"); 147 | }, 148 | 149 | /** 150 | * Expand the postrequisites of the node 151 | */ 152 | expandOLs: function(notStart){ 153 | pvt.expandFromNode.call(this, notStart, "hasContractedOLs", "outlinks", "target"); 154 | } 155 | }); 156 | })(); 157 | }); 158 | -------------------------------------------------------------------------------- /js/views/concept-list-view.js: -------------------------------------------------------------------------------- 1 | 2 | /*global define*/ 3 | define(["backbone", "underscore", "jquery", "../views/concept-list-item"], function (Backbone, _, $, ConceptListItem) { 4 | 5 | return (function(){ 6 | // private class variables and methods 7 | var pvt = {}; 8 | 9 | pvt.consts = { 10 | clickedItmClass: "clicked-title", 11 | titleIdPrefix: "node-title-view-", 12 | visibleClass: "show-clist", 13 | hiddenClass: "hide-clist", 14 | viewId: "concept-list-panel", 15 | activeClass: "active", 16 | olId: "concept-list", 17 | wrapperId: "concept-list-wrapper", 18 | templateId : "concept-list-template" 19 | }; 20 | 21 | return Backbone.View.extend({ 22 | 23 | template: _.template(document.getElementById(pvt.consts.templateId).innerHTML), 24 | 25 | id: pvt.consts.viewId, 26 | 27 | events: { 28 | "keyup #concept-list-search-input": "keyUpCLSearchInput", 29 | "click #concept-list-show-button": "clickListShowButton", 30 | "click #concept-list-hide-button": "clickListHideButton", 31 | "click #cancel-search-input": "clickCancelSearchInput" 32 | }, 33 | 34 | /** override in subclass */ 35 | preinitialize: function () {}, 36 | 37 | /** 38 | * initialize this view 39 | * NOTE: this function should typically not be overridden -- use pre/post initialize to customize 40 | */ 41 | initialize: function (inp) { 42 | var thisView = this; 43 | thisView.preinitialize(inp); 44 | thisView.idToTitleView = {}; 45 | thisView.listenTo(thisView.model, "setFocusNode", function (id) { 46 | thisView.changeSelectedTitle(id); 47 | }); 48 | thisView.postinitialize(inp); 49 | }, 50 | 51 | /** override in subclass */ 52 | postinitialize: function (inp) { 53 | var thisView = this; 54 | thisView.ListItem = ConceptListItem; 55 | }, 56 | 57 | /** override in subclass */ 58 | prerender: function (inp) { 59 | var thisView = this; 60 | thisView.$el.html(thisView.template()); 61 | }, 62 | 63 | /** 64 | * Render the concept list view 65 | * NOTE: this function should typically not be overridden -- use pre/post render to customize 66 | */ 67 | render: function () { 68 | var thisView = this, 69 | nodes = thisView.model.getNodes(), 70 | appRouter = thisView.appRouter, // TODO disentangle metacademy object from graph 71 | consts = pvt.consts, 72 | olId = consts.olId, 73 | curNode, 74 | nliview; 75 | thisView.prerender(); 76 | 77 | thisView.isRendered = false; 78 | 79 | var $list = thisView.$el.find("#" + olId), 80 | nodeOrdering = thisView.model.getTopoSort(); 81 | $list = $list.length ? $list : $(document.createElement("ol")); 82 | $list.attr("id", olId); 83 | 84 | // add the list elements with the correct properties 85 | var i; 86 | for(i = 0; i < nodeOrdering.length; i++){ 87 | curNode = nodes.get(nodeOrdering[i]); 88 | nliview = new thisView.ListItem({model: curNode, appRouter: appRouter}); 89 | nliview.parentView = thisView; 90 | thisView.idToTitleView[curNode.id] = nliview; 91 | $list.append(nliview.render().el); 92 | } 93 | 94 | thisView.$list = $list; 95 | 96 | thisView.postrender(); 97 | 98 | thisView.isRendered = true; 99 | return thisView; 100 | }, 101 | 102 | /** can override in subclass */ 103 | postrender: function () { 104 | var thisView = this; 105 | thisView.$el.find("#" + pvt.consts.olId).append(thisView.$list); 106 | }, 107 | 108 | /** 109 | * Return a shallow copy of the private consts 110 | */ 111 | getConstsClone: function () { 112 | return _.clone(pvt.consts); 113 | }, 114 | 115 | /** 116 | * handle click event on the "show sidebar" element 117 | */ 118 | clickListShowButton: function (evt) { 119 | this.$el.parent().addClass(pvt.consts.visibleClass); 120 | this.$el.parent().removeClass(pvt.consts.hiddenClass); 121 | }, 122 | 123 | /** 124 | * handle click event on the "hide sidebar" element 125 | */ 126 | clickListHideButton: function (evt) { 127 | this.$el.parent().removeClass(pvt.consts.visibleClass); 128 | this.$el.parent().addClass(pvt.consts.hiddenClass); 129 | }, 130 | 131 | /** 132 | * Change the selected title element 133 | * 134 | * @param selId - the model id of the selected element 135 | */ 136 | changeSelectedTitle: function (selId) { 137 | var thisView = this, 138 | clickedItmClass = pvt.consts.clickedItmClass; 139 | thisView.$el.find("." + clickedItmClass).removeClass(clickedItmClass); 140 | $("#" + thisView.getDomIdFromId(selId)).addClass(clickedItmClass); 141 | }, 142 | 143 | /** 144 | * get the title dom element corresponding to the input id 145 | */ 146 | getDomIdFromId: function (id) { 147 | return pvt.consts.titleIdPrefix + id; 148 | }, 149 | 150 | /** 151 | * Handle keyup event on search input 152 | */ 153 | keyUpCLSearchInput: function () { 154 | var thisView = this, 155 | $inpEl = $("#concept-list-search-input"), 156 | inpVal = $.trim($inpEl.val()).toLowerCase(); 157 | 158 | if (inpVal.length) { 159 | $("#cancel-search-input").show(); 160 | } else { 161 | $("#cancel-search-input").hide(); 162 | } 163 | 164 | thisView.model.getNodes().each(function (node) { 165 | // TODO notGoalRelevant is not needed for kmapjs, only metacademy 166 | if (!node.get("notGoalRelevant") && (!inpVal.length || node.get("title").toLowerCase().match(inpVal))) { 167 | $("#" + pvt.consts.titleIdPrefix + node.id).show(); 168 | } else { 169 | $("#" + pvt.consts.titleIdPrefix + node.id ).hide(); 170 | } 171 | }); 172 | }, 173 | 174 | /** 175 | * handle click event on search input 176 | */ 177 | clickCancelSearchInput: function () { 178 | $("#concept-list-search-input").val(""); 179 | this.keyUpCLSearchInput(); 180 | }, 181 | 182 | /** 183 | * Return true if the view has been rendered 184 | */ 185 | isViewRendered: function(){ 186 | return this.isRendered; 187 | }, 188 | 189 | /** 190 | * Clean up the view 191 | */ 192 | close: function(){ 193 | this.remove(); 194 | this.unbind(); 195 | } 196 | }); 197 | })(); // end of return statement 198 | }); 199 | -------------------------------------------------------------------------------- /js/lib/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); -------------------------------------------------------------------------------- /js/lib/require-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.8 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{e=i.execCb(c,m,b,e)}catch(d){a=d}else e=i.execCb(c,m,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!== 19 | this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else e=m;this.exports=e;if(this.map.isDefine&&!this.ignore&&(r[c]=e,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= 20 | !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=n(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var m,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=n(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})), 21 | d=l(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(e,c){var d=a.name,g=n(d),B=O;c&&(e=c);B&&(O=!1);q(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{j.exec(e)}catch(ca){return v(A("fromtexteval", 22 | "fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],m)}),e.load(a.name,h,m,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&t(a,"error",u(this,this.errback))}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n, 24 | nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};F(a,function(a,b){e[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name, 25 | location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return v(A("requireargs", 26 | "Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(j.get)return j.get(i,e,a,d);g=n(e,a,!1,!0);g=g.id;return!s(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});C()});return d}f=f||{};Q(d,{isBrowser:z,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error", 34 | b.onScriptError,!1)),h.src=d,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+d,l,[c]))}};z&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,t.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",t.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),t.deps=t.deps?t.deps.concat(q):[q],!0}); 35 | define=function(b,c,d){var h,j;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=null);!c&&H(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j?j.defQueue: 36 | R).push([b,c,d])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(t)}})(this); 37 | -------------------------------------------------------------------------------- /js/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | (function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.0.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=k[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&window.ActiveXObject&&!(window.external&&window.external.msActiveXFilteringEnabled)){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var k={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var S=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var $=/\((.*?)\)/g;var T=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(S.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace($,"(?:$1)?").replace(T,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var P=/^\/+|\/+$/g;var O=/msie [\w.]+/;var C=/\/$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.substr(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({},{root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=O.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(P,"/");if(r&&this._wantsHashChange){this.iframe=a.$('