Browse Source

boilerplate for routes

Rafał Pitoń 9 years ago
parent
commit
7c89dc9aa6

+ 5 - 2
misago/frontend/gulpfile.js

@@ -38,11 +38,14 @@ gulp.task('lint', function() {
 gulp.task('misagojs', ['lint'], function() {
 gulp.task('misagojs', ['lint'], function() {
   return gulp.src([
   return gulp.src([
       'misago/app.js',
       'misago/app.js',
-      'misago/models/*.js',
-      'misago/components/**/*.js',
+
       'misago/services/*.js',
       'misago/services/*.js',
+      'misago/models/**/*.js',
+      'misago/routes/**/*.js',
+      'misago/components/**/*.js',
       'misago/templates/**/*.js',
       'misago/templates/**/*.js',
       'misago/utils/**/*.js',
       'misago/utils/**/*.js',
+
       'misago/urls.js',
       'misago/urls.js',
     ])
     ])
     .pipe(concat('misago.js'))
     .pipe(concat('misago.js'))

+ 0 - 18
misago/frontend/misago/components/index.js

@@ -1,18 +0,0 @@
-(function (Misago) {
-  'use strict';
-
-  var self = {
-    controller: function() {
-      var _ = self.container;
-      _.setTitle(_.settings.forum_index_title);
-    },
-    view: function() {
-      return m('.container', [
-        m('h1', 'Forum index page!'),
-        m('p', 'Lorem ipsum dolor met sit amet elit.'),
-        m('p', 'Sequar elit dolor nihi putto.')
-      ]);
-    }
-  };
-  Misago.IndexPage = self;
-}(Misago.prototype));

+ 0 - 78
misago/frontend/misago/components/legal.js

@@ -1,78 +0,0 @@
-(function (Misago) {
-  'use strict';
-
-  var legalPageFactory = function(typeName, defaultTitle) {
-    var dashedTypeName = typeName.replace(/_/g, '-');
-
-    var self = {
-      isDestroyed: true,
-      controller: function() {
-        var _ = self.container;
-        self.isDestroyed = false;
-
-        if (Misago.get(_.settings, typeName + '_link')) {
-          window.location = Misago.get(_.settings, typeName + '_link');
-        } else {
-          self.vm.init(_);
-        }
-
-        return {
-          onunload: function() {
-            self.isDestroyed = true;
-          }
-        };
-      },
-      vm: {
-        isBusy: false,
-        isReady: false,
-        content: null,
-
-        init: function(_) {
-
-          var vm = this;
-          if (vm.isReady) {
-            _.setTitle(vm.title);
-          } else {
-            _.setTitle();
-
-            if (!vm.isBusy) {
-              vm.isBusy = true;
-
-              _.api.one('legal-pages', dashedTypeName).then(function(data) {
-                vm.title = data.title || defaultTitle;
-                vm.body = data.body;
-                vm.isBusy = false;
-                vm.isReady = true;
-
-                if (!self.isDestroyed) {
-                  _.setTitle(vm.title);
-                  m.redraw();
-                }
-              });
-            }
-          }
-        }
-      },
-      view: function() {
-        var _ = this.container;
-
-        if (this.vm.isReady) {
-          return m('.page.page-legal.page-legal-' + dashedTypeName, [
-            _.component(Misago.PageHeader, {title: this.vm.title}),
-            m('.container',
-              m.trust(this.vm.body)
-            )
-          ]);
-        } else {
-          return _.component(Misago.LoadingPage);
-        }
-      }
-    };
-    return self;
-  };
-
-  Misago.TermsOfServicePage = legalPageFactory(
-    'terms_of_service', gettext('Terms of service'));
-  Misago.PrivacyPolicyPage = legalPageFactory(
-    'privacy_policy', gettext('Privacy policy'));
-}(Misago.prototype));

+ 40 - 0
misago/frontend/misago/routes/errors.js

@@ -0,0 +1,40 @@
+(function (Misago) {
+  'use strict';
+
+  Misago.Error403Route = Misago.route({
+    controller: function() {
+      this.container.setTitle(gettext('Page not available'));
+    },
+    error: null,
+    view: function(ctrl) {
+      return m('.container', [
+        m('h1', 'Error 403!'),
+        m('p.lead', this.error || 'No perm to see this page')
+      ]);
+    }
+  });
+
+  Misago.Error404Route = Misago.route({
+    controller: function() {
+      this.container.setTitle(gettext('Page not found'));
+    },
+    view: function(ctrl) {
+      return m('.container', [
+        m('h1', 'Error 404!'),
+        m('p.lead', 'Requested page could not be found.')
+      ]);
+    }
+  });
+
+  Misago.Error500Route = Misago.route({
+    controller: function() {
+      this.container.setTitle(gettext('Application error occured'));
+    },
+    view: function(ctrl) {
+      return m('.container', [
+        m('h1', 'Error 500!'),
+        m('p.lead', 'Application has derped.')
+      ]);
+    }
+  });
+}(Misago.prototype));

+ 29 - 0
misago/frontend/misago/routes/index.js

@@ -0,0 +1,29 @@
+(function (Misago) {
+  'use strict';
+
+  Misago.IndexRoute = Misago.route({
+    controller: function() {
+      var _ = this.container;
+      document.title = _.settings.forum_index_title || _.settings.forum_name;
+
+      var count = m.prop(0);
+
+      return {
+        count: count,
+        increment: function() {
+          console.log('increment()');
+          count(count() + 1);
+        }
+      };
+    },
+    view: function(ctrl) {
+      return m('.container', [
+        m('h1', ['Count: ', m('strong', ctrl.count())]),
+        m('p', 'Clicky click button to increase count!.'),
+        m('p',
+          m('button.btn.btn-primary', {onclick: ctrl.increment}, 'Clicky clicky!')
+        )
+      ]);
+    }
+  });
+}(Misago.prototype));

+ 62 - 0
misago/frontend/misago/routes/legal.js

@@ -0,0 +1,62 @@
+(function (Misago) {
+  'use strict';
+
+  var legalPageFactory = function(typeName, defaultTitle) {
+    var dashedTypeName = typeName.replace(/_/g, '-');
+
+    return Misago.route({
+      controller: function() {
+        var _ = this.container;
+
+        if (Misago.get(_.settings, typeName + '_link')) {
+          window.location = Misago.get(_.settings, typeName + '_link');
+        } else {
+          this.vm.init(this, _);
+        }
+      },
+      vm: {
+        isReady: false,
+        isBusy: false,
+        init: function(component, _) {
+          if (this.isReady) {
+            _.setTitle(this.title);
+          } else {
+            _.setTitle();
+            if (!this.isBusy) {
+              this.isBusy = true;
+              return _.api.one('legal-pages', dashedTypeName);
+            }
+          }
+        },
+        ondata: function(data, component, _) {
+          m.startComputation();
+
+          this.title = data.title || defaultTitle;
+          this.body = data.body;
+          this.isReady = true;
+
+          m.endComputation();
+
+          if (component.isActive) {
+            _.setTitle(data.title);
+          }
+        }
+      },
+      view: function() {
+        var _ = this.container;
+
+        return m('.page.page-legal.page-legal-' + dashedTypeName, [
+          _.component(Misago.PageHeader, {title: this.vm.title}),
+          m('.container',
+            m.trust(this.vm.body)
+          )
+        ]);
+      }
+    });
+  };
+
+  Misago.TermsOfServiceRoute = legalPageFactory(
+    'terms_of_service', gettext('Terms of service'));
+  Misago.PrivacyPolicyRoute = legalPageFactory(
+    'privacy_policy', gettext('Privacy policy'));
+}(Misago.prototype));

+ 9 - 4
misago/frontend/misago/services/api.js

@@ -7,7 +7,7 @@
     this.csrfToken = Misago.get(document.cookie.match(cookie_regex), 0).split('=')[1];
     this.csrfToken = Misago.get(document.cookie.match(cookie_regex), 0).split('=')[1];
 
 
     this.ajax = function(method, url, data, progress) {
     this.ajax = function(method, url, data, progress) {
-      var deferred = m.deferred();
+      var promise = m.deferred();
 
 
       var ajax_settings = {
       var ajax_settings = {
         url: url,
         url: url,
@@ -20,10 +20,15 @@
         dataType: 'json',
         dataType: 'json',
 
 
         success: function(data) {
         success: function(data) {
-          deferred.resolve(data);
+          promise.resolve(data);
         },
         },
         error: function(jqXHR) {
         error: function(jqXHR) {
-          deferred.reject(jqXHR);
+          var rejection = jqXHR.responseJSON || {};
+
+          rejection.status = jqXHR.status;
+          rejection.statusText = jqXHR.statusText;
+
+          promise.reject(rejection);
         }
         }
       };
       };
 
 
@@ -32,7 +37,7 @@
       }
       }
 
 
       $.ajax(ajax_settings);
       $.ajax(ajax_settings);
-      return deferred.promise;
+      return promise.promise;
     };
     };
 
 
     this.get = function(url) {
     this.get = function(url) {

+ 0 - 0
misago/frontend/misago/services/component.js → misago/frontend/misago/services/component-factory.js


+ 85 - 0
misago/frontend/misago/services/route.js

@@ -0,0 +1,85 @@
+(function (Misago) {
+  'use strict';
+
+  var noop = function() {};
+
+  Misago.route = function(component) {
+    /*
+      Boilerplate for Misago top-level components
+    */
+
+    // Component state
+    component.isActive = true;
+
+    // Wrap controller to store lifecycle methods
+    var __controller = component.controller || noop;
+    component.controller = function() {
+      component.isActive = true;
+
+      var controller = __controller.apply(component, arguments) || {};
+
+      // wrap onunload for lifestate
+      var __onunload = controller.onunload || noop;
+      controller.onunload = function() {
+        __onunload.apply(component, arguments);
+        controller.isActive = false;
+      };
+
+      return controller;
+    };
+
+    // Add state callbacks to View-Model
+    if (component.vm && component.vm.init) {
+      // wrap vm.init in promise handler
+      component.vm._promise = null;
+
+      var __init = component.vm.init;
+      component.vm.init = function() {
+        var initArgs = arguments;
+        var promise = __init.apply(component.vm, initArgs);
+
+        if (promise) {
+          component.vm._promise = promise;
+          promise.then(function() {
+            if (component.vm._promise === promise && component.vm.ondata) {
+              var finalArgs = [];
+              for (var i = 0; i < arguments.length; i++) {
+                finalArgs.push(arguments[i]);
+              }
+              for (var f = 0; f < initArgs.length; f++) {
+                finalArgs.push(initArgs[f]);
+              }
+
+              component.vm.ondata.apply(component.vm, finalArgs);
+            }
+          }, function(error) {
+            if (component.vm._promise === promise) {
+              component.container.router.errorPage(error);
+            }
+          });
+        }
+      };
+
+      // setup default loading view
+      if (!component.loading) {
+        component.loading = function () {
+          var _ = this.container;
+          return m('.page.page-loading',
+            _.component(Misago.Loader)
+          );
+        };
+      }
+
+      var __view = component.view;
+      component.view = function() {
+        if (component.vm.isReady) {
+          return __view.apply(component, arguments);
+        } else {
+          return component.loading.apply(component, arguments);
+        }
+      };
+    }
+
+    return component;
+  };
+}(Misago.prototype));

+ 40 - 3
misago/frontend/misago/services/router.js

@@ -12,15 +12,19 @@
     this.urls = {};
     this.urls = {};
     this.reverses = {};
     this.reverses = {};
 
 
+    var routedComponent = function(component) {
+      component.container = _;
+      return component;
+    };
+
     var populatePatterns = function(urlconf) {
     var populatePatterns = function(urlconf) {
       urlconf.patterns().forEach(function(url) {
       urlconf.patterns().forEach(function(url) {
         // set service container on component
         // set service container on component
-        url.component.container = _;
 
 
         var finalPattern = self.baseUrl + url.pattern;
         var finalPattern = self.baseUrl + url.pattern;
         finalPattern = finalPattern.replace('//', '/');
         finalPattern = finalPattern.replace('//', '/');
 
 
-        self.urls[finalPattern] = url.component;
+        self.urls[finalPattern] = routedComponent(url.component);
         self.reverses[url.name] = finalPattern;
         self.reverses[url.name] = finalPattern;
       });
       });
     };
     };
@@ -70,7 +74,6 @@
       if (url.substr(0, this.baseUrl.length) !== this.baseUrl) { return; }
       if (url.substr(0, this.baseUrl.length) !== this.baseUrl) { return; }
 
 
       // Is link to media/static/avatar server?
       // Is link to media/static/avatar server?
-      console.log(staticUrl);
       if (url.substr(0, staticUrl.length) === staticUrl) { return; }
       if (url.substr(0, staticUrl.length) === staticUrl) { return; }
 
 
       if (url.substr(0, mediaUrl.length) === mediaUrl) { return; }
       if (url.substr(0, mediaUrl.length) === mediaUrl) { return; }
@@ -107,6 +110,40 @@
 
 
     this.staticUrl = prefixUrl(staticUrl);
     this.staticUrl = prefixUrl(staticUrl);
     this.mediaUrl = prefixUrl(mediaUrl);
     this.mediaUrl = prefixUrl(mediaUrl);
+
+    // Errors
+    this.error403 = function(error) {
+      if (error.ban) {
+        var component = routedComponent(Misago.ErrorBannedRoute);
+      } else {
+        var component = routedComponent(Misago.Error403Route);
+      }
+
+      component.vm = error;
+      m.mount(this.fixture, component);
+    };
+
+    this.error404 = function(error) {
+      m.mount(this.fixture, routedComponent(Misago.Error404Route));
+    };
+
+    this.error500 = function() {
+      m.mount(this.fixture, routedComponent(Misago.Error500Route));
+    };
+
+    this.errorPage = function(error) {
+      if (error.status === 500) {
+        this.error500();
+      }
+
+      if (error.status === 404) {
+        this.error404();
+      }
+
+      if (error.status === 403) {
+        this.error403(error);
+      }
+    };
   };
   };
 
 
   Misago.RouterFactory = function(_) {
   Misago.RouterFactory = function(_) {

+ 3 - 3
misago/frontend/misago/urls.js

@@ -2,11 +2,11 @@
   'use strict';
   'use strict';
 
 
   var urls = new UrlConf();
   var urls = new UrlConf();
-  urls.url('/', Misago.IndexPage, 'index');
+  urls.url('/', Misago.IndexRoute, 'index');
 
 
   // Legal pages
   // Legal pages
-  urls.url('/terms-of-service/', Misago.TermsOfServicePage, 'terms_of_service');
-  urls.url('/privacy-policy/', Misago.PrivacyPolicyPage, 'privacy_policy');
+  urls.url('/terms-of-service/', Misago.TermsOfServiceRoute, 'terms_of_service');
+  urls.url('/privacy-policy/', Misago.PrivacyPolicyRoute, 'privacy_policy');
 
 
   Misago.urls = urls;
   Misago.urls = urls;
 } (Misago.prototype, Misago.prototype.UrlConf));
 } (Misago.prototype, Misago.prototype.UrlConf));

+ 7 - 3
misago/frontend/misago/utils/urlconf.js

@@ -1,8 +1,12 @@
 (function (Misago) {
 (function (Misago) {
   'use strict';
   'use strict';
 
 
-  Misago.UrlConfInvalidComponentError = function() {
-    this.message = 'component argument should be array or object';
+  Misago.UrlConfInvalidComponentError = function(name) {
+    this.message = "route's " + name + " component should be an array or object";
+
+    this.toString = function() {
+      return this.message;
+    };
   };
   };
 
 
   Misago.UrlConf = function() {
   Misago.UrlConf = function() {
@@ -27,7 +31,7 @@
 
 
     this.url = function(pattern, component, name) {
     this.url = function(pattern, component, name) {
       if (typeof component !== 'object') {
       if (typeof component !== 'object') {
-        throw new Misago.UrlConfInvalidComponentError();
+        throw new Misago.UrlConfInvalidComponentError(name);
       }
       }
 
 
       if (pattern === '') {
       if (pattern === '') {