├── .gitignore ├── .travis.yml ├── CNAME ├── README.md ├── build ├── .requirejs_version ├── build.js ├── build.sh ├── output │ ├── css │ │ └── styles.css │ ├── index.html │ └── js │ │ └── main.js ├── r.js ├── server └── update_requirejs.sh ├── css ├── 960.css ├── bootstrap.css ├── normalize.css ├── styles.css └── theme.css ├── images ├── backboneboilerplate_logo.png ├── favicon.png └── page_bg.png ├── img ├── glyphicons-halflings-white.png └── glyphicons-halflings.png ├── index.html ├── js ├── boilerplate.js ├── collections │ └── projects.js ├── events.js ├── libs │ ├── backbone │ │ ├── backbone-min.js │ │ └── backbone-optamd3-min.js │ ├── bootstrap │ │ └── bootstrap.js │ ├── jquery │ │ └── jquery-min.js │ ├── lodash │ │ └── lodash.js │ ├── require │ │ ├── require.js │ │ └── text.js │ ├── springy │ │ ├── springy.js │ │ └── springyui.js │ └── underscore │ │ └── underscore-min.js ├── main.js ├── models │ └── projects.js ├── router.js ├── views │ ├── app.js │ ├── backbone │ │ ├── page.js │ │ ├── section.js │ │ └── sidemenu.js │ ├── dashboard │ │ └── page.js │ ├── footer │ │ └── footer.js │ ├── header │ │ └── menu.js │ ├── manager │ │ └── page.js │ ├── modules │ │ └── page.js │ └── optimize │ │ └── page.js └── vm.js ├── package.json └── templates ├── backbone ├── page.html └── sidemenu.html ├── dashboard └── page.html ├── footer └── footer.html ├── header └── menu.html ├── home └── main.html ├── layout.html ├── manager └── page.html ├── modules ├── modules.html ├── page.html └── untitled └── optimize └── page.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | # test on two node.js versions: 0.4 and 0.6 4 | node_js: 5 | - 0.4 6 | - 0.6 7 | - 0.7 8 | 9 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | backboneboilerplate.com 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backbone boilerplate 2 | [http://backboneboilerplate.com](http://backboneboilerplate.com) is a community driven effort to help developers learn and rapidly deploy single page web applications. 3 | 4 | ## Philosophy 5 | Coming soon 6 | 7 | 8 | ## Other resources 9 | 10 | [http://backbonetutorials.com](http://backbonetutorials.com) - As single page apps and large scale javascript applications become more prominent on the web, useful resources for those developers who are jumping the ship are crucial. 11 | 12 | ## About the author 13 | 14 | **Contact:** 15 | 16 | * [@neutralthoughts](http://twitter.com/neutralthoughts) on twitter 17 | * Github - https://github.com/thomasdavis 18 | * thomasalwyndavis@gmail.com 19 | 20 | **Projects:** 21 | 22 | * Javascript Library CDN - http://cdnjs.com 23 | * Backbone.js Tutorials - http://backbonetutorials.com 24 | * Proposal Generation Start up - http://protosal.com 25 | * Technical Blog - http://thomasdavis.github.com 26 | * Quora - http://www.quora.com/Thomas-Davis 27 | * StackOverflow - http://stackoverflow.com/users/580675/thomas-davis 28 | 29 | Love you mum! 30 | Clicky 31 | -------------------------------------------------------------------------------- /build/.requirejs_version: -------------------------------------------------------------------------------- 1 | 2.1.5 2 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | ({ 2 | name: '../js/main', 3 | baseUrl: '../js', 4 | out: 'output/js/main.js', 5 | findNestedDependencies: true, 6 | mainConfigFile: '../js/main.js' 7 | }) 8 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf output 4 | node r.js -o build.js 5 | node r.js -o cssIn=../css/styles.css out=output/css/styles.css 6 | 7 | cp ../index.html output/index.html 8 | REQUIRE_VERSION='2.1.5' 9 | SEDCMD='sed -i' 10 | if [[ $OSTYPE == *"darwin"* ]]; then 11 | SEDCMD=$SEDCMD' .tmp' 12 | fi 13 | SEDCMD=$SEDCMD' s/js\/libs\/require\/require.js/http:\/\/requirejs.org\/docs\/release\/'$REQUIRE_VERSION'\/minified\/require.js/g output/index.html' 14 | $SEDCMD 15 | rm -f output/*.tmp 16 | -------------------------------------------------------------------------------- /build/output/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Backbone Boilerplate 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /build/server: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var express = require("express"); 3 | var site = express.createServer(); 4 | 5 | site.use(express.static(__dirname + '/..')); 6 | 7 | site.use(express.favicon("./favicon.ico")); 8 | 9 | site.get("*", function(req, res) { 10 | fs.createReadStream("./index.html").pipe(res); 11 | }); 12 | 13 | site.listen(1337); 14 | 15 | console.log("Server listening on http://localhost:1337"); 16 | -------------------------------------------------------------------------------- /build/update_requirejs.sh: -------------------------------------------------------------------------------- 1 | # This script updates requireJS to the latest stable, minified version. 2 | # It drops the new script into js/libs/require and also updates the build script 3 | 4 | #!/bin/bash 5 | 6 | # Make sure curl is available 7 | curl -V >> /dev/null 2>> /dev/null 8 | if [[ $? -ne 0 ]] ; then 9 | echo "ERROR: You need curl to run this script. Please install curl using your preferred package manager and try again" >& 2 10 | exit 1 11 | fi 12 | 13 | echo "Finding latest version of requireJS..." 14 | REQUIREJS_URI=`curl -f http://requirejs.org/docs/download.html 2>> /dev/null | \ 15 | grep -o 'http://requirejs.org/docs/release/[0-9]\.[0-9]\.[0-9]/minified/require.js'` 16 | RJS_VERSION=`echo $REQUIREJS_URI | grep -o [0-9]\.[0-9]\.[0-9]` 17 | 18 | # Don't update unless we have to 19 | BASE_RJS_VERSION=`cat .requirejs_version` 20 | if [[ $RJS_VERSION = $BASE_RJS_VERSION ]] ; then 21 | echo "You're version of requireJS is up to date with the latest stable version ($RJS_VERSION)" >& 2 22 | exit 0 23 | fi 24 | 25 | echo "You're about to upgrade requireJS from version $BASE_RJS_VERSION to $RJS_VERSION." 26 | echo -n "Would you like to continue? [y/N]: " 27 | 28 | read CONFIRM 29 | if [[ "$CONFIRM" = "N" ]] ; then 30 | echo "Update aborted" 31 | exit 2 32 | elif [[ "$CONFIRM" != "y" ]] ; then 33 | echo "Unrecognized response" 34 | exit 3 35 | fi 36 | 37 | echo "Fetching requireJS v$RJS_VERSION" 38 | REQUIRE_PATH=`dirname $PWD`/js/libs/require 39 | # If, for some reason, curl fails, we'll still have our old copy of require 40 | mv $REQUIRE_PATH/require.js $REQUIRE_PATH/require.js.old 41 | curl -f -# $REQUIREJS_URI -o $REQUIRE_PATH/require.js 42 | # If curl fails... 43 | if [[ $? -ne 0 ]] ; then 44 | # Put the old version of require back. Otherwise... 45 | mv $REQUIRE_PATH/require.js.old $REQUIRE_PATH/require.js 46 | else 47 | # Remove the old version of require completely 48 | rm -f ../js/libs/require/require.js.old 49 | echo "requireJS has been updated and placed in its proper place in the libs folder" 50 | fi 51 | 52 | sed -i "s/^REQUIRE_VERSION='[0-9]\.[0-9]\.[0-9]'/REQUIRE_VERSION='$RJS_VERSION'/g" build.sh 53 | echo "Build script has been updated to use the latest requireJS version" 54 | 55 | # Finally, store the new version of requireJS in .requirejs_version 56 | echo $RJS_VERSION > .requirejs_version 57 | -------------------------------------------------------------------------------- /css/960.css: -------------------------------------------------------------------------------- 1 | /* 2 | 960 Grid System ~ Core CSS. 3 | Learn more ~ http://960.gs/ 4 | 5 | Licensed under GPL and MIT. 6 | */ 7 | 8 | /* 9 | Forces backgrounds to span full width, 10 | even if there is horizontal scrolling. 11 | Increase this if your layout is wider. 12 | 13 | Note: IE6 works fine without this fix. 14 | */ 15 | 16 | body { 17 | min-width: 960px; 18 | } 19 | 20 | /* Container 21 | ----------------------------------------------------------------------------------------------------*/ 22 | 23 | .container_16 { 24 | margin-left: auto; 25 | margin-right: auto; 26 | width: 960px; 27 | } 28 | 29 | /* Grid >> Global 30 | ----------------------------------------------------------------------------------------------------*/ 31 | 32 | .grid_1, 33 | .grid_2, 34 | .grid_3, 35 | .grid_4, 36 | .grid_5, 37 | .grid_6, 38 | .grid_7, 39 | .grid_8, 40 | .grid_9, 41 | .grid_10, 42 | .grid_11, 43 | .grid_12, 44 | .grid_13, 45 | .grid_14, 46 | .grid_15, 47 | .grid_16 { 48 | display: inline; 49 | float: left; 50 | margin-left: 10px; 51 | margin-right: 10px; 52 | } 53 | 54 | .push_1, .pull_1, 55 | .push_2, .pull_2, 56 | .push_3, .pull_3, 57 | .push_4, .pull_4, 58 | .push_5, .pull_5, 59 | .push_6, .pull_6, 60 | .push_7, .pull_7, 61 | .push_8, .pull_8, 62 | .push_9, .pull_9, 63 | .push_10, .pull_10, 64 | .push_11, .pull_11, 65 | .push_12, .pull_12, 66 | .push_13, .pull_13, 67 | .push_14, .pull_14, 68 | .push_15, .pull_15, 69 | .push_16, .pull_16 { 70 | position: relative; 71 | } 72 | 73 | /* Grid >> Children (Alpha ~ First, Omega ~ Last) 74 | ----------------------------------------------------------------------------------------------------*/ 75 | 76 | .alpha { 77 | margin-left: 0; 78 | } 79 | 80 | .omega { 81 | margin-right: 0; 82 | } 83 | 84 | /* Grid >> 16 Columns 85 | ----------------------------------------------------------------------------------------------------*/ 86 | 87 | .container_16 .grid_1 { 88 | width: 40px; 89 | } 90 | 91 | .container_16 .grid_2 { 92 | width: 100px; 93 | } 94 | 95 | .container_16 .grid_3 { 96 | width: 160px; 97 | } 98 | 99 | .container_16 .grid_4 { 100 | width: 220px; 101 | } 102 | 103 | .container_16 .grid_5 { 104 | width: 280px; 105 | } 106 | 107 | .container_16 .grid_6 { 108 | width: 340px; 109 | } 110 | 111 | .container_16 .grid_7 { 112 | width: 400px; 113 | } 114 | 115 | .container_16 .grid_8 { 116 | width: 460px; 117 | } 118 | 119 | .container_16 .grid_9 { 120 | width: 520px; 121 | } 122 | 123 | .container_16 .grid_10 { 124 | width: 580px; 125 | } 126 | 127 | .container_16 .grid_11 { 128 | width: 640px; 129 | } 130 | 131 | .container_16 .grid_12 { 132 | width: 700px; 133 | } 134 | 135 | .container_16 .grid_13 { 136 | width: 760px; 137 | } 138 | 139 | .container_16 .grid_14 { 140 | width: 820px; 141 | } 142 | 143 | .container_16 .grid_15 { 144 | width: 880px; 145 | } 146 | 147 | .container_16 .grid_16 { 148 | width: 940px; 149 | } 150 | 151 | /* Prefix Extra Space >> 16 Columns 152 | ----------------------------------------------------------------------------------------------------*/ 153 | 154 | .container_16 .prefix_1 { 155 | padding-left: 60px; 156 | } 157 | 158 | .container_16 .prefix_2 { 159 | padding-left: 120px; 160 | } 161 | 162 | .container_16 .prefix_3 { 163 | padding-left: 180px; 164 | } 165 | 166 | .container_16 .prefix_4 { 167 | padding-left: 240px; 168 | } 169 | 170 | .container_16 .prefix_5 { 171 | padding-left: 300px; 172 | } 173 | 174 | .container_16 .prefix_6 { 175 | padding-left: 360px; 176 | } 177 | 178 | .container_16 .prefix_7 { 179 | padding-left: 420px; 180 | } 181 | 182 | .container_16 .prefix_8 { 183 | padding-left: 480px; 184 | } 185 | 186 | .container_16 .prefix_9 { 187 | padding-left: 540px; 188 | } 189 | 190 | .container_16 .prefix_10 { 191 | padding-left: 600px; 192 | } 193 | 194 | .container_16 .prefix_11 { 195 | padding-left: 660px; 196 | } 197 | 198 | .container_16 .prefix_12 { 199 | padding-left: 720px; 200 | } 201 | 202 | .container_16 .prefix_13 { 203 | padding-left: 780px; 204 | } 205 | 206 | .container_16 .prefix_14 { 207 | padding-left: 840px; 208 | } 209 | 210 | .container_16 .prefix_15 { 211 | padding-left: 900px; 212 | } 213 | 214 | /* Suffix Extra Space >> 16 Columns 215 | ----------------------------------------------------------------------------------------------------*/ 216 | 217 | .container_16 .suffix_1 { 218 | padding-right: 60px; 219 | } 220 | 221 | .container_16 .suffix_2 { 222 | padding-right: 120px; 223 | } 224 | 225 | .container_16 .suffix_3 { 226 | padding-right: 180px; 227 | } 228 | 229 | .container_16 .suffix_4 { 230 | padding-right: 240px; 231 | } 232 | 233 | .container_16 .suffix_5 { 234 | padding-right: 300px; 235 | } 236 | 237 | .container_16 .suffix_6 { 238 | padding-right: 360px; 239 | } 240 | 241 | .container_16 .suffix_7 { 242 | padding-right: 420px; 243 | } 244 | 245 | .container_16 .suffix_8 { 246 | padding-right: 480px; 247 | } 248 | 249 | .container_16 .suffix_9 { 250 | padding-right: 540px; 251 | } 252 | 253 | .container_16 .suffix_10 { 254 | padding-right: 600px; 255 | } 256 | 257 | .container_16 .suffix_11 { 258 | padding-right: 660px; 259 | } 260 | 261 | .container_16 .suffix_12 { 262 | padding-right: 720px; 263 | } 264 | 265 | .container_16 .suffix_13 { 266 | padding-right: 780px; 267 | } 268 | 269 | .container_16 .suffix_14 { 270 | padding-right: 840px; 271 | } 272 | 273 | .container_16 .suffix_15 { 274 | padding-right: 900px; 275 | } 276 | 277 | /* Push Space >> 16 Columns 278 | ----------------------------------------------------------------------------------------------------*/ 279 | 280 | .container_16 .push_1 { 281 | left: 60px; 282 | } 283 | 284 | .container_16 .push_2 { 285 | left: 120px; 286 | } 287 | 288 | .container_16 .push_3 { 289 | left: 180px; 290 | } 291 | 292 | .container_16 .push_4 { 293 | left: 240px; 294 | } 295 | 296 | .container_16 .push_5 { 297 | left: 300px; 298 | } 299 | 300 | .container_16 .push_6 { 301 | left: 360px; 302 | } 303 | 304 | .container_16 .push_7 { 305 | left: 420px; 306 | } 307 | 308 | .container_16 .push_8 { 309 | left: 480px; 310 | } 311 | 312 | .container_16 .push_9 { 313 | left: 540px; 314 | } 315 | 316 | .container_16 .push_10 { 317 | left: 600px; 318 | } 319 | 320 | .container_16 .push_11 { 321 | left: 660px; 322 | } 323 | 324 | .container_16 .push_12 { 325 | left: 720px; 326 | } 327 | 328 | .container_16 .push_13 { 329 | left: 780px; 330 | } 331 | 332 | .container_16 .push_14 { 333 | left: 840px; 334 | } 335 | 336 | .container_16 .push_15 { 337 | left: 900px; 338 | } 339 | 340 | /* Pull Space >> 16 Columns 341 | ----------------------------------------------------------------------------------------------------*/ 342 | 343 | .container_16 .pull_1 { 344 | left: -60px; 345 | } 346 | 347 | .container_16 .pull_2 { 348 | left: -120px; 349 | } 350 | 351 | .container_16 .pull_3 { 352 | left: -180px; 353 | } 354 | 355 | .container_16 .pull_4 { 356 | left: -240px; 357 | } 358 | 359 | .container_16 .pull_5 { 360 | left: -300px; 361 | } 362 | 363 | .container_16 .pull_6 { 364 | left: -360px; 365 | } 366 | 367 | .container_16 .pull_7 { 368 | left: -420px; 369 | } 370 | 371 | .container_16 .pull_8 { 372 | left: -480px; 373 | } 374 | 375 | .container_16 .pull_9 { 376 | left: -540px; 377 | } 378 | 379 | .container_16 .pull_10 { 380 | left: -600px; 381 | } 382 | 383 | .container_16 .pull_11 { 384 | left: -660px; 385 | } 386 | 387 | .container_16 .pull_12 { 388 | left: -720px; 389 | } 390 | 391 | .container_16 .pull_13 { 392 | left: -780px; 393 | } 394 | 395 | .container_16 .pull_14 { 396 | left: -840px; 397 | } 398 | 399 | .container_16 .pull_15 { 400 | left: -900px; 401 | } 402 | 403 | /* `Clear Floated Elements 404 | ----------------------------------------------------------------------------------------------------*/ 405 | 406 | /* http://sonspring.com/journal/clearing-floats */ 407 | 408 | .clear { 409 | clear: both; 410 | display: block; 411 | overflow: hidden; 412 | visibility: hidden; 413 | width: 0; 414 | height: 0; 415 | } 416 | 417 | /* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */ 418 | 419 | .clearfix:before, 420 | .clearfix:after, 421 | .container_16:before, 422 | .container_16:after { 423 | content: '.'; 424 | display: block; 425 | overflow: hidden; 426 | visibility: hidden; 427 | font-size: 0; 428 | line-height: 0; 429 | width: 0; 430 | height: 0; 431 | } 432 | 433 | .clearfix:after, 434 | .container_16:after { 435 | clear: both; 436 | } 437 | 438 | /* 439 | The following zoom:1 rule is specifically for IE6 + IE7. 440 | Move to separate stylesheet if invalid CSS is a problem. 441 | */ 442 | 443 | .clearfix, 444 | .container_16 { 445 | zoom: 1; 446 | } -------------------------------------------------------------------------------- /css/normalize.css: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | 10 | /* ============================================================================= 11 | HTML5 element display 12 | ========================================================================== */ 13 | 14 | article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } 15 | audio[controls], canvas, video { display: inline-block; *display: inline; *zoom: 1; } 16 | 17 | 18 | /* ============================================================================= 19 | Base 20 | ========================================================================== */ 21 | 22 | /* 23 | * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units 24 | * http://clagnut.com/blog/348/#c790 25 | * 2. Force vertical scrollbar in non-IE 26 | * 3. Remove Android and iOS tap highlight color to prevent entire container being highlighted 27 | * www.yuiblog.com/blog/2010/10/01/quick-tip-customizing-the-mobile-safari-tap-highlight-color/ 28 | * 4. Prevent iOS text size adjust on device orientation change, without disabling user zoom 29 | * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ 30 | */ 31 | 32 | html { font-size: 100%; overflow-y: scroll; -webkit-overflow-scrolling: touch; -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } 33 | 34 | body { margin: 0; font-size: 13px; line-height: 1.231; } 35 | 36 | body, button, input, select, textarea { font-family: sans-serif; color: #222; } 37 | 38 | /* 39 | * These selection declarations have to be separate 40 | * No text-shadow: twitter.com/miketaylr/status/12228805301 41 | * Also: hot pink! 42 | */ 43 | 44 | ::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } 45 | ::selection { background: #fe57a1; color: #fff; text-shadow: none; } 46 | 47 | 48 | /* ============================================================================= 49 | Links 50 | ========================================================================== */ 51 | 52 | a { color: #00e; } 53 | a:visited { color: #551a8b; } 54 | a:focus { outline: thin dotted; } 55 | 56 | /* Improve readability when focused and hovered in all browsers: people.opera.com/patrickl/experiments/keyboard/test */ 57 | a:hover, a:active { outline: 0; } 58 | 59 | 60 | /* ============================================================================= 61 | Typography 62 | ========================================================================== */ 63 | 64 | abbr[title] { border-bottom: 1px dotted; } 65 | 66 | b, strong { font-weight: bold; } 67 | 68 | blockquote { margin: 1em 40px; } 69 | 70 | dfn { font-style: italic; } 71 | 72 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 73 | 74 | ins { background: #ff9; color: #000; text-decoration: none; } 75 | 76 | mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } 77 | 78 | /* Redeclare monospace font family: en.wikipedia.org/wiki/User:Davidgothberg/Test59 */ 79 | pre, code, kbd, samp { font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 1em; } 80 | 81 | /* Improve readability of pre-formatted text in all browsers */ 82 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } 83 | 84 | q { quotes: none; } 85 | q:before, q:after { content: ""; content: none; } 86 | 87 | small { font-size: 85%; } 88 | 89 | /* Position subscript and superscript content without affecting line-height: gist.github.com/413930 */ 90 | sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } 91 | sup { top: -0.5em; } 92 | sub { bottom: -0.25em; } 93 | 94 | 95 | /* ============================================================================= 96 | Lists 97 | ========================================================================== */ 98 | 99 | ul, ol { margin: 1em 0; padding: 0 0 0 40px; } 100 | dd { margin: 0 0 0 40px; } 101 | nav ul, nav ol { list-style: none; margin: 0; padding: 0; } 102 | 103 | 104 | /* ============================================================================= 105 | Embedded content 106 | ========================================================================== */ 107 | 108 | /* 109 | * Improve image quality when scaled in IE7 110 | * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ 111 | */ 112 | 113 | img { border: 0; -ms-interpolation-mode: bicubic; } 114 | 115 | /* 116 | * Correct overflow displayed oddly in IE9 117 | */ 118 | 119 | svg:not(:root) { 120 | overflow: hidden; 121 | } 122 | 123 | 124 | /* ============================================================================= 125 | Figures 126 | ========================================================================== */ 127 | 128 | figure { margin: 0; } 129 | 130 | 131 | /* ============================================================================= 132 | Forms 133 | ========================================================================== */ 134 | 135 | form { margin: 0; } 136 | fieldset { border: 0; margin: 0; padding: 0; } 137 | 138 | /* 139 | * 1. Correct color not inheriting in IE6/7/8/9 140 | * 2. Correct alignment displayed oddly in IE6/7 141 | */ 142 | 143 | legend { border: 0; *margin-left: -7px; padding: 0; } 144 | 145 | /* Indicate that 'label' will shift focus to the associated form element */ 146 | label { cursor: pointer; } 147 | 148 | /* 149 | * 1. Correct font-size not inheriting in all browsers 150 | * 2. Remove margins in FF3/4 S5 Chrome 151 | * 3. Define consistent vertical alignment display in all browsers 152 | */ 153 | 154 | button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } 155 | 156 | /* 157 | * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet) 158 | * 2. Correct inner spacing displayed oddly in IE6/7 159 | */ 160 | 161 | button, input { line-height: normal; *overflow: visible; } 162 | 163 | /* 164 | * 1. Display hand cursor for clickable form elements 165 | * 2. Allow styling of clickable form elements in iOS 166 | */ 167 | 168 | button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; } 169 | 170 | /* 171 | * Consistent box sizing and appearance 172 | */ 173 | 174 | input[type="checkbox"], input[type="radio"] { box-sizing: border-box; } 175 | input[type="search"] { -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } 176 | 177 | /* 178 | * Remove inner padding and border in FF3/4 179 | * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ 180 | */ 181 | 182 | button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } 183 | 184 | /* Remove default vertical scrollbar in IE6/7/8/9 */ 185 | textarea { overflow: auto; vertical-align: top; } 186 | 187 | /* Colors for form validity */ 188 | input:valid, textarea:valid { } 189 | input:invalid, textarea:invalid { background-color: #f0dddd; } 190 | 191 | 192 | /* ============================================================================= 193 | Tables 194 | ========================================================================== */ 195 | 196 | table { border-collapse: collapse; border-spacing: 0; } 197 | 198 | 199 | /* ============================================================================= 200 | Non-semantic helper classes 201 | Please define your styles before this section. 202 | ========================================================================== */ 203 | 204 | /* For image replacement */ 205 | .ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } 206 | .ir br { display: none; } 207 | 208 | /* Hide for both screenreaders and browsers: 209 | css-discuss.incutio.com/wiki/Screenreader_Visibility */ 210 | .hidden { display: none; visibility: hidden; } 211 | 212 | /* Hide only visually, but have it available for screenreaders: by Jon Neal. 213 | www.webaim.org/techniques/css/invisiblecontent/ & j.mp/visuallyhidden */ 214 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } 215 | 216 | /* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: drupal.org/node/897638 */ 217 | .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } 218 | 219 | /* Hide visually and from screenreaders, but maintain layout */ 220 | .invisible { visibility: hidden; } 221 | 222 | /* Contain floats: nicolasgallagher.com/micro-clearfix-hack/ */ 223 | .clearfix:before, .clearfix:after { content: ""; display: table; } 224 | .clearfix:after { clear: both; } 225 | .clearfix { zoom: 1; } 226 | 227 | 228 | 229 | /* ============================================================================= 230 | PLACEHOLDER Media Queries for Responsive Design. 231 | These override the primary ('mobile first') styles 232 | Modify as content requires. 233 | ========================================================================== */ 234 | 235 | @media only screen and (min-width: 480px) { 236 | /* Style adjustments for viewports 480px and over go here */ 237 | 238 | } 239 | 240 | @media only screen and (min-width: 768px) { 241 | /* Style adjustments for viewports 768px and over go here */ 242 | 243 | } 244 | 245 | 246 | /* ============================================================================= 247 | Print styles. 248 | Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ 249 | ========================================================================== */ 250 | 251 | @media print { 252 | * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: sanbeiji.com/archives/953 */ 253 | a, a:visited { color: #444 !important; text-decoration: underline; } 254 | a[href]:after { content: " (" attr(href) ")"; } 255 | abbr[title]:after { content: " (" attr(title) ")"; } 256 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ 257 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 258 | thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */ 259 | tr, img { page-break-inside: avoid; } 260 | img { max-width: 100% !important; } 261 | @page { margin: 0.5cm; } 262 | p, h2, h3 { orphans: 3; widows: 3; } 263 | h2, h3{ page-break-after: avoid; } 264 | } 265 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | # Backbone Boilerplate 4 | 5 | */ 6 | 7 | /* 8 | ### Third Party 9 | */ 10 | @import url("bootstrap.css"); 11 | 12 | 13 | -------------------------------------------------------------------------------- /css/theme.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* 5 | ## Main Menu Template 6 | ``` 7 |
8 | 19 |
20 | 21 | ``` 22 | ## Main Menu Styles 23 | */ 24 | 25 | 26 | .main-menu-container { 27 | margin-bottom: 20px; 28 | } 29 | 30 | .main-menu { 31 | margin: 10px 0 10px 0; 32 | } 33 | .main-menu ul { 34 | margin: 0; 35 | padding: 0; 36 | list-style: none; 37 | } 38 | 39 | .main-menu li { 40 | margin-right: 10px; 41 | float: left; 42 | } 43 | .main-menu li a, 44 | .main-menu li a:visited { 45 | padding: 5px; 46 | color: #515151; 47 | font-size: 18px; 48 | text-decoration: none; 49 | display: inline-block; 50 | } 51 | .main-menu li a.active, 52 | .main-menu li a:hover { 53 | background: #c0c0c0; 54 | border-radius: 6px; 55 | } 56 | .divider { 57 | clear: both; 58 | border-top: 1px solid #919191; 59 | border-bottom: 1px solid #ececec; 60 | } 61 | /* 62 | 63 | ## Heading and Paragraph 64 | 65 | ``` 66 |

