PageRenderTime 60ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/public/js/app.js

https://bitbucket.org/bskrypnyk/nodejs-express-project-template
JavaScript | 1121 lines | 819 code | 287 blank | 15 comment | 146 complexity | 2930d480792f85d070f01bb68a5d51f7 MD5 | raw file
  1. define( [
  2. "text",
  3. "underscore",
  4. "jquery",
  5. "json2",
  6. "jquery_cookies",
  7. "jquery_pulse",
  8. "jquery_transit",
  9. "jquery_form",
  10. "bootstrap",
  11. "backbone",
  12. "locale",
  13. "tmpl", // swig only
  14. "common" ],
  15. function(
  16. text,
  17. _underscore,
  18. _jQuery,
  19. _json2, _jquery_cookies, _jquery_pulse, _jquery_transit, _jquery_form, _bootstrap, /* all placeholders, will be undefined */
  20. _backbone, /* just a placeholder, notice, lowercase 'b', Backbone (capitabl 'B') is a global variable; not using AMD-wrapped backbone here */
  21. _locale, /* just a placeholder, will be undefined; injects self into global window namespace */
  22. _tmpl,
  23. common
  24. ) {
  25. var $ = jQuery;
  26. if( typeof window.console == 'undefined' ) {
  27. window.console = {
  28. log : function( msg ) {}
  29. }
  30. }
  31. window.common = common;
  32. // initialize the app
  33. var BaseModel = Backbone.Model.extend( {
  34. // custom base model for the application
  35. hasLocalProgress : false
  36. });
  37. var BaseCollection = Backbone.Collection.extend( {
  38. // custom base collection for the application
  39. hasLocalProgress : false
  40. });
  41. var BaseView = Backbone.View.extend( {
  42. progressDelayMillis : 500,
  43. showReloadOnError : true,
  44. title : function() {
  45. return null;
  46. },
  47. loadingMessage : function() {
  48. return "Loading...";
  49. },
  50. loadingRequest : 0,
  51. _beforeConfigure : function() {},
  52. _afterConfigure : function() {},
  53. _afterSetElement : function() {},
  54. parseJSON : function( data ) {
  55. if( !data ) {
  56. return {};
  57. }
  58. try {
  59. return JSON.parse( data );
  60. }
  61. catch( e ) {
  62. console.log( e );
  63. return {};
  64. }
  65. },
  66. fetch : function( model ) {
  67. var m = model || this.model || this.collection;
  68. if( m && m.isNew && m.isNew() ) {
  69. return;
  70. };
  71. var _this = this;
  72. _this.showLoadProgress();
  73. m.fetch( {
  74. error : function( model, response ) {
  75. _this.loadingRequest = null;
  76. var errorMessage = "Sorry, an unexpected server error has occurred. Please try later.";
  77. if( response.responseText ) {
  78. try {
  79. var data = _this.parseJSON( response.responseText );
  80. if( data && data.errorMessage ) {
  81. errorMessage = data.errorMessage;
  82. }
  83. }
  84. catch( e ) {
  85. }
  86. }
  87. _this.showLoadError( errorMessage );
  88. },
  89. success : function( model, response ) {
  90. _this.loadingRequest = null;
  91. _this.$el.find( ".load-progress" ).remove();
  92. _this.$el.css( "opacity", 0 );
  93. _this.render();
  94. _this.$el.transition( { opacity : 1 } );
  95. }
  96. });
  97. },
  98. showLoadProgress : function() {
  99. var _this = this;
  100. var loadingRequest = ( new Date().getTime() ) + "-" + Math.random();
  101. _this.loadingRequest = loadingRequest;
  102. setTimeout( function() {
  103. if( _this.loadingRequest != loadingRequest ) { // things changed, bail
  104. return;
  105. }
  106. var progress = $( "<div class='view-load-progress'><div class='load-progress progress progress-striped active'>" +
  107. "<div class='bar progress-info' style='width: 100%;'></div></div><div class='view-load-message'></div></div>" );
  108. progress.find( ".view-load-message" ).text( _this.loadingMessage() || "Loading..." );
  109. _this.$el.empty().append( progress );
  110. }, this.progressDelayMillis );
  111. },
  112. showLoadError : function( errorMessage ) {
  113. var errorBlock = $( "<div></div>" );
  114. errorBlock.addClass( "view-error" );
  115. var alert = $( "<div></div>" );
  116. alert.addClass( "alert alert-error" );
  117. /*
  118. var dismiss = $( "<button type='button'>&times;</button>" );
  119. dismiss.addClass( "close" );
  120. dismiss.attr( "data-dismiss", "alert" );
  121. alert.append( dismiss );
  122. */
  123. var msg = $( "<span></span>" );
  124. msg.addClass( "load-error" );
  125. if( errorMessage ) {
  126. msg.html( errorMessage );
  127. }
  128. else {
  129. msg.text( "Sorry, an unexpected error occurred while loading... Please try again later" );
  130. }
  131. alert.append( msg );
  132. errorBlock.append( alert );
  133. if( this.showReloadOnError ) {
  134. var reload = $( " <a href='javascript:;' class='reload'>Try again</a>" );
  135. reload.on( "click", function() {
  136. window.location.reload();
  137. });
  138. msg.append( reload );
  139. }
  140. this.$el.empty().append( errorBlock );
  141. }
  142. });
  143. BaseView.prototype._super_configure = BaseView.prototype._configure;
  144. BaseView.prototype._configure = function( options ) {
  145. var _this = this;
  146. _this._beforeConfigure( options );
  147. _this._super_configure( options );
  148. _this._afterConfigure( options );
  149. // here we wrap around the render method, since
  150. // we are doing this after instantiation, we have access to the
  151. // sub-class's render() method
  152. // here we can trigger custom events, etc
  153. var _render = this.render;
  154. var _setElement = this.setElement;
  155. _this.setElement = function() {
  156. _setElement.apply( this, arguments );
  157. _this.trigger( "element-set", this );
  158. _this._afterSetElement.apply( this, arguments );
  159. };
  160. _this.render = function() {
  161. _render.apply( this, arguments );
  162. _this.trigger( "rendered", this );
  163. };
  164. _this.trigger( "configured", this );
  165. };
  166. var BaseFormView = BaseView.extend( {
  167. progressIndicatorDelay : 500,
  168. _wasNew : false,
  169. events : {
  170. "keydown input" : "onEnterKey"
  171. },
  172. getFieldsToSubmit : function() {}, // must override for submit, return { field: val }
  173. beforeSubmit : function() {}, // to be overriden
  174. submitError : function( model, xhr ) {}, // to be overriden
  175. submitSuccess : function( model, data ) {}, // to be overriden
  176. onEnterKey : function( e ) {
  177. if( e && e.keyCode == 13 ) {
  178. e.stopPropagation();
  179. e.preventDefault();
  180. this.$el.find( ".submit" ).eq(0).trigger( "click" );
  181. return false;
  182. }
  183. },
  184. prepareFormForSubmit : function() {
  185. var _this = this;
  186. this.$el.find( ".progress-indicator" ).attr( "data-delayed", "true" );
  187. setTimeout( function() {
  188. if( _this.$el.find( ".progress-indicator" ).attr( "data-delayed" ) === "true" ) {
  189. _this.$el.find( ".progress-indicator" ).removeClass( "invisible" );
  190. }
  191. }, this.progressIndicatorDelay);
  192. this.$el.find( ".alert" ).html( "&nbsp;" ).addClass( "invisible" );
  193. common.clearErrorsFromFormControls( this.$el );
  194. this.$el.find( ".submit" ).attr( "disabled", "disabled" );
  195. },
  196. restoreFormAfterSubmit : function() {
  197. this.$el.find( ".progress-indicator" ).removeAttr( "data-delayed" )
  198. this.$el.find( ".progress-indicator" ).addClass( "invisible" );
  199. this.$el.find( ".submit" ).removeAttr( "disabled" );
  200. },
  201. _onError : function( model, xhr ) {
  202. this.restoreFormAfterSubmit();
  203. var data = {};
  204. if( !xhr.responseText ) {
  205. data = {
  206. errorMessage : "Sorry, an unexpected server error has occurred. Please try later."
  207. }
  208. }
  209. else {
  210. data = this.parseJSON( xhr.responseText );
  211. }
  212. if( data.errors ) {
  213. common.addErrorsToFormControls( this.$el, data.errors, {
  214. placement : this.options.fieldErrorPlacement ? this.options.fieldErrorPlacement : "right"
  215. });
  216. }
  217. if( data.errorMessage ) {
  218. this.$el.find( ".alert" ).html( data.errorMessage ).
  219. removeClass( "invisible" );
  220. }
  221. this.submitError( model, xhr );
  222. },
  223. _onSuccess : function( model, data ) {
  224. this.restoreFormAfterSubmit();
  225. this.submitSuccess( model, data );
  226. },
  227. wasNew : function() {
  228. return this._wasNew;
  229. },
  230. submit : function() {
  231. var _this = this;
  232. if( !this.model ) {
  233. console.log( "cannot submit: no model" );
  234. return;
  235. }
  236. _this.prepareFormForSubmit();
  237. if( _this.model ) {
  238. _this._wasNew = _this.model.isNew();
  239. _this.model.save( this.getFieldsToSubmit(), {
  240. wait : true,
  241. error : function( model, xhr ) {
  242. _this._onError( model, xhr );
  243. }
  244. ,
  245. success : function( model, xhr ) {
  246. _this._onSuccess( model, xhr );
  247. model.trigger('sync', model, xhr);
  248. }
  249. });
  250. }
  251. }
  252. });
  253. var BaseCollectionView = BaseView.extend( {
  254. });
  255. var syncSuper = Backbone.sync;
  256. Backbone.sync = function( method, model, options ) {
  257. options = options || {};
  258. var callerError = options.error;
  259. var callerSuccess = options.success;
  260. options.error = function() {
  261. if( !model.hasLocalProgress ) {
  262. App.hideProgress( model );
  263. }
  264. if( callerError ) {
  265. callerError.apply( null, arguments );
  266. }
  267. };
  268. options.success = function() {
  269. if( !model.hasLocalProgress ) {
  270. App.hideProgress( model );
  271. }
  272. if( callerSuccess ) {
  273. callerSuccess.apply( null, arguments );
  274. }
  275. };
  276. if( !model.hasLocalProgress ) {
  277. App.showProgress( model );
  278. }
  279. syncSuper( method, model, options );
  280. };
  281. ( function() {
  282. Backbone.History.prototype._loadUrl = Backbone.History.prototype.loadUrl;
  283. Backbone.History.prototype.routeInterceptors = [];
  284. Backbone.History.prototype.pushRouteInterceptor = function( interceptor ) {
  285. this.routeInterceptors.push( interceptor );
  286. };
  287. Backbone.History.prototype.loadUrl = function(fragmentOverride) {
  288. var fragment = this.getFragment(fragmentOverride);
  289. for( var i=0; i<this.routeInterceptors.length; ++i ) {
  290. if( this.routeInterceptors[i]( fragment ) ) {
  291. return true;
  292. }
  293. }
  294. return this._loadUrl( fragmentOverride );
  295. };
  296. Backbone.History.prototype.withRoot = function( url ) {
  297. url = url || "";
  298. if( this.options.root ) {
  299. if( url.indexOf( this.options.root) == 0 ) {
  300. return url;
  301. }
  302. if( this.options.root.match( /.+\/$/ ) ) {
  303. if( url.indexOf( '/' ) == 0 ) {
  304. return this.options.root + url.substring(1);
  305. }
  306. else {
  307. return this.options.root + url;
  308. }
  309. }
  310. else {
  311. this.options.root + '/' + url;
  312. }
  313. }
  314. return url;
  315. };
  316. Backbone.History.prototype.withoutRoot = function( url ) {
  317. url = url || "";
  318. if( this.options.root ) {
  319. if( url.indexOf( this.options.root) == 0 ) {
  320. if( this.options.root.match( /.+\/$/ ) ) {
  321. return url.replace( this.options.root, "/" );
  322. }
  323. else {
  324. return url.replace( this.options.root, "" );
  325. }
  326. }
  327. }
  328. return url;
  329. };
  330. Backbone.History.prototype.navigateURL = function( url, silent ) {
  331. var prev = window.location.pathname + ( window.location.search || "" );
  332. url = this.withoutRoot( url );
  333. this.navigate( url, { trigger : !silent } );
  334. this.trigger( "navigateURL", {
  335. before : prev,
  336. after: this.withRoot( url )
  337. });
  338. };
  339. Backbone.History.prototype.replaceURL = function( url, silent ) {
  340. var prev = window.location.pathname + ( window.location.search || "" );
  341. var url = this.withoutRoot( url );
  342. this.navigate( url, { replace: true, trigger : !silent } );
  343. this.trigger( "replaceURL", {
  344. before : prev,
  345. after: this.withRoot( url )
  346. });
  347. };
  348. })();
  349. var HistoryLayout = BaseView.extend( {
  350. currentIndex : -1,
  351. views : [],
  352. history: [],
  353. visibleBreadcrumbs : 5,
  354. createStack : function() {
  355. var out = document.createElement( "div" );
  356. out.className = "stack-item";
  357. this.$container.append( out );
  358. return out;
  359. },
  360. initialize : function( options ) {
  361. },
  362. addView : function( view ) {
  363. var _this = this;
  364. if( this.currentIndex < this.views.length - 1 ) {
  365. this._trimStack( this.currentIndex );
  366. }
  367. var m = view.model || view.collection;
  368. if( m ) {
  369. m.on( "change", function() {
  370. _this._navigated();
  371. });
  372. }
  373. var current = _.last( this.views );
  374. _this.views.push( {
  375. url : window.location.pathname + ( window.location.search ? window.location.search : "" ),
  376. title : view.title(),
  377. view: view
  378. } );
  379. this.history.push( {
  380. url : window.location.pathname + ( window.location.search ? window.location.search : "" ),
  381. title: view.title(),
  382. view : view
  383. });
  384. if( current && current.view ) {
  385. this._hideView( current.view );
  386. }
  387. var stack = this.createStack();
  388. $(stack).empty().append( view.$el );
  389. this._revealView( view );
  390. this.currentIndex++;
  391. this._navigated();
  392. this._prune();
  393. },
  394. back : function() {
  395. this.gotoIndex( this.currentIndex - 1 );
  396. },
  397. forward : function() {
  398. this.gotoIndex( this.currentIndex + 1 );
  399. },
  400. checkRouteInHistory : function( fragment ) {
  401. var url = Backbone.history.options.root + fragment;
  402. for( var i=this.views.length-1; i>=0; --i ) {
  403. if( this.views[i] && this.views[i].url === url ) {
  404. this.gotoIndex( i );
  405. return true;
  406. }
  407. }
  408. return false;
  409. },
  410. gotoIndex : function( index ) {
  411. if( index < 0 || index > this.views.length - 1 ) {
  412. return;
  413. }
  414. var previous = this.currentIndex;
  415. this.currentIndex = index;
  416. var entry = this.views[previous];
  417. if( entry && entry.view ) {
  418. this._hideView( entry.view );
  419. }
  420. var currentEntry = this.views[this.currentIndex];
  421. if( currentEntry && currentEntry.view ) {
  422. this._revealView( currentEntry.view );
  423. }
  424. if( Backbone.history ) {
  425. Backbone.history.navigate( Backbone.history.withoutRoot( currentEntry.url ), { trigger: false, replace: true });
  426. }
  427. this._navigated();
  428. },
  429. updateBreadcrumbs : function() {
  430. var _this = this;
  431. var breadcrumbsStartIndex = -1;
  432. // get current crumb
  433. if( this.views.length > 0 ) {
  434. var crumbVisible = false;
  435. var currentView = this.views[this.currentIndex];
  436. if( currentView ) {
  437. var saved = _this.$breadcrumbs.data( "breadcrumbsStartIndex" );
  438. if( isNaN(saved) ) {
  439. saved = -1;
  440. }
  441. breadcrumbsStartIndex = saved;
  442. this.$breadcrumbs.find( "a.visible-crumb" ).each( function(idx) {
  443. if( $(this).attr( "href" ) === currentView.url ) {
  444. crumbVisible = true;
  445. if( idx == 0 && breadcrumbsStartIndex >= 0 && breadcrumbsStartIndex > 0 ) {
  446. breadcrumbsStartIndex--;
  447. }
  448. else if( idx >= _this.visibleBreadcrumbs -1 && breadcrumbsStartIndex >= 0 &&
  449. breadcrumbsStartIndex + _this.visibleBreadcrumbs < _this.views.length) {
  450. breadcrumbsStartIndex++;
  451. }
  452. return false;
  453. }
  454. });
  455. }
  456. if( !crumbVisible ) {
  457. breadcrumbsStartIndex = -1;
  458. }
  459. }
  460. this.$breadcrumbs.empty();
  461. if( this.views.length == 0 ) {
  462. return;
  463. }
  464. if( breadcrumbsStartIndex == -1 ) {
  465. if( this.views.length - this.currentIndex >= this.visibleBreadcrumbs ) {
  466. breadcrumbsStartIndex = this.currentIndex - 1;
  467. }
  468. else {
  469. breadcrumbsStartIndex = this.views.length - this.visibleBreadcrumbs;
  470. }
  471. if( breadcrumbsStartIndex < 0 ) {
  472. breadcrumbsStartIndex = 0;
  473. }
  474. }
  475. this.$breadcrumbs.data( "breadcrumbsStartIndex", breadcrumbsStartIndex );
  476. if( breadcrumbsStartIndex > 0 ) {
  477. var li = $( "<li></li>" );
  478. li.addClass( "dropdown left" );
  479. var a = $( "<a></a>" );
  480. a.addClass( "dropdown-toggle" );
  481. a.attr( "role", "button" );
  482. a.attr( "data-toggle", "dropdown" );
  483. a.attr( "data-target", "#" );
  484. a.attr( "href", "javascript:;" );
  485. a.text( "..." );
  486. li.append( a );
  487. var dropdown = $( "<ul></ul>" );
  488. dropdown.addClass( "dropdown-menu" );
  489. dropdown.attr( "role", "menu" );
  490. li.append( dropdown );
  491. for( var i=breadcrumbsStartIndex-1; i>=0; --i ) {
  492. var item = $( "<li></li>" );
  493. var a = $( "<a></a>" );
  494. a.attr( "tabindex", "-1" );
  495. a.attr( "href", this.views[i].url );
  496. a.text( this.views[i].view.title() );
  497. a.on( "click", function(e) {
  498. e.preventDefault();
  499. e.stopPropagation();
  500. Backbone.history.navigateURL( $(this).attr("href") );
  501. return false;
  502. });
  503. item.append( a );
  504. dropdown.append( item );
  505. }
  506. this.$breadcrumbs.append( li );
  507. }
  508. for( var i=breadcrumbsStartIndex; i<this.views.length && i < (breadcrumbsStartIndex + this.visibleBreadcrumbs); ++i ) {
  509. var li = $( "<li></li>" );
  510. if( i > 0 ) {
  511. li.append( $( "<span class='divider'>/</span>" ) );
  512. }
  513. var a = $( "<a></a>" );
  514. a.attr( "href", this.views[i].url );
  515. a.addClass( "visible-crumb" );
  516. a.text( this.views[i].view.title() );
  517. a.on( "click", function(e) {
  518. e.preventDefault();
  519. e.stopPropagation();
  520. Backbone.history.navigateURL( $(this).attr("href") );
  521. return false;
  522. });
  523. li.append( a );
  524. if( this.views[i].url == this.views[this.currentIndex].url ) {
  525. li.addClass( "active" );
  526. a.addClass( "active" );
  527. }
  528. this.$breadcrumbs.append( li );
  529. }
  530. if( breadcrumbsStartIndex + this.visibleBreadcrumbs < this.views.length ) {
  531. var li = $( "<li></li>" );
  532. li.addClass( "dropdown right" );
  533. li.append( $( "<span class='divider'>/</span>" ) );
  534. var a = $( "<a></a>" );
  535. a.addClass( "dropdown-toggle" );
  536. a.attr( "role", "button" );
  537. a.attr( "data-toggle", "dropdown" );
  538. a.attr( "data-target", "#" );
  539. a.attr( "href", "javascript:;" );
  540. a.text( "..." );
  541. li.append( a );
  542. var dropdown = $( "<ul></ul>" );
  543. dropdown.addClass( "dropdown-menu" );
  544. dropdown.attr( "role", "menu" );
  545. li.append( dropdown );
  546. for( var i=breadcrumbsStartIndex + this.visibleBreadcrumbs; i<this.views.length; ++i ) {
  547. var item = $( "<li></li>" );
  548. var a = $( "<a></a>" );
  549. a.attr( "tabindex", "-1" );
  550. a.attr( "href", this.views[i].url );
  551. a.text( this.views[i].view.title() );
  552. a.on( "click", function(e) {
  553. e.preventDefault();
  554. e.stopPropagation();
  555. Backbone.history.navigateURL( $(this).attr("href") );
  556. return false;
  557. });
  558. item.append( a );
  559. dropdown.append( item );
  560. }
  561. this.$breadcrumbs.append( li );
  562. }
  563. },
  564. updateNavigation : function() {
  565. if( this.views.length == 0 ) {
  566. this.$buttons.find( "a" ).attr( "disabled" );
  567. return;
  568. }
  569. this.$buttons.find( "a" ).removeAttr( "disabled" );
  570. if( this.currentIndex == this.views.length - 1 ) {
  571. this.$buttons.find( ".next" ).attr( "disabled", "disabled" );
  572. }
  573. else {
  574. this.$buttons.find( ".next" ).removeAttr( "disabled" );
  575. }
  576. if( this.currentIndex == 0 ) {
  577. this.$buttons.find( ".prev" ).attr( "disabled", "disabled" );
  578. }
  579. else {
  580. this.$buttons.find( ".prev" ).removeAttr( "disabled" );
  581. }
  582. var v = this.views[this.currentIndex];
  583. if( v && v.view && v.view.model && v.view.model.isNew() ) {
  584. this.$buttons.find( ".reload" ).attr( "disabled", "disabled" );
  585. }
  586. else {
  587. this.$buttons.find( ".reload" ).removeAttr( "disabled" );
  588. }
  589. },
  590. updateControls : function() {
  591. this.updateBreadcrumbs();
  592. this.updateNavigation();
  593. },
  594. _navigated : function() {
  595. this._dumpViews();
  596. this.updateControls();
  597. },
  598. _dumpViews : function() {
  599. console.log( "++++++++++++++++++++++++++++" );
  600. for( var i=0; i<this.views.length; ++i ) {
  601. console.log( this.views[i].url );
  602. }
  603. console.log( "++++++++++++++++++++++++++++" );
  604. },
  605. _prune : function() {
  606. },
  607. showHistory : function() {
  608. },
  609. _trimStack : function( fromIndex ) {
  610. for( var i=fromIndex+1; i<this.views.length; ++i ) {
  611. this._destroyView( i );
  612. }
  613. this.views = this.views.slice( 0, fromIndex+1 );
  614. },
  615. _destroyView : function( index ) {
  616. var v = this.views[index];
  617. if( v && v.view) {
  618. try {
  619. v.view.$el.parent().remove();
  620. v.view.remove();
  621. this.views[index] = null;
  622. }
  623. catch( e ) {
  624. }
  625. }
  626. },
  627. _hideView : function( view ) {
  628. if( !view ) {
  629. return;
  630. }
  631. view.$el.parent().hide();
  632. },
  633. _revealView : function( view ) {
  634. view.$el.parent().show();
  635. },
  636. _removeURL : function( url ) {
  637. var found = -1;
  638. for( var i=0; i<this.views.length; ++i ) {
  639. if( this.views[i].url === url ) {
  640. found = i;
  641. this.views[i] = null;
  642. }
  643. }
  644. if( found < 0 ) {
  645. return;
  646. }
  647. if( found < this.currentIndex ) {
  648. this.currentIndex--;
  649. }
  650. this.views = _.compact( this.views );
  651. this._navigated();
  652. },
  653. _afterSetElement : function() {
  654. var _this = this;
  655. if( !Backbone.history ) {
  656. return;
  657. }
  658. Backbone.history.on( "replaceURL", function( e ) {
  659. _this._removeURL( e.before );
  660. });
  661. },
  662. render : function() {
  663. var _this = this;
  664. this.$toolbar = $( "<div></div>" );
  665. this.$toolbar.addClass( "stack-toolbar" );
  666. this.$buttons = $( "<div></div>" );
  667. this.$buttons.addClass( "btn-toolbar" );
  668. var $reload = $( "<a class='btn btn-small reload' href='javascript:;'><i class='icon-repeat'></i></a>" );
  669. $reload.on( "click", function( e ) {
  670. e.preventDefault();
  671. if( _this.currentIndex >= 0 ) {
  672. var entry = _this.views[_this.currentIndex];
  673. if( entry && entry.view && entry.view.fetch ) {
  674. entry.view.fetch();
  675. return false;
  676. }
  677. }
  678. window.location.reload();
  679. return false;
  680. });
  681. this.$buttons.append( $reload );
  682. var $popout = $( "<a class='btn btn-small new-window' href='javascript:;'><i class='icon-fullscreen'></i></a>" );
  683. $popout.on( "click", function( e ) {
  684. e.preventDefault();
  685. var ww = $(window).width();
  686. var wh = $(window).height();
  687. window.common.new_window( window.location.href, ww, wh, Math.floor( Math.random() * 1000 ) + "" );
  688. return false;
  689. });
  690. this.$buttons.append( $popout );
  691. var $nav = $( "<div class='btn-group'></div>" );
  692. this.$buttons.append( $nav );
  693. var $prev = $( "<a class='btn btn-small prev' href='javascript:;'><i class='icon-chevron-left'></i></a>" );
  694. $prev.on( "click", function( e ) {
  695. e.preventDefault();
  696. _this.back();
  697. return false;
  698. });
  699. $nav.append( $prev );
  700. var $next = $( "<a class='btn btn-small next' href='javascript:;'><i class='icon-chevron-right'></i></a>" );
  701. $next.on( "click", function( e ) {
  702. e.preventDefault();
  703. _this.forward();
  704. return false;
  705. });
  706. $nav.append( $next );
  707. this.$toolbar.append( this.$buttons );
  708. this.$breadcrumbs = $( "<ul></ul>" );
  709. this.$breadcrumbs.addClass( "breadcrumb" );
  710. this.$toolbar.append( this.$breadcrumbs );
  711. this.$container = $( "<div></div>" );
  712. this.$container.addClass( "stack" );
  713. this.$el.append( this.$toolbar );
  714. this.$el.append( this.$container );
  715. if( Backbone.history ) {
  716. Backbone.history.pushRouteInterceptor( _.bind( this.checkRouteInHistory, this ) );
  717. }
  718. }
  719. });
  720. window.App = _.extend( {
  721. options : {
  722. globalProgressDefaultMessage : "Working...",
  723. globalProgressIndicatorSelector : "#main-progress-indicator",
  724. globalProgressIndicatorMessageSelector : '',
  725. globalProgressIndicatorDelay : 500
  726. },
  727. views : {},
  728. models : {},
  729. setProgressContent: function ( el, content ) {
  730. var o = this.options;
  731. if( o.globalProgressIndicatorMessageSelector ) {
  732. el.find( o.globalProgressIndicatorMessageSelector ).html( content );
  733. }
  734. else {
  735. el.html( content );
  736. }
  737. },
  738. showProgress : function( source, optionalMessage ) {
  739. var o = this.options;
  740. var p = jQuery( o.globalProgressIndicatorSelector );
  741. if( p.length == 0 ) {
  742. return;
  743. }
  744. if( !source.cid ) {
  745. source.cid = _.uniqueId('unique');
  746. }
  747. var queue = p.data( 'progress-queue' );
  748. if( !queue ) {
  749. queue = [];
  750. p.data( 'progress-queue', queue );
  751. }
  752. var msg = optionalMessage ? optionalMessage : o.globalProgressDefaultMessage;
  753. queue.push( {
  754. cid : source.cid,
  755. msg : msg
  756. });
  757. var _this = this;
  758. function setProgressContent( el, content ) {
  759. if( o.globalProgressIndicatorMessageSelector ) {
  760. el.find( o.globalProgressIndicatorMessageSelector ).html( content );
  761. }
  762. else {
  763. el.html( content );
  764. }
  765. }
  766. setTimeout( function() {
  767. var last = _.last( queue );
  768. if( !last || last.cid !== source.cid ) {
  769. return;
  770. }
  771. if( !p.hasClass( "progress-active" ) ) {
  772. _this.setProgressContent( p, msg );
  773. p.addClass( "progress-active" );
  774. _this.showProgressAnimate( p );
  775. }
  776. else {
  777. _this.setProgressContent( p, msg );
  778. }
  779. }, o.globalProgressIndicatorDelay );
  780. },
  781. hideProgress : function( source ) {
  782. var o = this.options;
  783. var p = jQuery( o.globalProgressIndicatorSelector );
  784. if( p.length == 0 ) {
  785. return;
  786. }
  787. if( !source.cid ) {
  788. return;
  789. }
  790. var queue = p.data( 'progress-queue' );
  791. if( !queue ) {
  792. queue = [];
  793. p.data( 'progress-queue', queue );
  794. }
  795. jQuery.each( queue, function( index, item ) {
  796. if( !item ) {
  797. return;
  798. }
  799. if( item.cid == source.cid ) {
  800. queue[index] = null;
  801. return false;
  802. }
  803. });
  804. queue = _.compact( queue );
  805. p.data( 'progress-queue', queue );
  806. if( queue.length == 0 ) {
  807. p.removeClass( "progress-active" );
  808. this.hideProgressAnimate( p );
  809. return;
  810. }
  811. var last = _.last( queue );
  812. this.setProgressContent( p, last.msg );
  813. },
  814. showProgressAnimate : function( el ) {
  815. jQuery(el).transition( { top : -2 } );
  816. },
  817. hideProgressAnimate : function( el ) {
  818. jQuery(el).transition( { top : -100 } );
  819. },
  820. BaseModel : BaseModel,
  821. BaseCollection : BaseCollection,
  822. BaseView : BaseView,
  823. BaseFormView : BaseFormView,
  824. BaseCollectionView : BaseCollectionView,
  825. HistoryLayout : HistoryLayout
  826. }, Backbone.Events );
  827. jQuery( function($) {
  828. // DOM ready init
  829. if (!$.support.transition) {
  830. $.fn.transition = $.fn.animate;
  831. }
  832. });
  833. return window.App;
  834. });