Mercurial > wikked
changeset 69:0adac3bc079e
Client refactoring:
- Correctly clean-up Backbone views.
- Remove some coupling to the router.
- Remove duplication for view hosts.
- Added ability to revert a page to a previous revision.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 12 Feb 2013 20:55:13 -0800 |
parents | 4cb946982fca |
children | acc615617fdf |
files | static/js/wikked/app.js static/js/wikked/models.js static/js/wikked/view-manager.js static/js/wikked/views.js static/tpl/revision-page.html |
diffstat | 5 files changed, 157 insertions(+), 59 deletions(-) [+] |
line wrap: on
line diff
--- a/static/js/wikked/app.js Tue Feb 12 20:52:58 2013 -0800 +++ b/static/js/wikked/app.js Tue Feb 12 20:55:13 2013 -0800 @@ -10,10 +10,55 @@ ], function($, _, Backbone, Views, Models) { + var exports = {}; + + /** + * View manager. + */ + var ViewManager = exports.ViewManager = function(el) { + this.initialize.apply(this, arguments); + }; + _.extend(ViewManager.prototype, { + initialize: function(el) { + this.el = el; + }, + _currentView: false, + switchView: function(view, autoFetch) { + if (this._currentView) { + this._currentView.dispose(); + this._currentView = false; + } + + if (view) { + this._currentView = view; + this.el.html(view.el); + if (autoFetch || autoFetch === undefined) { + view.model.fetch(); + } + } + + return this; + } + }); + /** * Main router. */ var AppRouter = Backbone.Router.extend({ + initialize: function(options) { + this.viewManager = options ? options.viewManager : undefined; + if (!this.viewManager) { + this.viewManager = new ViewManager($('#app')); + } + + var $router = this; + Backbone.View.prototype.navigate = function(url, options) { + $router.navigate(url, options); + }; + Backbone.Model.prototype.navigate = function(url, options) { + $router.navigate(url, options); + }; + }, routes: { 'read/*path': "readPage", '': "readMainPage", @@ -33,14 +78,12 @@ path_clean = this.stripQuery(path); no_redirect = this.getQueryVariable('no_redirect', path); var view = new Views.PageReadView({ - el: $('#app'), model: new Models.PageReadModel({ path: path_clean }) }); if (no_redirect) { view.model.set('no_redirect', true); } - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/read/' + path); }, readMainPage: function() { @@ -48,60 +91,48 @@ }, editPage: function(path) { var view = new Views.PageEditView({ - el: $('#app'), model: new Models.PageEditModel({ path: path }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/edit/' + path); }, showPageHistory: function(path) { var view = new Views.PageHistoryView({ - el: $('#app'), model: new Models.PageHistoryModel({ path: path }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/changes/' + path); }, showIncomingLinks: function(path) { var view = new Views.IncomingLinksView({ - el: $('#app'), model: new Models.IncomingLinksModel({ path: path }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/inlinks/' + path); }, readPageRevision: function(path, rev) { var view = new Views.PageRevisionView({ - el: $('#app'), rev: rev, model: new Models.PageRevisionModel({ path: path, rev: rev }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/revision/' + path + '/' + rev); }, showDiffWithPrevious: function(path, rev) { var view = new Views.PageDiffView({ - el: $('#app'), rev1: rev, model: new Models.PageDiffModel({ path: path, rev1: rev }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/diff/c/' + path + '/' + rev); }, showDiff: function(path, rev1, rev2) { var view = new Views.PageDiffView({ - el: $('#app'), rev1: rev1, rev2: rev2, model: new Models.PageDiffModel({ path: path, rev1: rev1, rev2: rev2 }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2); }, showSearchResults: function(query) { @@ -109,20 +140,16 @@ query = this.getQueryVariable('q'); } var view = new Views.WikiSearchView({ - el: $('#app'), model: new Models.WikiSearchModel() }); - view.model.setApp(this); - view.model.execute(query); + this.viewManager.switchView(view); this.navigate('/search/' + query); }, showLogin: function() { var view = new Views.LoginView({ - el: $('#app'), model: new Models.LoginModel() }); - view.model.setApp(this); - view.render(); + this.viewManager.switchView(view); this.navigate('/login'); }, doLogout: function() { @@ -137,11 +164,9 @@ }, showSpecialPages: function() { var view = new Views.SpecialPagesView({ - el: $('#app'), model: new Models.SpecialPagesModel() }); - view.model.setApp(this); - view.render(); + this.viewManager.switchView(view); this.navigate('/special'); }, showSpecialPage: function(page) { @@ -159,11 +184,9 @@ return; } var view = new viewType({ - el: $('#app'), model: new Models.GenericSpecialPageModel({ page: page }) }); - view.model.setApp(this); - view.model.fetch(); + this.viewManager.switchView(view); this.navigate('/special/' + page); }, stripQuery: function(url) {
--- a/static/js/wikked/models.js Tue Feb 12 20:52:58 2013 -0800 +++ b/static/js/wikked/models.js Tue Feb 12 20:55:13 2013 -0800 @@ -48,7 +48,7 @@ }); }, doSearch: function(form) { - this.app.navigate('/search/' + $(form.q).val(), { trigger: true }); + this.navigate('/search/' + $(form.q).val(), { trigger: true }); }, _onChangePath: function(path) { this.set({ @@ -81,7 +81,7 @@ defaults: function() { return { url_extras: [ - { name: 'Home', url: '/' }, + { name: 'Home', url: '/#/' }, { name: 'Special Pages', url: '/#/special' } ] }; @@ -106,7 +106,7 @@ var $model = this; $.post('/api/user/login', $(form).serialize()) .success(function() { - $model.app.navigate('/', { trigger: true }); + $model.navigate('/', { trigger: true }); }) .error(function() { $model.set('has_error', true); @@ -227,16 +227,17 @@ this.footer.addExtraUrl('JSON', function() { return '/api/read/' + model.id; }); }, _onChange: function() { - // Handle redirects. - if (this.getMeta('redirect') && !this.get('no_redirect')) { + if (this.getMeta('redirect') && + !this.get('no_redirect') && + !this.get('redirected_from')) { + // Handle redirects. var oldPath = this.get('path'); - this.set('path', this.getMeta('redirect')); - this.fetch({ - success: function(model) { - model.set('redirected_from', oldPath); - } + this.set({ + 'path': this.getMeta('redirect'), + 'redirected_from': oldPath }); - this.app.navigate('/read/' + this.getMeta('redirect'), { replace: true }); + this.fetch(); + this.navigate('/read/' + this.getMeta('redirect'), { replace: true, trigger: false }); } } }); @@ -252,9 +253,10 @@ doEdit: function(form) { var $model = this; var path = this.get('path'); + this.navigate('/read/' + path, { trigger: true }); $.post('/api/edit/' + path, $(form).serialize()) .success(function(data) { - $model.app.navigate('/read/' + path, { trigger: true }); + $model.navigate('/read/' + path, { trigger: true }); }) .error(function() { alert('Error saving page...'); @@ -268,7 +270,7 @@ doDiff: function(form) { var rev1 = $('input[name=rev1]:checked', form).val(); var rev2 = $('input[name=rev2]:checked', form).val(); - this.app.navigate('/diff/r/' + this.get('path') + '/' + rev1 + '/' + rev2, { trigger: true }); + this.navigate('/diff/r/' + this.get('path') + '/' + rev1 + '/' + rev2, { trigger: true }); }, _onChangePath: function(path) { PageHistoryModel.__super__._onChangePath.apply(this, arguments); @@ -299,6 +301,17 @@ this._onChangeRev(this.get('rev')); return this; }, + doRevert: function(form) { + var $model = this; + var path = this.get('path'); + $.post('/api/revert/' + path, $(form).serialize()) + .success(function(data) { + $model.navigate('/read/' + path, { trigger: true }); + }) + .error(function() { + alert('Error reverting page...'); + }); + }, _onChangeRev: function(rev) { var setmap = { disp_rev: rev }; if (rev.match(/[a-f0-9]{40}/)) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/js/wikked/view-manager.js Tue Feb 12 20:55:13 2013 -0800 @@ -0,0 +1,22 @@ +/** + * An object responsible for opening, switching, and closing + * down view. + */ +define([ + ], + function() { + + var ViewManager = { + _currentView: false, + switchView: function(view, autoFetch) { + if (this._currentView) { + this._currentView.remove(); + this._currentView = false; + } + + if (view) { + this._currentView = view; + } + } + }; +});
--- a/static/js/wikked/views.js Tue Feb 12 20:52:58 2013 -0800 +++ b/static/js/wikked/views.js Tue Feb 12 20:55:13 2013 -0800 @@ -50,6 +50,16 @@ } return this; }, + dispose: function() { + this.remove(); + this.unbind(); + if (this.model) { + this.model.unbind(); + } + if (this._onDispose) { + this._onDispose(); + } + }, render: function(view) { if (this.template !== undefined) { this.renderTemplate(this.template); @@ -214,8 +224,29 @@ else jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); }); + // If we've already rendered the content, and we need to display + // a warning, do so now. + if (this.model.get('content')) { + this._showPageStateWarning(); + } }, - events: { + _showPageStateWarning: function() { + if (this._pageState === undefined) + return; + + var state = this._pageState.get('state'); + if (state == 'new' || state == 'modified') { + var warning = $(this.warningTemplate(this._pageState.toJSON())); + warning.css('display', 'none'); + warning.prependTo($('#app .page')); + warning.slideDown(); + $('.dismiss', warning).click(function() { + warning.slideUp(); + return false; + }); + } + }, + /*events: { "click .wiki-link": "_navigateLink" }, _navigateLink: function(e) { @@ -225,21 +256,17 @@ this.model.fetch(); e.preventDefault(); return false; - }, + },*/ _checkPageState: function() { - var stateTpl = this.warningTemplate; + var $view = this; var stateModel = new Models.PageStateModel({ path: this.model.get('path') }); stateModel.fetch({ success: function(model, response, options) { - if (model.get('state') == 'new' || model.get('state') == 'modified') { - var warning = $(stateTpl(model.toJSON())); - warning.css('display', 'none'); - warning.prependTo($('#app .page')); - warning.slideDown(); - $('.dismiss', warning).click(function() { - warning.slideUp(); - return false; - }); + $view._pageState = model; + // If we've already rendered the content, display + // the warning, if any, now. + if ($view.model && $view.model.get('content')) { + $view._showPageStateWarning(); } } }); @@ -328,7 +355,7 @@ }, _submitDiffPage: function(e) { e.preventDefault(); - this.model.doDiff(this); + this.model.doDiff(e.currentTarget); return false; }, titleFormat: function(title) { @@ -340,6 +367,14 @@ defaultTemplateSource: tplRevisionPage, titleFormat: function(title) { return title + ' [' + this.model.get('rev') + ']'; + }, + events: { + "submit #page-revert": "_submitPageRevert" + }, + _submitPageRevert: function(e) { + e.preventDefault(); + this.model.doRevert(e.currentTarget); + return false; } });
--- a/static/tpl/revision-page.html Tue Feb 12 20:52:58 2013 -0800 +++ b/static/tpl/revision-page.html Tue Feb 12 20:55:13 2013 -0800 @@ -2,5 +2,10 @@ <div class="page span12"> <h1>{{meta.title}} <span class="decorator">Revision: <span class="rev_id">{{disp_rev}}</span></span></h1> <pre><code>{{text}}</code></pre> + <form id="page-revert" class="page-revert"> + <input type="hidden" name="rev" value="{{rev}}"/> + <button type="submit" class="btn">Revert</button> + <small>Revert the page to this revision</small> + </form> </div> </article>