67 |

68 | ``` 69 | */ 70 | 71 | .h3 { 72 | list-style: none; 73 | margin: 0; 74 | padding: 0; 75 | } 76 | 77 | h1, h2, h3, h4, h5, h6 { 78 | font-family: 'Squada One'; 79 | font-weight: normal; 80 | margin: 0; 81 | line-height: 1em; 82 | } 83 | 84 | h2 { font-size: 40px; } 85 | h3 { font-size: 36px; } 86 | h4 { font-size: 32px; } 87 | h5 { font-size: 28px; } 88 | h6 { font-size: 24px; } 89 | 90 | /* 91 | 92 | 93 | 94 | /* 95 | 96 | ## Action buttons 97 | 98 | adadasd 99 | 100 | ``` 101 | 107 | ``` 108 | */ 109 | 110 | .action-list { 111 | list-style: none; 112 | margin: 0; 113 | padding: 0; 114 | } 115 | .action-list li { 116 | 117 | text-align: center; 118 | } 119 | .action-list li a { 120 | font-size: 34px; 121 | color: #594936; 122 | font-weight: bold; 123 | } 124 | .action-list li a:visited { 125 | color: #594936; 126 | } 127 | 128 | 129 | 130 | body { 131 | background: #dddddd url('../images/page_bg.png'); 132 | color: #11141b; 133 | font-family: 'PT Sans', sans-serif; 134 | font-size: 14px; 135 | } 136 | 137 | .logo { 138 | color: #5a4935; 139 | padding: 0; 140 | margin: 0; 141 | text-shadow: 0 1px 1px #fff; 142 | text-align: center; 143 | font-family: 'Squada One'; 144 | font-size: 112px; 145 | font-weight: normal; 146 | text-transform: uppercase; 147 | } 148 | /* 149 | 150 | Boilerplate Stamp! 151 | 152 | ``` 153 | 154 | ``` 155 | */ 156 | .bp_logo { 157 | width: 280px; 158 | height: 266px; 159 | background: url('../images/backboneboilerplate_logo.png'); 160 | } 161 | 162 | #springydemo { 163 | border-radius: 5px; 164 | background-color: rgba(0,0,0,0.2); 165 | } 166 | 167 | .container { 168 | margin-bottom: 150px; 169 | margin-top: 4em; 170 | } 171 | 172 | code, pre { 173 | background-color: #F8F8F8; 174 | border: solid 1px #ccc; 175 | border-radius: 3px; 176 | display: block; 177 | font-family: monospace; 178 | padding: 10px; 179 | } 180 | 181 | /* 182 | ## Logo 183 | ``` 184 |

BACKBONE BOILERPLATE

185 | ``` 186 | 187 | ## Colors 188 | 189 | For the page header and links 190 | \#5a4935 191 | 192 | For body text 193 | \#11141b 194 | 195 | For Navigation links 196 | \#515151 197 | 198 | For Navigation current state 199 | \#c0c0c0 200 | 201 | Body Background color 202 | \#dddddd 203 | 204 | hr top 205 | \#919191 206 | hr bottom 207 | \#ececec 208 | 209 | 210 | ## Fonts 211 | 212 | Body 213 | PT Sans 214 | 215 | Header 216 | Squada One 217 | 218 | */ 219 | -------------------------------------------------------------------------------- /images/backboneboilerplate_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasdavis/backboneboilerplate/40d8c26c40f8339af4534da51d7ce6d5f70b6db6/images/backboneboilerplate_logo.png -------------------------------------------------------------------------------- /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasdavis/backboneboilerplate/40d8c26c40f8339af4534da51d7ce6d5f70b6db6/images/favicon.png -------------------------------------------------------------------------------- /images/page_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasdavis/backboneboilerplate/40d8c26c40f8339af4534da51d7ce6d5f70b6db6/images/page_bg.png -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasdavis/backboneboilerplate/40d8c26c40f8339af4534da51d7ce6d5f70b6db6/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasdavis/backboneboilerplate/40d8c26c40f8339af4534da51d7ce6d5f70b6db6/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Backbone Boilerplate 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /js/boilerplate.js: -------------------------------------------------------------------------------- 1 | // Use this as a quick template for future modules 2 | define([ 3 | 'jquery', 4 | 'underscore', 5 | 'backbone' 6 | ], function($, _, Backbone){ 7 | 8 | return {}; 9 | }); 10 | -------------------------------------------------------------------------------- /js/collections/projects.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'models/projects' 6 | ], function($, _, Backbone, projectsModel){ 7 | var projectsCollection = Backbone.Collection.extend({ 8 | model: projectsModel, 9 | initialize: function(){ 10 | 11 | } 12 | 13 | }); 14 | 15 | return projectsCollection; 16 | }); 17 | -------------------------------------------------------------------------------- /js/events.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'underscore', 4 | 'backbone' 5 | ], function($, _, Backbone){ 6 | var vent = _.extend({}, Backbone.Events); 7 | return vent; 8 | }); -------------------------------------------------------------------------------- /js/libs/backbone/backbone-min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.9.2 2 | 3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 | // Backbone may be freely distributed under the MIT license. 5 | // For all details and documentation: 6 | // http://backbonejs.org 7 | (function(h,g){typeof exports!=="undefined"?g(h,exports,require("underscore")):typeof define==="function"&&define.amd?define(["underscore","jquery","exports"],function(f,i,p){h.Backbone=g(h,p,f,i)}):h.Backbone=g(h,{},h._,h.jQuery||h.Zepto||h.ender)})(this,function(h,g,f,i){var p=h.Backbone,y=Array.prototype.slice,z=Array.prototype.splice;g.VERSION="0.9.2";g.setDomLibrary=function(a){i=a};g.noConflict=function(){h.Backbone=p;return g};g.emulateHTTP=false;g.emulateJSON=false;var q=/\s+/,l=g.Events= 8 | {on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(q);for(d=this._callbacks||(this._callbacks={});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,k,g,j,h;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(q):f.keys(e);d=a.shift();)if(k=e[d],delete e[d],k&&(b||c))for(g=k.tail;(k=k.next)!==g;)if(j=k.callback,h=k.context,b&&j!==b||c&&h!==c)this.on(d,j,h);return this}}, 9 | trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(q);for(g=y.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};l.bind=l.on;l.unbind=l.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);if(b&&b.collection)this.collection=b.collection; 10 | this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent={};this._pending={};this.set(a,{silent:true});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,l,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b; 11 | if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(b==null?"":""+b)},has:function(a){return this.get(a)!=null},set:function(a,b,c){var d,e;f.isObject(a)||a==null?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;if(d instanceof o)d=d.attributes;if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return false;if(this.idAttribute in d)this.id=d[this.idAttribute];var b=c.changes={},g=this.attributes,h=this._escapedAttributes,j=this._previousAttributes|| 12 | {};for(e in d){a=d[e];if(!f.isEqual(g[e],a)||c.unset&&f.has(g,e))delete h[e],(c.silent?this._silent:b)[e]=true;c.unset?delete g[e]:g[e]=a;!f.isEqual(j[e],a)||f.has(g,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=true)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=true;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=true;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a= 13 | a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return false;c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||a==null?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return false;e=f.clone(this.attributes)}a=f.extend({},c,{silent:true});if(d&&!this.set(d,c.wait?a:c))return false;var k=this,h=c.success;c.success=function(a,b,e){b=k.parse(a,e); 14 | c.wait&&(delete c.wait,b=f.extend(d||{},b));if(!k.set(b,c))return false;h?h(k,a):k.trigger("sync",k,a,c)};c.error=g.wrapError(c.error,k,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),false;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync|| 15 | g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();return this.isNew()?a:a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return this.id==null},change:function(a){a||(a={});var b=this._changing;this._changing=true;for(var c in this._silent)this._pending[c]=true;var d=f.extend({},a.changes,this._silent); 16 | this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending={};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=false;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed): 17 | false;var b,c=false,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return true;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return true;b&&b.error? 18 | b.error(this,c,b):this.trigger("error",this,c,b);return false}});var r=g.Collection=function(a,b){b||(b={});if(b.model)this.model=b.model;if(b.comparator)this.comparator=b.comparator;this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:true,parse:b.parse})};f.extend(r.prototype,l,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,h,j={},i={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d= 19 | a.length;c').hide().appendTo("body")[0].contentWindow,this.navigate(a);if(this._hasPushState)i(window).bind("popstate", 30 | this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)i(window).bind("hashchange",this.checkUrl);else if(this._wantsHashChange)this._checkUrlInterval=setInterval(this.checkUrl,this.interval);this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,true),window.location.replace(this.options.root+"#"+this.fragment),true;else if(this._wantsPushState&&this._hasPushState&& 31 | b&&a.hash)this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()},stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=false},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe))); 32 | if(a==this.fragment)return false;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),true})},navigate:function(a,b){if(!m.started)return false;if(!b||b===true)b={trigger:b};var c=(a||"").replace(s,"");if(this.fragment!=c)this._hasPushState?(c.indexOf(this.options.root)!=0&&(c=this.options.root+c),this.fragment=c,window.history[b.replace? 33 | "replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a)},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid= 34 | f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},E=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");f.extend(v.prototype,l,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c!=null&&i(a).html(c);return a},setElement:function(a, 35 | b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];b!==false&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(E),e=d[1],d=d[2],c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+ 36 | this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b').hide().appendTo("body")[0].contentWindow,this.navigate(a);if(this._hasPushState)i(window).bind("popstate",this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)i(window).bind("hashchange",this.checkUrl);else if(this._wantsHashChange)this._checkUrlInterval= 29 | setInterval(this.checkUrl,this.interval);this.fragment=a;l=true;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,true),window.location.replace(this.options.root+"#"+this.fragment),true;else if(this._wantsPushState&&this._hasPushState&&b&&a.hash)this.fragment=a.hash.replace(n,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()}, 30 | stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);l=false},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment))return false;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b= 31 | this.fragment=this.getFragment(a);return g.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),true})},navigate:function(a,b){if(!l)return false;if(!b||b===true)b={trigger:b};var c=(a||"").replace(n,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c)))this._hasPushState?(c.indexOf(this.options.root)!=0&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location, 32 | c,b.replace),this.iframe&&c!=this.getFragment(this.iframe.location.hash)&&(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a)},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});e.View=function(a){this.cid=g.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()}; 33 | var x=/^(\S+)\s*(.*)$/,p="model,collection,el,id,attributes,className,tagName".split(",");g.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el=i(a);this.el=this.$el[0];b!==false&&this.delegateEvents();return this},delegateEvents:function(a){if(a|| 34 | (a=j(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];g.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(x),e=d[1],d=d[2],c=g.bind(c,this);e+=".delegateEvents"+this.cid;d===""?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=g.extend({},this.options,a));for(var b=0,c=p.length;bthis.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=i.execCb(c,n,b,e)}catch(d){a=d}else e=i.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",v(this.error= 19 | a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,l.onResourceLoad))l.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=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h= 20 | 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=j(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(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 n=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=u(this, 21 | function(a){this.inited=!0;this.error=a;a.requireModules=[b];G(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),n.fromText=u(this,function(e,c){var d=a.name,g=j(d),C=O;c&&(e=c);C&&(O=!1);r(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{l.exec(e)}catch(ca){return v(B("fromtexteval","fromText eval for "+b+" failed: "+ca,ca,[b]))}C&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],n)}),e.load(a.name,h,n,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]= 22 | this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&t(a,"error",this.errback)}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));G(this.pluginMaps,u(this,function(a){var b=m(p,a.id);b&&!b.enabled&&i.enable(a, 23 | 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:q,urlFetched:U,defQueue:H,Module:Z,makeModuleMap:j,nextTick:l.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};G(a,function(a,b){e[b]? 24 | "map"===b?(k.map||(k.map={}),R(k[b],a,!0,!0)):R(k[b],a,!0):k[b]=a});a.shim&&(G(a.shim,function(a,b){J(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,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);G(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=j(b))});if(a.deps||a.callback)i.require(a.deps||[], 25 | a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(aa,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return v(B("requireargs","Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(l.get)return l.get(i,e,a,d);g=j(e,a,!1,!0);g=g.id;return!s(q,g)?v(B("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+ 26 | b+(a?"":". Use require([])"))):q[g]}L();i.nextTick(function(){L();k=r(j(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});D()});return d}f=f||{};R(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!Y?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",b.onScriptError,!1)),h.src=d,K=h,D?x.insertBefore(h,D):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};A&&M(document.getElementsByTagName("script"),function(b){x||(x= 34 | b.parentNode);if(t=b.getAttribute("data-main"))return r.baseUrl||(E=t.split("/"),Q=E.pop(),fa=E.length?E.join("/")+"/":"./",r.baseUrl=fa,t=Q),t=t.replace(ea,""),r.deps=r.deps?r.deps.concat(t):[t],!0});define=function(b,c,d){var l,h;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&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(!(l=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"), 35 | function(b){if("interactive"===b.readyState)return P=b}),l=P;l&&(b||(b=l.getAttribute("data-requiremodule")),h=F[l.getAttribute("data-requirecontext")])}(h?h.defQueue:T).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(r)}})(this); 36 | -------------------------------------------------------------------------------- /js/libs/require/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS text 2.0.3 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/requirejs/text for details 5 | */ 6 | /*jslint regexp: true */ 7 | /*global require: false, XMLHttpRequest: false, ActiveXObject: false, 8 | define: false, window: false, process: false, Packages: false, 9 | java: false, location: false */ 10 | 11 | define(['module'], function (module) { 12 | 'use strict'; 13 | 14 | var text, fs, 15 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], 16 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, 17 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, 18 | hasLocation = typeof location !== 'undefined' && location.href, 19 | defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), 20 | defaultHostName = hasLocation && location.hostname, 21 | defaultPort = hasLocation && (location.port || undefined), 22 | buildMap = [], 23 | masterConfig = (module.config && module.config()) || {}; 24 | 25 | text = { 26 | version: '2.0.3', 27 | 28 | strip: function (content) { 29 | //Strips declarations so that external SVG and XML 30 | //documents can be added to a document without worry. Also, if the string 31 | //is an HTML document, only the part inside the body tag is returned. 32 | if (content) { 33 | content = content.replace(xmlRegExp, ""); 34 | var matches = content.match(bodyRegExp); 35 | if (matches) { 36 | content = matches[1]; 37 | } 38 | } else { 39 | content = ""; 40 | } 41 | return content; 42 | }, 43 | 44 | jsEscape: function (content) { 45 | return content.replace(/(['\\])/g, '\\$1') 46 | .replace(/[\f]/g, "\\f") 47 | .replace(/[\b]/g, "\\b") 48 | .replace(/[\n]/g, "\\n") 49 | .replace(/[\t]/g, "\\t") 50 | .replace(/[\r]/g, "\\r") 51 | .replace(/[\u2028]/g, "\\u2028") 52 | .replace(/[\u2029]/g, "\\u2029"); 53 | }, 54 | 55 | createXhr: masterConfig.createXhr || function () { 56 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first. 57 | var xhr, i, progId; 58 | if (typeof XMLHttpRequest !== "undefined") { 59 | return new XMLHttpRequest(); 60 | } else if (typeof ActiveXObject !== "undefined") { 61 | for (i = 0; i < 3; i += 1) { 62 | progId = progIds[i]; 63 | try { 64 | xhr = new ActiveXObject(progId); 65 | } catch (e) {} 66 | 67 | if (xhr) { 68 | progIds = [progId]; // so faster next time 69 | break; 70 | } 71 | } 72 | } 73 | 74 | return xhr; 75 | }, 76 | 77 | /** 78 | * Parses a resource name into its component parts. Resource names 79 | * look like: module/name.ext!strip, where the !strip part is 80 | * optional. 81 | * @param {String} name the resource name 82 | * @returns {Object} with properties "moduleName", "ext" and "strip" 83 | * where strip is a boolean. 84 | */ 85 | parseName: function (name) { 86 | var strip = false, index = name.indexOf("."), 87 | modName = name.substring(0, index), 88 | ext = name.substring(index + 1, name.length); 89 | 90 | index = ext.indexOf("!"); 91 | if (index !== -1) { 92 | //Pull off the strip arg. 93 | strip = ext.substring(index + 1, ext.length); 94 | strip = strip === "strip"; 95 | ext = ext.substring(0, index); 96 | } 97 | 98 | return { 99 | moduleName: modName, 100 | ext: ext, 101 | strip: strip 102 | }; 103 | }, 104 | 105 | xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, 106 | 107 | /** 108 | * Is an URL on another domain. Only works for browser use, returns 109 | * false in non-browser environments. Only used to know if an 110 | * optimized .js version of a text resource should be loaded 111 | * instead. 112 | * @param {String} url 113 | * @returns Boolean 114 | */ 115 | useXhr: function (url, protocol, hostname, port) { 116 | var uProtocol, uHostName, uPort, 117 | match = text.xdRegExp.exec(url); 118 | if (!match) { 119 | return true; 120 | } 121 | uProtocol = match[2]; 122 | uHostName = match[3]; 123 | 124 | uHostName = uHostName.split(':'); 125 | uPort = uHostName[1]; 126 | uHostName = uHostName[0]; 127 | 128 | return (!uProtocol || uProtocol === protocol) && 129 | (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && 130 | ((!uPort && !uHostName) || uPort === port); 131 | }, 132 | 133 | finishLoad: function (name, strip, content, onLoad) { 134 | content = strip ? text.strip(content) : content; 135 | if (masterConfig.isBuild) { 136 | buildMap[name] = content; 137 | } 138 | onLoad(content); 139 | }, 140 | 141 | load: function (name, req, onLoad, config) { 142 | //Name has format: some.module.filext!strip 143 | //The strip part is optional. 144 | //if strip is present, then that means only get the string contents 145 | //inside a body tag in an HTML string. For XML/SVG content it means 146 | //removing the declarations so the content can be inserted 147 | //into the current doc without problems. 148 | 149 | // Do not bother with the work if a build and text will 150 | // not be inlined. 151 | if (config.isBuild && !config.inlineText) { 152 | onLoad(); 153 | return; 154 | } 155 | 156 | masterConfig.isBuild = config.isBuild; 157 | 158 | var parsed = text.parseName(name), 159 | nonStripName = parsed.moduleName + '.' + parsed.ext, 160 | url = req.toUrl(nonStripName), 161 | useXhr = (masterConfig.useXhr) || 162 | text.useXhr; 163 | 164 | //Load the text. Use XHR if possible and in a browser. 165 | if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { 166 | text.get(url, function (content) { 167 | text.finishLoad(name, parsed.strip, content, onLoad); 168 | }, function (err) { 169 | if (onLoad.error) { 170 | onLoad.error(err); 171 | } 172 | }); 173 | } else { 174 | //Need to fetch the resource across domains. Assume 175 | //the resource has been optimized into a JS module. Fetch 176 | //by the module name + extension, but do not include the 177 | //!strip part to avoid file system issues. 178 | req([nonStripName], function (content) { 179 | text.finishLoad(parsed.moduleName + '.' + parsed.ext, 180 | parsed.strip, content, onLoad); 181 | }); 182 | } 183 | }, 184 | 185 | write: function (pluginName, moduleName, write, config) { 186 | if (buildMap.hasOwnProperty(moduleName)) { 187 | var content = text.jsEscape(buildMap[moduleName]); 188 | write.asModule(pluginName + "!" + moduleName, 189 | "define(function () { return '" + 190 | content + 191 | "';});\n"); 192 | } 193 | }, 194 | 195 | writeFile: function (pluginName, moduleName, req, write, config) { 196 | var parsed = text.parseName(moduleName), 197 | nonStripName = parsed.moduleName + '.' + parsed.ext, 198 | //Use a '.js' file name so that it indicates it is a 199 | //script that can be loaded across domains. 200 | fileName = req.toUrl(parsed.moduleName + '.' + 201 | parsed.ext) + '.js'; 202 | 203 | //Leverage own load() method to load plugin value, but only 204 | //write out values that do not have the strip argument, 205 | //to avoid any potential issues with ! in file names. 206 | text.load(nonStripName, req, function (value) { 207 | //Use own write() method to construct full module value. 208 | //But need to create shell that translates writeFile's 209 | //write() to the right interface. 210 | var textWrite = function (contents) { 211 | return write(fileName, contents); 212 | }; 213 | textWrite.asModule = function (moduleName, contents) { 214 | return write.asModule(moduleName, fileName, contents); 215 | }; 216 | 217 | text.write(pluginName, nonStripName, textWrite, config); 218 | }, config); 219 | } 220 | }; 221 | 222 | if (masterConfig.env === 'node' || (!masterConfig.env && 223 | typeof process !== "undefined" && 224 | process.versions && 225 | !!process.versions.node)) { 226 | //Using special require.nodeRequire, something added by r.js. 227 | fs = require.nodeRequire('fs'); 228 | 229 | text.get = function (url, callback) { 230 | var file = fs.readFileSync(url, 'utf8'); 231 | //Remove BOM (Byte Mark Order) from utf8 files if it is there. 232 | if (file.indexOf('\uFEFF') === 0) { 233 | file = file.substring(1); 234 | } 235 | callback(file); 236 | }; 237 | } else if (masterConfig.env === 'xhr' || (!masterConfig.env && 238 | text.createXhr())) { 239 | text.get = function (url, callback, errback) { 240 | var xhr = text.createXhr(); 241 | xhr.open('GET', url, true); 242 | 243 | //Allow overrides specified in config 244 | if (masterConfig.onXhr) { 245 | masterConfig.onXhr(xhr, url); 246 | } 247 | 248 | xhr.onreadystatechange = function (evt) { 249 | var status, err; 250 | //Do not explicitly handle errors, those should be 251 | //visible via console output in the browser. 252 | if (xhr.readyState === 4) { 253 | status = xhr.status; 254 | if (status > 399 && status < 600) { 255 | //An http 4xx or 5xx error. Signal an error. 256 | err = new Error(url + ' HTTP status: ' + status); 257 | err.xhr = xhr; 258 | errback(err); 259 | } else { 260 | callback(xhr.responseText); 261 | } 262 | } 263 | }; 264 | xhr.send(null); 265 | }; 266 | } else if (masterConfig.env === 'rhino' || (!masterConfig.env && 267 | typeof Packages !== 'undefined' && typeof java !== 'undefined')) { 268 | //Why Java, why is this so awkward? 269 | text.get = function (url, callback) { 270 | var stringBuffer, line, 271 | encoding = "utf-8", 272 | file = new java.io.File(url), 273 | lineSeparator = java.lang.System.getProperty("line.separator"), 274 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), 275 | content = ''; 276 | try { 277 | stringBuffer = new java.lang.StringBuffer(); 278 | line = input.readLine(); 279 | 280 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 281 | // http://www.unicode.org/faq/utf_bom.html 282 | 283 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: 284 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 285 | if (line && line.length() && line.charAt(0) === 0xfeff) { 286 | // Eat the BOM, since we've already found the encoding on this file, 287 | // and we plan to concatenating this buffer with others; the BOM should 288 | // only appear at the top of a file. 289 | line = line.substring(1); 290 | } 291 | 292 | stringBuffer.append(line); 293 | 294 | while ((line = input.readLine()) !== null) { 295 | stringBuffer.append(lineSeparator); 296 | stringBuffer.append(line); 297 | } 298 | //Make sure we return a JavaScript string and not a Java string. 299 | content = String(stringBuffer.toString()); //String 300 | } finally { 301 | input.close(); 302 | } 303 | callback(content); 304 | }; 305 | } 306 | 307 | return text; 308 | }); 309 | -------------------------------------------------------------------------------- /js/libs/springy/springy.js: -------------------------------------------------------------------------------- 1 | define(function (){ 2 | /** 3 | Copyright (c) 2010 Dennis Hotson 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | var Graph = function() { 28 | this.nodeSet = {}; 29 | this.nodes = []; 30 | this.edges = []; 31 | this.adjacency = {}; 32 | 33 | this.nextNodeId = 0; 34 | this.nextEdgeId = 0; 35 | this.eventListeners = []; 36 | }; 37 | 38 | var Node = function(id, data) { 39 | this.id = id; 40 | this.data = typeof(data) !== 'undefined' ? data : {}; 41 | }; 42 | 43 | var Edge = function(id, source, target, data) { 44 | this.id = id; 45 | this.source = source; 46 | this.target = target; 47 | this.data = typeof(data) !== 'undefined' ? data : {}; 48 | }; 49 | 50 | Graph.prototype.addNode = function(node) { 51 | if (typeof(this.nodeSet[node.id]) === 'undefined') { 52 | this.nodes.push(node); 53 | } 54 | 55 | this.nodeSet[node.id] = node; 56 | 57 | this.notify(); 58 | return node; 59 | }; 60 | 61 | Graph.prototype.addEdge = function(edge) { 62 | var exists = false; 63 | this.edges.forEach(function(e) { 64 | if (edge.id === e.id) { exists = true; } 65 | }); 66 | 67 | if (!exists) { 68 | this.edges.push(edge); 69 | } 70 | 71 | if (typeof(this.adjacency[edge.source.id]) === 'undefined') { 72 | this.adjacency[edge.source.id] = {}; 73 | } 74 | if (typeof(this.adjacency[edge.source.id][edge.target.id]) === 'undefined') { 75 | this.adjacency[edge.source.id][edge.target.id] = []; 76 | } 77 | 78 | exists = false; 79 | this.adjacency[edge.source.id][edge.target.id].forEach(function(e) { 80 | if (edge.id === e.id) { exists = true; } 81 | }); 82 | 83 | if (!exists) { 84 | this.adjacency[edge.source.id][edge.target.id].push(edge); 85 | } 86 | 87 | this.notify(); 88 | return edge; 89 | }; 90 | 91 | Graph.prototype.newNode = function(data) { 92 | var node = new Node(this.nextNodeId++, data); 93 | this.addNode(node); 94 | return node; 95 | }; 96 | 97 | Graph.prototype.newEdge = function(source, target, data) { 98 | var edge = new Edge(this.nextEdgeId++, source, target, data); 99 | this.addEdge(edge); 100 | return edge; 101 | }; 102 | 103 | // find the edges from node1 to node2 104 | Graph.prototype.getEdges = function(node1, node2) { 105 | if (typeof(this.adjacency[node1.id]) !== 'undefined' 106 | && typeof(this.adjacency[node1.id][node2.id]) !== 'undefined') { 107 | return this.adjacency[node1.id][node2.id]; 108 | } 109 | 110 | return []; 111 | }; 112 | 113 | // remove a node and it's associated edges from the graph 114 | Graph.prototype.removeNode = function(node) { 115 | if (typeof(this.nodeSet[node.id]) !== 'undefined') { 116 | delete this.nodeSet[node.id]; 117 | } 118 | 119 | for (var i = this.nodes.length - 1; i >= 0; i--) { 120 | if (this.nodes[i].id === node.id) { 121 | this.nodes.splice(i, 1); 122 | } 123 | } 124 | 125 | this.detachNode(node); 126 | 127 | }; 128 | 129 | // removes edges associated with a given node 130 | Graph.prototype.detachNode = function(node) { 131 | var tmpEdges = this.edges.slice(); 132 | tmpEdges.forEach(function(e) { 133 | if (e.source.id === node.id || e.target.id === node.id) { 134 | this.removeEdge(e); 135 | } 136 | }, this); 137 | 138 | this.notify(); 139 | }; 140 | 141 | // remove a node and it's associated edges from the graph 142 | Graph.prototype.removeEdge = function(edge) { 143 | for (var i = this.edges.length - 1; i >= 0; i--) { 144 | if (this.edges[i].id === edge.id) { 145 | this.edges.splice(i, 1); 146 | } 147 | } 148 | 149 | for (var x in this.adjacency) { 150 | for (var y in this.adjacency[x]) { 151 | var edges = this.adjacency[x][y]; 152 | 153 | for (var j=edges.length - 1; j>=0; j--) { 154 | if (this.adjacency[x][y][j].id === edge.id) { 155 | this.adjacency[x][y].splice(j, 1); 156 | } 157 | } 158 | } 159 | } 160 | 161 | this.notify(); 162 | }; 163 | 164 | /* Merge a list of nodes and edges into the current graph. eg. 165 | var o = { 166 | nodes: [ 167 | {id: 123, data: {type: 'user', userid: 123, displayname: 'aaa'}}, 168 | {id: 234, data: {type: 'user', userid: 234, displayname: 'bbb'}} 169 | ], 170 | edges: [ 171 | {from: 0, to: 1, type: 'submitted_design', directed: true, data: {weight: }} 172 | ] 173 | } 174 | */ 175 | Graph.prototype.merge = function(data) { 176 | var nodes = []; 177 | data.nodes.forEach(function(n) { 178 | nodes.push(this.addNode(new Node(n.id, n.data))); 179 | }, this); 180 | 181 | data.edges.forEach(function(e) { 182 | var from = nodes[e.from]; 183 | var to = nodes[e.to]; 184 | 185 | var id = (e.directed) 186 | ? (id = e.type + "-" + from.id + "-" + to.id) 187 | : (from.id < to.id) // normalise id for non-directed edges 188 | ? e.type + "-" + from.id + "-" + to.id 189 | : e.type + "-" + to.id + "-" + from.id; 190 | 191 | var edge = this.addEdge(new Edge(id, from, to, e.data)); 192 | edge.data.type = e.type; 193 | }, this); 194 | }; 195 | 196 | Graph.prototype.filterNodes = function(fn) { 197 | var tmpNodes = this.nodes.slice(); 198 | tmpNodes.forEach(function(n) { 199 | if (!fn(n)) { 200 | this.removeNode(n); 201 | } 202 | }, this); 203 | }; 204 | 205 | Graph.prototype.filterEdges = function(fn) { 206 | var tmpEdges = this.edges.slice(); 207 | tmpEdges.forEach(function(e) { 208 | if (!fn(e)) { 209 | this.removeEdge(e); 210 | } 211 | }, this); 212 | }; 213 | 214 | 215 | Graph.prototype.addGraphListener = function(obj) { 216 | this.eventListeners.push(obj); 217 | }; 218 | 219 | Graph.prototype.notify = function() { 220 | this.eventListeners.forEach(function(obj){ 221 | obj.graphChanged(); 222 | }); 223 | }; 224 | 225 | // ----------- 226 | var Layout = {}; 227 | Layout.ForceDirected = function(graph, stiffness, repulsion, damping) { 228 | this.graph = graph; 229 | this.stiffness = stiffness; // spring stiffness constant 230 | this.repulsion = repulsion; // repulsion constant 231 | this.damping = damping; // velocity damping factor 232 | 233 | this.nodePoints = {}; // keep track of points associated with nodes 234 | this.edgeSprings = {}; // keep track of springs associated with edges 235 | }; 236 | 237 | Layout.ForceDirected.prototype.point = function(node) { 238 | if (typeof(this.nodePoints[node.id]) === 'undefined') { 239 | var mass = typeof(node.data.mass) !== 'undefined' ? node.data.mass : 1.0; 240 | this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass); 241 | } 242 | 243 | return this.nodePoints[node.id]; 244 | }; 245 | 246 | Layout.ForceDirected.prototype.spring = function(edge) { 247 | if (typeof(this.edgeSprings[edge.id]) === 'undefined') { 248 | var length = typeof(edge.data.length) !== 'undefined' ? edge.data.length : 1.0; 249 | 250 | var existingSpring = false; 251 | 252 | var from = this.graph.getEdges(edge.source, edge.target); 253 | from.forEach(function(e) { 254 | if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { 255 | existingSpring = this.edgeSprings[e.id]; 256 | } 257 | }, this); 258 | 259 | if (existingSpring !== false) { 260 | return new Layout.ForceDirected.Spring(existingSpring.point1, existingSpring.point2, 0.0, 0.0); 261 | } 262 | 263 | var to = this.graph.getEdges(edge.target, edge.source); 264 | from.forEach(function(e){ 265 | if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { 266 | existingSpring = this.edgeSprings[e.id]; 267 | } 268 | }, this); 269 | 270 | if (existingSpring !== false) { 271 | return new Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0, 0.0); 272 | } 273 | 274 | this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring( 275 | this.point(edge.source), this.point(edge.target), length, this.stiffness 276 | ); 277 | } 278 | 279 | return this.edgeSprings[edge.id]; 280 | }; 281 | 282 | // callback should accept two arguments: Node, Point 283 | Layout.ForceDirected.prototype.eachNode = function(callback) { 284 | var t = this; 285 | this.graph.nodes.forEach(function(n){ 286 | callback.call(t, n, t.point(n)); 287 | }); 288 | }; 289 | 290 | // callback should accept two arguments: Edge, Spring 291 | Layout.ForceDirected.prototype.eachEdge = function(callback) { 292 | var t = this; 293 | this.graph.edges.forEach(function(e){ 294 | callback.call(t, e, t.spring(e)); 295 | }); 296 | }; 297 | 298 | // callback should accept one argument: Spring 299 | Layout.ForceDirected.prototype.eachSpring = function(callback) { 300 | var t = this; 301 | this.graph.edges.forEach(function(e){ 302 | callback.call(t, t.spring(e)); 303 | }); 304 | }; 305 | 306 | 307 | // Physics stuff 308 | Layout.ForceDirected.prototype.applyCoulombsLaw = function() { 309 | this.eachNode(function(n1, point1) { 310 | this.eachNode(function(n2, point2) { 311 | if (point1 !== point2) 312 | { 313 | var d = point1.p.subtract(point2.p); 314 | var distance = d.magnitude() + 0.1; // avoid massive forces at small distances (and divide by zero) 315 | var direction = d.normalise(); 316 | 317 | // apply force to each end point 318 | point1.applyForce(direction.multiply(this.repulsion).divide(distance * distance * 0.5)); 319 | point2.applyForce(direction.multiply(this.repulsion).divide(distance * distance * -0.5)); 320 | } 321 | }); 322 | }); 323 | }; 324 | 325 | Layout.ForceDirected.prototype.applyHookesLaw = function() { 326 | this.eachSpring(function(spring){ 327 | var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring 328 | var displacement = spring.length - d.magnitude(); 329 | var direction = d.normalise(); 330 | 331 | // apply force to each end point 332 | spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5)); 333 | spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5)); 334 | }); 335 | }; 336 | 337 | Layout.ForceDirected.prototype.attractToCentre = function() { 338 | this.eachNode(function(node, point) { 339 | var direction = point.p.multiply(-1.0); 340 | point.applyForce(direction.multiply(this.repulsion / 50.0)); 341 | }); 342 | }; 343 | 344 | 345 | Layout.ForceDirected.prototype.updateVelocity = function(timestep) { 346 | this.eachNode(function(node, point) { 347 | // Is this, along with updatePosition below, the only places that your 348 | // integration code exist? 349 | point.v = point.v.add(point.a.multiply(timestep)).multiply(this.damping); 350 | point.a = new Vector(0,0); 351 | }); 352 | }; 353 | 354 | Layout.ForceDirected.prototype.updatePosition = function(timestep) { 355 | this.eachNode(function(node, point) { 356 | // Same question as above; along with updateVelocity, is this all of 357 | // your integration code? 358 | point.p = point.p.add(point.v.multiply(timestep)); 359 | }); 360 | }; 361 | 362 | // Calculate the total kinetic energy of the system 363 | Layout.ForceDirected.prototype.totalEnergy = function(timestep) { 364 | var energy = 0.0; 365 | this.eachNode(function(node, point) { 366 | var speed = point.v.magnitude(); 367 | energy += 0.5 * point.m * speed * speed; 368 | }); 369 | 370 | return energy; 371 | }; 372 | 373 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // stolen from coffeescript, thanks jashkenas! ;-) 374 | 375 | Layout.requestAnimationFrame = __bind(window.requestAnimationFrame || 376 | window.webkitRequestAnimationFrame || 377 | window.mozRequestAnimationFrame || 378 | window.oRequestAnimationFrame || 379 | window.msRequestAnimationFrame || 380 | function(callback, element) { 381 | window.setTimeout(callback, 10); 382 | }, window); 383 | 384 | 385 | // start simulation 386 | Layout.ForceDirected.prototype.start = function(interval, render, done) { 387 | var t = this; 388 | 389 | if (this._started) return; 390 | this._started = true; 391 | 392 | Layout.requestAnimationFrame(function step() { 393 | t.applyCoulombsLaw(); 394 | t.applyHookesLaw(); 395 | t.attractToCentre(); 396 | t.updateVelocity(0.03); 397 | t.updatePosition(0.03); 398 | 399 | if (typeof(render) !== 'undefined') 400 | render(); 401 | 402 | // stop simulation when energy of the system goes below a threshold 403 | if (t.totalEnergy() < 0.01) { 404 | t._started = false; 405 | if (typeof(done) !== 'undefined') { done(); } 406 | } else { 407 | Layout.requestAnimationFrame(step); 408 | } 409 | }); 410 | }; 411 | 412 | // Find the nearest point to a particular position 413 | Layout.ForceDirected.prototype.nearest = function(pos) { 414 | var min = {node: null, point: null, distance: null}; 415 | var t = this; 416 | this.graph.nodes.forEach(function(n){ 417 | var point = t.point(n); 418 | var distance = point.p.subtract(pos).magnitude(); 419 | 420 | if (min.distance === null || distance < min.distance) { 421 | min = {node: n, point: point, distance: distance}; 422 | } 423 | }); 424 | 425 | return min; 426 | }; 427 | 428 | // returns [bottomleft, topright] 429 | Layout.ForceDirected.prototype.getBoundingBox = function() { 430 | var bottomleft = new Vector(-2,-2); 431 | var topright = new Vector(2,2); 432 | 433 | this.eachNode(function(n, point) { 434 | if (point.p.x < bottomleft.x) { 435 | bottomleft.x = point.p.x; 436 | } 437 | if (point.p.y < bottomleft.y) { 438 | bottomleft.y = point.p.y; 439 | } 440 | if (point.p.x > topright.x) { 441 | topright.x = point.p.x; 442 | } 443 | if (point.p.y > topright.y) { 444 | topright.y = point.p.y; 445 | } 446 | }); 447 | 448 | var padding = topright.subtract(bottomleft).multiply(0.07); // ~5% padding 449 | 450 | return {bottomleft: bottomleft.subtract(padding), topright: topright.add(padding)}; 451 | }; 452 | 453 | 454 | // Vector 455 | Vector = function(x, y) { 456 | this.x = x; 457 | this.y = y; 458 | }; 459 | 460 | Vector.random = function() { 461 | return new Vector(10.0 * (Math.random() - 0.5), 10.0 * (Math.random() - 0.5)); 462 | }; 463 | 464 | Vector.prototype.add = function(v2) { 465 | return new Vector(this.x + v2.x, this.y + v2.y); 466 | }; 467 | 468 | Vector.prototype.subtract = function(v2) { 469 | return new Vector(this.x - v2.x, this.y - v2.y); 470 | }; 471 | 472 | Vector.prototype.multiply = function(n) { 473 | return new Vector(this.x * n, this.y * n); 474 | }; 475 | 476 | Vector.prototype.divide = function(n) { 477 | return new Vector((this.x / n) || 0, (this.y / n) || 0); // Avoid divide by zero errors.. 478 | }; 479 | 480 | Vector.prototype.magnitude = function() { 481 | return Math.sqrt(this.x*this.x + this.y*this.y); 482 | }; 483 | 484 | Vector.prototype.normal = function() { 485 | return new Vector(-this.y, this.x); 486 | }; 487 | 488 | Vector.prototype.normalise = function() { 489 | return this.divide(this.magnitude()); 490 | }; 491 | 492 | // Point 493 | Layout.ForceDirected.Point = function(position, mass) { 494 | this.p = position; // position 495 | this.m = mass; // mass 496 | this.v = new Vector(0, 0); // velocity 497 | this.a = new Vector(0, 0); // acceleration 498 | }; 499 | 500 | Layout.ForceDirected.Point.prototype.applyForce = function(force) { 501 | this.a = this.a.add(force.divide(this.m)); 502 | }; 503 | 504 | // Spring 505 | Layout.ForceDirected.Spring = function(point1, point2, length, k) { 506 | this.point1 = point1; 507 | this.point2 = point2; 508 | this.length = length; // spring length at rest 509 | this.k = k; // spring constant (See Hooke's law) .. how stiff the spring is 510 | }; 511 | 512 | // Layout.ForceDirected.Spring.prototype.distanceToPoint = function(point) 513 | // { 514 | // // hardcore vector arithmetic.. ohh yeah! 515 | // // .. see http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/865080#865080 516 | // var n = this.point2.p.subtract(this.point1.p).normalise().normal(); 517 | // var ac = point.p.subtract(this.point1.p); 518 | // return Math.abs(ac.x * n.x + ac.y * n.y); 519 | // }; 520 | 521 | // Renderer handles the layout rendering loop 522 | function Renderer(interval, layout, clear, drawEdge, drawNode) { 523 | this.interval = interval; 524 | this.layout = layout; 525 | this.clear = clear; 526 | this.drawEdge = drawEdge; 527 | this.drawNode = drawNode; 528 | 529 | this.layout.graph.addGraphListener(this); 530 | } 531 | 532 | Renderer.prototype.graphChanged = function(e) { 533 | this.start(); 534 | }; 535 | 536 | Renderer.prototype.start = function() { 537 | var t = this; 538 | this.layout.start(50, function render() { 539 | t.clear(); 540 | 541 | t.layout.eachEdge(function(edge, spring) { 542 | t.drawEdge(edge, spring.point1.p, spring.point2.p); 543 | }); 544 | 545 | t.layout.eachNode(function(node, point) { 546 | t.drawNode(node, point.p); 547 | }); 548 | }); 549 | }; 550 | 551 | }); -------------------------------------------------------------------------------- /js/libs/springy/springyui.js: -------------------------------------------------------------------------------- 1 | define(['jquery'], function (jQuery) { 2 | /** 3 | Copyright (c) 2010 Dennis Hotson 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | var Graph = function() { 28 | this.nodeSet = {}; 29 | this.nodes = []; 30 | this.edges = []; 31 | this.adjacency = {}; 32 | 33 | this.nextNodeId = 0; 34 | this.nextEdgeId = 0; 35 | this.eventListeners = []; 36 | }; 37 | 38 | var Node = function(id, data) { 39 | this.id = id; 40 | this.data = typeof(data) !== 'undefined' ? data : {}; 41 | }; 42 | 43 | var Edge = function(id, source, target, data) { 44 | this.id = id; 45 | this.source = source; 46 | this.target = target; 47 | this.data = typeof(data) !== 'undefined' ? data : {}; 48 | }; 49 | 50 | Graph.prototype.addNode = function(node) { 51 | if (typeof(this.nodeSet[node.id]) === 'undefined') { 52 | this.nodes.push(node); 53 | } 54 | 55 | this.nodeSet[node.id] = node; 56 | 57 | this.notify(); 58 | return node; 59 | }; 60 | 61 | Graph.prototype.addEdge = function(edge) { 62 | var exists = false; 63 | this.edges.forEach(function(e) { 64 | if (edge.id === e.id) { exists = true; } 65 | }); 66 | 67 | if (!exists) { 68 | this.edges.push(edge); 69 | } 70 | 71 | if (typeof(this.adjacency[edge.source.id]) === 'undefined') { 72 | this.adjacency[edge.source.id] = {}; 73 | } 74 | if (typeof(this.adjacency[edge.source.id][edge.target.id]) === 'undefined') { 75 | this.adjacency[edge.source.id][edge.target.id] = []; 76 | } 77 | 78 | exists = false; 79 | this.adjacency[edge.source.id][edge.target.id].forEach(function(e) { 80 | if (edge.id === e.id) { exists = true; } 81 | }); 82 | 83 | if (!exists) { 84 | this.adjacency[edge.source.id][edge.target.id].push(edge); 85 | } 86 | 87 | this.notify(); 88 | return edge; 89 | }; 90 | 91 | Graph.prototype.newNode = function(data) { 92 | var node = new Node(this.nextNodeId++, data); 93 | this.addNode(node); 94 | return node; 95 | }; 96 | 97 | Graph.prototype.newEdge = function(source, target, data) { 98 | var edge = new Edge(this.nextEdgeId++, source, target, data); 99 | this.addEdge(edge); 100 | return edge; 101 | }; 102 | 103 | // find the edges from node1 to node2 104 | Graph.prototype.getEdges = function(node1, node2) { 105 | if (typeof(this.adjacency[node1.id]) !== 'undefined' 106 | && typeof(this.adjacency[node1.id][node2.id]) !== 'undefined') { 107 | return this.adjacency[node1.id][node2.id]; 108 | } 109 | 110 | return []; 111 | }; 112 | 113 | // remove a node and it's associated edges from the graph 114 | Graph.prototype.removeNode = function(node) { 115 | if (typeof(this.nodeSet[node.id]) !== 'undefined') { 116 | delete this.nodeSet[node.id]; 117 | } 118 | 119 | for (var i = this.nodes.length - 1; i >= 0; i--) { 120 | if (this.nodes[i].id === node.id) { 121 | this.nodes.splice(i, 1); 122 | } 123 | } 124 | 125 | this.detachNode(node); 126 | 127 | }; 128 | 129 | // removes edges associated with a given node 130 | Graph.prototype.detachNode = function(node) { 131 | var tmpEdges = this.edges.slice(); 132 | tmpEdges.forEach(function(e) { 133 | if (e.source.id === node.id || e.target.id === node.id) { 134 | this.removeEdge(e); 135 | } 136 | }, this); 137 | 138 | this.notify(); 139 | }; 140 | 141 | // remove a node and it's associated edges from the graph 142 | Graph.prototype.removeEdge = function(edge) { 143 | for (var i = this.edges.length - 1; i >= 0; i--) { 144 | if (this.edges[i].id === edge.id) { 145 | this.edges.splice(i, 1); 146 | } 147 | } 148 | 149 | for (var x in this.adjacency) { 150 | for (var y in this.adjacency[x]) { 151 | var edges = this.adjacency[x][y]; 152 | 153 | for (var j=edges.length - 1; j>=0; j--) { 154 | if (this.adjacency[x][y][j].id === edge.id) { 155 | this.adjacency[x][y].splice(j, 1); 156 | } 157 | } 158 | } 159 | } 160 | 161 | this.notify(); 162 | }; 163 | 164 | /* Merge a list of nodes and edges into the current graph. eg. 165 | var o = { 166 | nodes: [ 167 | {id: 123, data: {type: 'user', userid: 123, displayname: 'aaa'}}, 168 | {id: 234, data: {type: 'user', userid: 234, displayname: 'bbb'}} 169 | ], 170 | edges: [ 171 | {from: 0, to: 1, type: 'submitted_design', directed: true, data: {weight: }} 172 | ] 173 | } 174 | */ 175 | Graph.prototype.merge = function(data) { 176 | var nodes = []; 177 | data.nodes.forEach(function(n) { 178 | nodes.push(this.addNode(new Node(n.id, n.data))); 179 | }, this); 180 | 181 | data.edges.forEach(function(e) { 182 | var from = nodes[e.from]; 183 | var to = nodes[e.to]; 184 | 185 | var id = (e.directed) 186 | ? (id = e.type + "-" + from.id + "-" + to.id) 187 | : (from.id < to.id) // normalise id for non-directed edges 188 | ? e.type + "-" + from.id + "-" + to.id 189 | : e.type + "-" + to.id + "-" + from.id; 190 | 191 | var edge = this.addEdge(new Edge(id, from, to, e.data)); 192 | edge.data.type = e.type; 193 | }, this); 194 | }; 195 | 196 | Graph.prototype.filterNodes = function(fn) { 197 | var tmpNodes = this.nodes.slice(); 198 | tmpNodes.forEach(function(n) { 199 | if (!fn(n)) { 200 | this.removeNode(n); 201 | } 202 | }, this); 203 | }; 204 | 205 | Graph.prototype.filterEdges = function(fn) { 206 | var tmpEdges = this.edges.slice(); 207 | tmpEdges.forEach(function(e) { 208 | if (!fn(e)) { 209 | this.removeEdge(e); 210 | } 211 | }, this); 212 | }; 213 | 214 | 215 | Graph.prototype.addGraphListener = function(obj) { 216 | this.eventListeners.push(obj); 217 | }; 218 | 219 | Graph.prototype.notify = function() { 220 | this.eventListeners.forEach(function(obj){ 221 | obj.graphChanged(); 222 | }); 223 | }; 224 | 225 | // ----------- 226 | var Layout = {}; 227 | Layout.ForceDirected = function(graph, stiffness, repulsion, damping) { 228 | this.graph = graph; 229 | this.stiffness = stiffness; // spring stiffness constant 230 | this.repulsion = repulsion; // repulsion constant 231 | this.damping = damping; // velocity damping factor 232 | 233 | this.nodePoints = {}; // keep track of points associated with nodes 234 | this.edgeSprings = {}; // keep track of springs associated with edges 235 | }; 236 | 237 | Layout.ForceDirected.prototype.point = function(node) { 238 | if (typeof(this.nodePoints[node.id]) === 'undefined') { 239 | var mass = typeof(node.data.mass) !== 'undefined' ? node.data.mass : 1.0; 240 | this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass); 241 | } 242 | 243 | return this.nodePoints[node.id]; 244 | }; 245 | 246 | Layout.ForceDirected.prototype.spring = function(edge) { 247 | if (typeof(this.edgeSprings[edge.id]) === 'undefined') { 248 | var length = typeof(edge.data.length) !== 'undefined' ? edge.data.length : 1.0; 249 | 250 | var existingSpring = false; 251 | 252 | var from = this.graph.getEdges(edge.source, edge.target); 253 | from.forEach(function(e) { 254 | if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { 255 | existingSpring = this.edgeSprings[e.id]; 256 | } 257 | }, this); 258 | 259 | if (existingSpring !== false) { 260 | return new Layout.ForceDirected.Spring(existingSpring.point1, existingSpring.point2, 0.0, 0.0); 261 | } 262 | 263 | var to = this.graph.getEdges(edge.target, edge.source); 264 | from.forEach(function(e){ 265 | if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { 266 | existingSpring = this.edgeSprings[e.id]; 267 | } 268 | }, this); 269 | 270 | if (existingSpring !== false) { 271 | return new Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0, 0.0); 272 | } 273 | 274 | this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring( 275 | this.point(edge.source), this.point(edge.target), length, this.stiffness 276 | ); 277 | } 278 | 279 | return this.edgeSprings[edge.id]; 280 | }; 281 | 282 | // callback should accept two arguments: Node, Point 283 | Layout.ForceDirected.prototype.eachNode = function(callback) { 284 | var t = this; 285 | this.graph.nodes.forEach(function(n){ 286 | callback.call(t, n, t.point(n)); 287 | }); 288 | }; 289 | 290 | // callback should accept two arguments: Edge, Spring 291 | Layout.ForceDirected.prototype.eachEdge = function(callback) { 292 | var t = this; 293 | this.graph.edges.forEach(function(e){ 294 | callback.call(t, e, t.spring(e)); 295 | }); 296 | }; 297 | 298 | // callback should accept one argument: Spring 299 | Layout.ForceDirected.prototype.eachSpring = function(callback) { 300 | var t = this; 301 | this.graph.edges.forEach(function(e){ 302 | callback.call(t, t.spring(e)); 303 | }); 304 | }; 305 | 306 | 307 | // Physics stuff 308 | Layout.ForceDirected.prototype.applyCoulombsLaw = function() { 309 | this.eachNode(function(n1, point1) { 310 | this.eachNode(function(n2, point2) { 311 | if (point1 !== point2) 312 | { 313 | var d = point1.p.subtract(point2.p); 314 | var distance = d.magnitude() + 0.1; // avoid massive forces at small distances (and divide by zero) 315 | var direction = d.normalise(); 316 | 317 | // apply force to each end point 318 | point1.applyForce(direction.multiply(this.repulsion).divide(distance * distance * 0.5)); 319 | point2.applyForce(direction.multiply(this.repulsion).divide(distance * distance * -0.5)); 320 | } 321 | }); 322 | }); 323 | }; 324 | 325 | Layout.ForceDirected.prototype.applyHookesLaw = function() { 326 | this.eachSpring(function(spring){ 327 | var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring 328 | var displacement = spring.length - d.magnitude(); 329 | var direction = d.normalise(); 330 | 331 | // apply force to each end point 332 | spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5)); 333 | spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5)); 334 | }); 335 | }; 336 | 337 | Layout.ForceDirected.prototype.attractToCentre = function() { 338 | this.eachNode(function(node, point) { 339 | var direction = point.p.multiply(-1.0); 340 | point.applyForce(direction.multiply(this.repulsion / 50.0)); 341 | }); 342 | }; 343 | 344 | 345 | Layout.ForceDirected.prototype.updateVelocity = function(timestep) { 346 | this.eachNode(function(node, point) { 347 | // Is this, along with updatePosition below, the only places that your 348 | // integration code exist? 349 | point.v = point.v.add(point.a.multiply(timestep)).multiply(this.damping); 350 | point.a = new Vector(0,0); 351 | }); 352 | }; 353 | 354 | Layout.ForceDirected.prototype.updatePosition = function(timestep) { 355 | this.eachNode(function(node, point) { 356 | // Same question as above; along with updateVelocity, is this all of 357 | // your integration code? 358 | point.p = point.p.add(point.v.multiply(timestep)); 359 | }); 360 | }; 361 | 362 | // Calculate the total kinetic energy of the system 363 | Layout.ForceDirected.prototype.totalEnergy = function(timestep) { 364 | var energy = 0.0; 365 | this.eachNode(function(node, point) { 366 | var speed = point.v.magnitude(); 367 | energy += 0.5 * point.m * speed * speed; 368 | }); 369 | 370 | return energy; 371 | }; 372 | 373 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; // stolen from coffeescript, thanks jashkenas! ;-) 374 | 375 | Layout.requestAnimationFrame = __bind(window.requestAnimationFrame || 376 | window.webkitRequestAnimationFrame || 377 | window.mozRequestAnimationFrame || 378 | window.oRequestAnimationFrame || 379 | window.msRequestAnimationFrame || 380 | function(callback, element) { 381 | window.setTimeout(callback, 10); 382 | }, window); 383 | 384 | 385 | // start simulation 386 | Layout.ForceDirected.prototype.start = function(interval, render, done) { 387 | var t = this; 388 | 389 | if (this._started) return; 390 | this._started = true; 391 | 392 | Layout.requestAnimationFrame(function step() { 393 | t.applyCoulombsLaw(); 394 | t.applyHookesLaw(); 395 | t.attractToCentre(); 396 | t.updateVelocity(0.03); 397 | t.updatePosition(0.03); 398 | 399 | if (typeof(render) !== 'undefined') 400 | render(); 401 | 402 | // stop simulation when energy of the system goes below a threshold 403 | if (t.totalEnergy() < 0.01) { 404 | t._started = false; 405 | if (typeof(done) !== 'undefined') { done(); } 406 | } else { 407 | Layout.requestAnimationFrame(step); 408 | } 409 | }); 410 | }; 411 | 412 | // Find the nearest point to a particular position 413 | Layout.ForceDirected.prototype.nearest = function(pos) { 414 | var min = {node: null, point: null, distance: null}; 415 | var t = this; 416 | this.graph.nodes.forEach(function(n){ 417 | var point = t.point(n); 418 | var distance = point.p.subtract(pos).magnitude(); 419 | 420 | if (min.distance === null || distance < min.distance) { 421 | min = {node: n, point: point, distance: distance}; 422 | } 423 | }); 424 | 425 | return min; 426 | }; 427 | 428 | // returns [bottomleft, topright] 429 | Layout.ForceDirected.prototype.getBoundingBox = function() { 430 | var bottomleft = new Vector(-2,-2); 431 | var topright = new Vector(2,2); 432 | 433 | this.eachNode(function(n, point) { 434 | if (point.p.x < bottomleft.x) { 435 | bottomleft.x = point.p.x; 436 | } 437 | if (point.p.y < bottomleft.y) { 438 | bottomleft.y = point.p.y; 439 | } 440 | if (point.p.x > topright.x) { 441 | topright.x = point.p.x; 442 | } 443 | if (point.p.y > topright.y) { 444 | topright.y = point.p.y; 445 | } 446 | }); 447 | 448 | var padding = topright.subtract(bottomleft).multiply(0.07); // ~5% padding 449 | 450 | return {bottomleft: bottomleft.subtract(padding), topright: topright.add(padding)}; 451 | }; 452 | 453 | 454 | // Vector 455 | Vector = function(x, y) { 456 | this.x = x; 457 | this.y = y; 458 | }; 459 | 460 | Vector.random = function() { 461 | return new Vector(10.0 * (Math.random() - 0.5), 10.0 * (Math.random() - 0.5)); 462 | }; 463 | 464 | Vector.prototype.add = function(v2) { 465 | return new Vector(this.x + v2.x, this.y + v2.y); 466 | }; 467 | 468 | Vector.prototype.subtract = function(v2) { 469 | return new Vector(this.x - v2.x, this.y - v2.y); 470 | }; 471 | 472 | Vector.prototype.multiply = function(n) { 473 | return new Vector(this.x * n, this.y * n); 474 | }; 475 | 476 | Vector.prototype.divide = function(n) { 477 | return new Vector((this.x / n) || 0, (this.y / n) || 0); // Avoid divide by zero errors.. 478 | }; 479 | 480 | Vector.prototype.magnitude = function() { 481 | return Math.sqrt(this.x*this.x + this.y*this.y); 482 | }; 483 | 484 | Vector.prototype.normal = function() { 485 | return new Vector(-this.y, this.x); 486 | }; 487 | 488 | Vector.prototype.normalise = function() { 489 | return this.divide(this.magnitude()); 490 | }; 491 | 492 | // Point 493 | Layout.ForceDirected.Point = function(position, mass) { 494 | this.p = position; // position 495 | this.m = mass; // mass 496 | this.v = new Vector(0, 0); // velocity 497 | this.a = new Vector(0, 0); // acceleration 498 | }; 499 | 500 | Layout.ForceDirected.Point.prototype.applyForce = function(force) { 501 | this.a = this.a.add(force.divide(this.m)); 502 | }; 503 | 504 | // Spring 505 | Layout.ForceDirected.Spring = function(point1, point2, length, k) { 506 | this.point1 = point1; 507 | this.point2 = point2; 508 | this.length = length; // spring length at rest 509 | this.k = k; // spring constant (See Hooke's law) .. how stiff the spring is 510 | }; 511 | 512 | // Layout.ForceDirected.Spring.prototype.distanceToPoint = function(point) 513 | // { 514 | // // hardcore vector arithmetic.. ohh yeah! 515 | // // .. see http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment/865080#865080 516 | // var n = this.point2.p.subtract(this.point1.p).normalise().normal(); 517 | // var ac = point.p.subtract(this.point1.p); 518 | // return Math.abs(ac.x * n.x + ac.y * n.y); 519 | // }; 520 | 521 | // Renderer handles the layout rendering loop 522 | function Renderer(interval, layout, clear, drawEdge, drawNode) { 523 | this.interval = interval; 524 | this.layout = layout; 525 | this.clear = clear; 526 | this.drawEdge = drawEdge; 527 | this.drawNode = drawNode; 528 | 529 | this.layout.graph.addGraphListener(this); 530 | } 531 | 532 | Renderer.prototype.graphChanged = function(e) { 533 | this.start(); 534 | }; 535 | 536 | Renderer.prototype.start = function() { 537 | var t = this; 538 | this.layout.start(50, function render() { 539 | t.clear(); 540 | 541 | t.layout.eachEdge(function(edge, spring) { 542 | t.drawEdge(edge, spring.point1.p, spring.point2.p); 543 | }); 544 | 545 | t.layout.eachNode(function(node, point) { 546 | t.drawNode(node, point.p); 547 | }); 548 | }); 549 | }; 550 | 551 | 552 | jQuery.fn.springy = function(params) { 553 | var graph = this.graph = params.graph || new Graph(); 554 | 555 | var stiffness = params.stiffness || 400.0; 556 | var repulsion = params.repulsion || 400.0; 557 | var damping = params.damping || 0.5; 558 | 559 | var canvas = this[0]; 560 | var ctx = canvas.getContext("2d"); 561 | 562 | var layout = this.layout = new Layout.ForceDirected(graph, stiffness, repulsion, damping); 563 | 564 | // calculate bounding box of graph layout.. with ease-in 565 | var currentBB = layout.getBoundingBox(); 566 | var targetBB = {bottomleft: new Vector(-2, -2), topright: new Vector(2, 2)}; 567 | 568 | // auto adjusting bounding box 569 | Layout.requestAnimationFrame(function adjust() { 570 | targetBB = layout.getBoundingBox(); 571 | // current gets 20% closer to target every iteration 572 | currentBB = { 573 | bottomleft: currentBB.bottomleft.add( targetBB.bottomleft.subtract(currentBB.bottomleft) 574 | .divide(10)), 575 | topright: currentBB.topright.add( targetBB.topright.subtract(currentBB.topright) 576 | .divide(10)) 577 | }; 578 | 579 | Layout.requestAnimationFrame(adjust); 580 | }); 581 | 582 | // convert to/from screen coordinates 583 | toScreen = function(p) { 584 | var size = currentBB.topright.subtract(currentBB.bottomleft); 585 | var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * canvas.width; 586 | var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * canvas.height; 587 | return new Vector(sx, sy); 588 | }; 589 | 590 | fromScreen = function(s) { 591 | var size = currentBB.topright.subtract(currentBB.bottomleft); 592 | var px = (s.x / canvas.width) * size.x + currentBB.bottomleft.x; 593 | var py = (s.y / canvas.height) * size.y + currentBB.bottomleft.y; 594 | return new Vector(px, py); 595 | }; 596 | 597 | // half-assed drag and drop 598 | var selected = null; 599 | var nearest = null; 600 | var dragged = null; 601 | 602 | jQuery(canvas).mousedown(function(e) { 603 | jQuery('.actions').hide(); 604 | 605 | var pos = jQuery(this).offset(); 606 | var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top}); 607 | selected = nearest = dragged = layout.nearest(p); 608 | 609 | if (selected.node !== null) { 610 | // Part of the same bug mentioned later. Store the previous mass 611 | // before upscaling it for dragging. 612 | dragged.point.m = 10000.0; 613 | } 614 | 615 | renderer.start(); 616 | }); 617 | 618 | jQuery(canvas).mousemove(function(e) { 619 | var pos = jQuery(this).offset(); 620 | var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top}); 621 | nearest = layout.nearest(p); 622 | 623 | if (dragged !== null && dragged.node !== null) { 624 | dragged.point.p.x = p.x; 625 | dragged.point.p.y = p.y; 626 | } 627 | 628 | renderer.start(); 629 | }); 630 | 631 | jQuery(window).bind('mouseup',function(e) { 632 | // Bug! Node's mass isn't reset on mouseup. Nodes which have been 633 | // dragged don't repulse very well. Store the initial mass in mousedown 634 | // and then restore it here. 635 | dragged = null; 636 | }); 637 | 638 | Node.prototype.getWidth = function() { 639 | var text = typeof(this.data.label) !== 'undefined' ? this.data.label : this.id; 640 | if (this._width && this._width[text]) 641 | return this._width[text]; 642 | 643 | ctx.save(); 644 | ctx.font = "16px Verdana, sans-serif"; 645 | var width = ctx.measureText(text).width + 10; 646 | ctx.restore(); 647 | 648 | this._width || (this._width = {}); 649 | this._width[text] = width; 650 | 651 | return width; 652 | }; 653 | 654 | Node.prototype.getHeight = function() { 655 | // Magic number with no explanation. 656 | return 20; 657 | }; 658 | 659 | var renderer = new Renderer(1, layout, 660 | function clear() { 661 | ctx.clearRect(0,0,canvas.width,canvas.height); 662 | }, 663 | function drawEdge(edge, p1, p2) { 664 | var x1 = toScreen(p1).x; 665 | var y1 = toScreen(p1).y; 666 | var x2 = toScreen(p2).x; 667 | var y2 = toScreen(p2).y; 668 | 669 | var direction = new Vector(x2-x1, y2-y1); 670 | var normal = direction.normal().normalise(); 671 | 672 | var from = graph.getEdges(edge.source, edge.target); 673 | var to = graph.getEdges(edge.target, edge.source); 674 | 675 | var total = from.length + to.length; 676 | 677 | // Figure out edge's position in relation to other edges between the same nodes 678 | var n = 0; 679 | for (var i=0; i 1 || ub < 0 || ub > 1) { 795 | return false; 796 | } 797 | 798 | return new Vector(p1.x + ua * (p2.x - p1.x), p1.y + ua * (p2.y - p1.y)); 799 | } 800 | 801 | function intersect_line_box(p1, p2, p3, w, h) { 802 | var tl = {x: p3.x, y: p3.y}; 803 | var tr = {x: p3.x + w, y: p3.y}; 804 | var bl = {x: p3.x, y: p3.y + h}; 805 | var br = {x: p3.x + w, y: p3.y + h}; 806 | 807 | var result; 808 | if (result = intersect_line_line(p1, p2, tl, tr)) { return result; } // top 809 | if (result = intersect_line_line(p1, p2, tr, br)) { return result; } // right 810 | if (result = intersect_line_line(p1, p2, br, bl)) { return result; } // bottom 811 | if (result = intersect_line_line(p1, p2, bl, tl)) { return result; } // left 812 | 813 | return false; 814 | } 815 | 816 | return this; 817 | } 818 | 819 | 820 | return Graph; 821 | }); -------------------------------------------------------------------------------- /js/libs/underscore/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd&& 11 | define("underscore",function(){return b}),r._=b;b.VERSION="1.3.1";var j=b.each=b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a==null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&& 13 | (c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e; 14 | if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a, 15 | function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a, 16 | b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c= 17 | e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e= 20 | 0;e= 24 | 0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]}); 25 | return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"}; 26 | b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate= 27 | function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g, 28 | "/")};b.mixin=function(a){j(b.functions(a),function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape|| 29 | t,function(a,b){return"',_.escape("+u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c? 30 | b(a).chain():a},K=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments), 31 | this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | // Require.js allows us to configure shortcut alias 2 | // Their usage will become more apparent futher along in the tutorial. 3 | require.config({ 4 | paths: { 5 | // Major libraries 6 | jquery: 'libs/jquery/jquery-min', 7 | underscore: 'libs/underscore/underscore-min', // https://github.com/amdjs 8 | lodash: 'libs/lodash/lodash', // alternative to underscore 9 | backbone: 'libs/backbone/backbone-min', // https://github.com/amdjs 10 | sinon: 'libs/sinon/sinon.js', 11 | 12 | // Require.js plugins 13 | text: 'libs/require/text', 14 | 15 | // Just a short cut so we can put our html outside the js dir 16 | // When you have HTML/CSS designers this aids in keeping them out of the js directory 17 | templates: '../templates' 18 | } 19 | 20 | }); 21 | 22 | // Let's kick off the application 23 | 24 | require([ 25 | 'views/app', 26 | 'router', 27 | 'vm' 28 | ], function(AppView, Router, Vm){ 29 | var appView = Vm.create({}, 'AppView', AppView); 30 | appView.render(); 31 | Router.initialize({appView: appView}); // The router now has a copy of all main appview 32 | }); 33 | -------------------------------------------------------------------------------- /js/models/projects.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'lodash', 3 | 'backbone' 4 | ], function(_, Backbone) { 5 | var projectsModel = Backbone.Model.extend({ 6 | defaults: { 7 | score: 10 8 | }, 9 | initialize: function(){ 10 | } 11 | 12 | }); 13 | return projectsModel; 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /js/router.js: -------------------------------------------------------------------------------- 1 | // Filename: router.js 2 | define([ 3 | 'jquery', 4 | 'underscore', 5 | 'backbone', 6 | 'vm' 7 | ], function ($, _, Backbone, Vm) { 8 | var AppRouter = Backbone.Router.extend({ 9 | routes: { 10 | // Pages 11 | 'modules': 'modules', 12 | 'optimize': 'optimize', 13 | 'backbone/:section': 'backbone', 14 | 'backbone': 'backbone', 15 | 'manager': 'manager', 16 | 17 | // Default - catch all 18 | '*actions': 'defaultAction' 19 | } 20 | }); 21 | 22 | var initialize = function(options){ 23 | var appView = options.appView; 24 | var router = new AppRouter(options); 25 | router.on('route:optimize', function () { 26 | require(['views/optimize/page'], function (OptimizePage) { 27 | var optimizePage = Vm.create(appView, 'OptimizePage', OptimizePage); 28 | optimizePage.render(); 29 | }); 30 | }); 31 | router.on('route:defaultAction', function (actions) { 32 | require(['views/dashboard/page'], function (DashboardPage) { 33 | var dashboardPage = Vm.create(appView, 'DashboardPage', DashboardPage); 34 | dashboardPage.render(); 35 | }); 36 | }); 37 | router.on('route:modules', function () { 38 | require(['views/modules/page'], function (ModulePage) { 39 | var modulePage = Vm.create(appView, 'ModulesPage', ModulePage); 40 | modulePage.render(); 41 | }); 42 | }); 43 | router.on('route:backbone', function (section) { 44 | require(['views/backbone/page'], function (BackbonePage) { 45 | var backbonePage = Vm.create(appView, 'BackbonePage', BackbonePage, {section: section}); 46 | backbonePage.render(); 47 | }); 48 | }); 49 | router.on('route:manager', function () { 50 | require(['views/manager/page'], function (ManagerPage) { 51 | var managerPage = Vm.create(appView, 'ManagerPage', ManagerPage); 52 | managerPage.render(); 53 | }); 54 | }); 55 | Backbone.history.start(); 56 | }; 57 | return { 58 | initialize: initialize 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /js/views/app.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'vm', 6 | 'events', 7 | 'text!templates/layout.html' 8 | ], function($, _, Backbone, Vm, Events, layoutTemplate){ 9 | var AppView = Backbone.View.extend({ 10 | el: '.container', 11 | initialize: function () { 12 | 13 | var NestedView2 = Backbone.View.extend({}); 14 | var NestedView1 = Backbone.View.extend({ 15 | initialize: function () { 16 | var nestedView2 = Vm.create(this, 'Nested View 2', NestedView2); 17 | } 18 | }); 19 | var nestedView1 = Vm.create(this, 'Nested View 1', NestedView1); 20 | 21 | }, 22 | render: function () { 23 | var that = this; 24 | $(this.el).html(layoutTemplate); 25 | require(['views/header/menu'], function (HeaderMenuView) { 26 | var headerMenuView = Vm.create(that, 'HeaderMenuView', HeaderMenuView); 27 | headerMenuView.render(); 28 | }); 29 | require(['views/footer/footer'], function (FooterView) { 30 | // Pass the appView down into the footer so we can render the visualisation 31 | var footerView = Vm.create(that, 'FooterView', FooterView, {appView: that}); 32 | footerView.render(); 33 | }); 34 | 35 | } 36 | }); 37 | return AppView; 38 | }); 39 | -------------------------------------------------------------------------------- /js/views/backbone/page.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'vm', 6 | 'text!templates/backbone/page.html', 7 | 'views/backbone/sidemenu', 8 | 'views/backbone/section' 9 | ], function($, _, Backbone, Vm, backbonePageTemplate, SidemenuView, SectionView){ 10 | var BackbonePage = Backbone.View.extend({ 11 | el: '.page', 12 | render: function () { 13 | this.$el.html(backbonePageTemplate); 14 | 15 | var sidemenuView = Vm.create(this, 'BackboneSideMenuView', SidemenuView); 16 | sidemenuView.render(); 17 | 18 | var sectionView = Vm.create(this, 'BackboneSectionView', SectionView, {section: this.options.section}); 19 | sectionView.render(); 20 | } 21 | }); 22 | return BackbonePage; 23 | }); 24 | -------------------------------------------------------------------------------- /js/views/backbone/section.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'vm' 6 | ], function($, _, Backbone, Vm){ 7 | var BackbonePage = Backbone.View.extend({ 8 | el: '.content', 9 | render: function () { 10 | this.$el.html('

Incomplete

'); 11 | } 12 | }); 13 | return BackbonePage; 14 | }); 15 | -------------------------------------------------------------------------------- /js/views/backbone/sidemenu.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'text!templates/backbone/sidemenu.html', 6 | ], function($, _, Backbone, sidemenuTemplate){ 7 | var Sidemenu = Backbone.View.extend({ 8 | el: '.submenu', 9 | render: function () { 10 | $(this.el).html(sidemenuTemplate); 11 | } 12 | }); 13 | return Sidemenu; 14 | }); 15 | -------------------------------------------------------------------------------- /js/views/dashboard/page.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'text!templates/dashboard/page.html' 6 | ], function($, _, Backbone, dashboardPageTemplate){ 7 | var DashboardPage = Backbone.View.extend({ 8 | el: '.page', 9 | render: function () { 10 | 11 | $(this.el).html(dashboardPageTemplate); 12 | } 13 | }); 14 | return DashboardPage; 15 | }); 16 | -------------------------------------------------------------------------------- /js/views/footer/footer.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'events', 6 | 'text!templates/footer/footer.html', 7 | 'libs/springy/springyui' 8 | ], function($, _, Backbone, Events, footerTemplate, Springy){ 9 | var FooterView = Backbone.View.extend({ 10 | el: '.footer', 11 | intialize: function () { 12 | 13 | }, 14 | render: function () { 15 | $(this.el).html(footerTemplate); 16 | this.renderSpringy(); 17 | Events.on('viewCreated', this.renderSpringy, this); 18 | }, 19 | renderSpringy: function () { 20 | var that = this; 21 | var graph = new Springy(); 22 | var generateGraph = function (context, parentName, first) { 23 | if(typeof first === 'undefined'){ 24 | var first = graph.newNode({label: parentName}); 25 | } 26 | _.each(context.children, function(view, viewname) { 27 | var second = graph.newNode({label: viewname + ' (' + view.cid + ')'}); 28 | graph.newEdge(first, second, {color: '#000'}); 29 | generateGraph(view, viewname, second); 30 | }); 31 | return; 32 | }; 33 | 34 | generateGraph(this.options.appView, 'AppView'); 35 | 36 | $('#springydemo').remove(); 37 | $('.springy-container').html(''); 38 | var springy = $('#springydemo'); 39 | springy.springy({ 40 | graph: graph 41 | }); 42 | }, 43 | clean: function () { 44 | Events.off('viewCreated', this.renderSpringy); 45 | } 46 | }); 47 | 48 | return FooterView; 49 | }); 50 | -------------------------------------------------------------------------------- /js/views/header/menu.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'text!templates/header/menu.html' 6 | ], function($, _, Backbone, headerMenuTemplate){ 7 | var HeaderMenuView = Backbone.View.extend({ 8 | el: '.main-menu-container', 9 | initialize: function () { 10 | }, 11 | render: function () { 12 | $(this.el).html(headerMenuTemplate); 13 | $('a[href="' + window.location.hash + '"]').addClass('active'); 14 | }, 15 | events: { 16 | 'click a': 'highlightMenuItem' 17 | }, 18 | highlightMenuItem: function (ev) { 19 | $('.active').removeClass('active'); 20 | $(ev.currentTarget).addClass('active'); 21 | } 22 | }) 23 | 24 | return HeaderMenuView; 25 | }); 26 | -------------------------------------------------------------------------------- /js/views/manager/page.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'vm', 6 | 'text!templates/manager/page.html' 7 | ], function($, _, Backbone, Vm, managerPageTemplate){ 8 | var ManagerPage = Backbone.View.extend({ 9 | el: '.page', 10 | render: function () { 11 | this.$el.html(managerPageTemplate); 12 | }, 13 | events: { 14 | 'click .add-view': 'addView' 15 | }, 16 | counter: 1, 17 | addView: function () { 18 | var RandomView = Backbone.View.extend({}); 19 | var randomView = Vm.create(this, 'RandomView ' + this.counter, RandomView); 20 | this.counter++; 21 | return false; 22 | } 23 | }); 24 | return ManagerPage; 25 | }); 26 | -------------------------------------------------------------------------------- /js/views/modules/page.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'text!templates/modules/page.html' 6 | ], function($, _, Backbone, modulesPageTemplate){ 7 | var ModulesPage = Backbone.View.extend({ 8 | el: '.page', 9 | render: function () { 10 | this.$el.html(modulesPageTemplate); 11 | } 12 | }); 13 | return ModulesPage; 14 | }); 15 | -------------------------------------------------------------------------------- /js/views/optimize/page.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'lodash', 4 | 'backbone', 5 | 'text!templates/optimize/page.html' 6 | ], function($, _, Backbone, optimizePageTemplate){ 7 | var OptimizePage = Backbone.View.extend({ 8 | el: '.page', 9 | render: function () { 10 | this.$el.html(optimizePageTemplate); 11 | } 12 | }); 13 | return OptimizePage; 14 | }); 15 | -------------------------------------------------------------------------------- /js/vm.js: -------------------------------------------------------------------------------- 1 | // Use this as a quick template for future modules 2 | define([ 3 | 'jquery', 4 | 'underscore', 5 | 'backbone', 6 | 'events' 7 | ], function($, _, Backbone, Events){ 8 | var views = {}; 9 | var create = function (context, name, View, options) { 10 | // View clean up isn't actually implemented yet but will simply call .clean, .remove and .unbind 11 | if(typeof views[name] !== 'undefined') { 12 | views[name].undelegateEvents(); 13 | if(typeof views[name].clean === 'function') { 14 | views[name].clean(); 15 | } 16 | } 17 | var view = new View(options); 18 | views[name] = view; 19 | if(typeof context.children === 'undefined'){ 20 | context.children = {}; 21 | context.children[name] = view; 22 | } else { 23 | context.children[name] = view; 24 | } 25 | Events.trigger('viewCreated'); 26 | return view; 27 | }; 28 | 29 | 30 | return { 31 | create: create 32 | }; 33 | }); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Thomas Davies (http://backbonetutorials.com/)", 3 | "name": "backboneboilerplate", 4 | "description": "A modular backbone environment", 5 | "version": "0.0.0", 6 | "homepage": "http://backboneboilerplate.com/", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/thomasdavis/backboneboilerplate.git" 10 | }, 11 | "main": "build/server", 12 | "engines": { 13 | "node": "~0.6.11" 14 | }, 15 | "dependencies": { 16 | "express": "~2.5.8" 17 | }, 18 | "devDependencies": {}, 19 | "optionalDependencies": {} 20 | } 21 | -------------------------------------------------------------------------------- /templates/backbone/page.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | -------------------------------------------------------------------------------- /templates/backbone/sidemenu.html: -------------------------------------------------------------------------------- 1 |

Incomplete

-------------------------------------------------------------------------------- /templates/dashboard/page.html: -------------------------------------------------------------------------------- 1 | 2 |

Introduction

3 |

This is a self-documenting example app useful for quick developer and/or learning backbone.js.

4 |

There are many boiler plates for Backbone.js boilerplates popping up over the place with different philosophies. 5 | This solution aims to be absolutely light-weight and robust keeping the following points in mind;

6 |
    7 |
  • Stand on the shoulders of giants (Backbone.js, Underscore.js, Require.js, Html5Boilerplate)
  • 8 |
  • Perpetuate great and efficient practices for single page applications
  • 9 |
  • Optimized deployment consisting of one js file, one image sprite and one css file (with exceptions)
  • 10 |
  • One command optimization
  • 11 |
  • No enforced pre-compilation creating a low entry barrier to starting
  • 12 |
  • Modular environment (reusable, isolated testing)
  • 13 |
  • Speed!
  • 14 |
15 |

The project aims at being a modular backbone environment with as little authority on development as possible such that developers can innovate and contribute in an attempt to mimic the success of backbone.js ambiguous nature.

16 | 17 |

Getting started

18 |

Simply clone the repo and serve the files on a http server (nginx, apache)

19 | git clone https://github.com/thomasdavis/backboneboilerplate.git 20 |

Alternatively run

21 |
node build/server
22 |

You may need to install Express

23 |
npm install express
24 | -------------------------------------------------------------------------------- /templates/footer/footer.html: -------------------------------------------------------------------------------- 1 |
2 |

Authors

3 | 13 | 14 | The view visualization is just sitting here for lack of a better place at the moment. It will update as new views come into play in the app. 15 |
-------------------------------------------------------------------------------- /templates/header/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /templates/home/main.html: -------------------------------------------------------------------------------- 1 | Welcome to the Modular Backbone homepage. 2 | 3 | There are many benefits of combining Require.js with Backbone.js 4 | 5 |
    6 |
  • Take advantage of Require.js compiler! My dying words would be to tell you to try r.js
  • 7 |
8 | -------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
Loading
6 | 7 |
8 | 9 |
10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/manager/page.html: -------------------------------------------------------------------------------- 1 |

Backbone View Manager

2 | Incomplete 3 |

This page talks about vm.js(View Manager) which is included in this boilerplate. Vm.js helps control the life cycle of views and assist in areas such as;

4 |
    5 |
  • Zombie Views
  • 6 |
  • Duplicate Events
  • 7 |
  • Organization of nested views
  • 8 |
9 | 10 |

Visualization

11 |

The View Manager can also be used for producing cool charts such as in the proof of concept in footer of the page. 12 | I'd love to plot the Event bus on top of this graph in the future to see how your applications events are tied up

13 |

Test: Add View

-------------------------------------------------------------------------------- /templates/modules/modules.html: -------------------------------------------------------------------------------- 1 | a 2 | a 3 | -------------------------------------------------------------------------------- /templates/modules/page.html: -------------------------------------------------------------------------------- 1 |

'Modular' enviroment

2 |

3 | Javascript currently lacks a native way to organized your code into modules unlike other languages. 4 | This has lead to developers writing spaghetti code or using global variable namespaces which both end 5 | diastrously when scaling. 6 |

7 |

I'm sure most people understand the benefits of 'modularized' code. But due to the nature of front-end development 8 | it has been quite disregarded up until now.

9 | 10 |

AMD and Require.js

11 |

Asynchronous Module Definitions designed to load modular code asynchronously in the browser and server. It is actually a fork of the Common.js specification. Many script loaders have built their implementations around AMD, seeing it as the future of modular Javascript development.

12 | 13 |

Backboneboilerplate.com supports Require.js the default AMD loader. It has a vibrant open-source ecology.

14 | 15 |

Laying out the modules

16 |

Compare backboneboilerplate.com to others here

-------------------------------------------------------------------------------- /templates/modules/untitled: -------------------------------------------------------------------------------- 1 | a 2 | a 3 | -------------------------------------------------------------------------------- /templates/optimize/page.html: -------------------------------------------------------------------------------- 1 |

Optimization

2 |

The boilerplate project contains a `build` folder and is designed to be a one line execution optimization process.

3 | 4 |
    5 |
  1. Make sure you have node.js installed
  2. 6 |
  3. Execute ./build.sh from inside the build folder
  4. 7 |
  5. Find your ready to go application in `/build/output`
  6. 8 |
9 | 10 |

Explanation

11 |

12 | The build process uses the require.js optimizer called r.js, it traverses through the dependency list and compiles and minifies them intelligently. 13 |

14 |

15 | Using r.js once again it runs over styles.css and inlines all the style sheets into one and then minifies the file. 16 |

17 |

18 | Finally it creates a directory for your output and replaces the relative path to require.js with the latest version hosted on cdnjs.com 19 |

20 |

21 | You should now have one Javascript file, one CSS file and the index.html 22 |

23 |

24 | A demo of the outputted code can be found at http://backboneboilerplate.com/build/output/, make sure you check out the network request tab! (ignore the twitter assets)

25 |

26 | 27 | --------------------------------------------------------------------------------