A JavaScript library for building Single Page Applications (SPA) using the Model-View-Controller (MVC) design pattern.
To add MVC Router to your application:
npm install mvc-router-spa --save
MVC is a proven technology that has withstood the test of time. Applications built using this pattern tend to be simpler and more maintainable.
MVC is a common and familiar pattern used by many frameworks for building both web and native applications. If you are an iOS developer, you may already be familiar with MVC in Cocoa. Most UI frameworks — including ASP.NET Core, Spring MVC (often used via Spring Boot), Ruby on Rails, and Django — are built around MVC or a close variation of it. This is not accidental. Across mobile and web platforms, MVC emerged as a practical way to separate concerns and structure user-interface code.
When using React to implement views, MVC supports one-way data flow. Other technologies such as Flux/Redux also implement one-way data flow, but MVC requires far less boilerplate code. MVC also offers better separation of concerns, which is a good idea to adopt even in Flux/Redux apps.
A typical MVC application has the following parts:
- The Application object holds application state and sets up routes.
- Model objects encapsulate application data.
- View objects display application data.
- Controller objects handle data retrieval and persistence, mediates between model and view layers, and handles control flow.
The application object is a singleton object that inherits from MvcRouter.App and holds the application state. When your site loads, the constructor of your Application object executes before anything else.
The Application object sets up the routes of the application by mapping URL paths to Controller classes, then calls the load method of the base class:
class MyApp extends MvcRouter.App {
constructor() {
super();
const router = this.getRouter();
router.addRoute("/", HomeController);
router.addRoute("/product", ProductController);
router.addRoute("/account", AccountController);
this.load();
}
}Note that the second parameter to the addRoute() method is not an instance of a controller, but the controller class.
Instead of mapping each path separately you can also supply your own path-to-controller resolver by calling router.setCustomResolver().
If the URL contains query parameters it will be made available to your controller. No additional setup is necessary to receive query parameters.
You can also pass values as part of the URL path. So for example, instead of /product?id=123 the URL can be in the form /product/123. In this case the route must be defined as follows:
router.addRoute("/product/:id", ProductController);Note that the parameter name is prefixed with a colon.
In either case the parameter name and values are made available to the controller in the same way, so your controller need not know whether the value was passed on the path or as a query parameter.
Model objects hold the data of your application. Objects in this layer are kept independent of the other layers.
MVC Router does not care what your model objects look like. However your choice of View technology may dictate the type and structure of your model objects. Most View technologies, such as React.js, allow you to use Plain Old JavaScript Objects (POJO) in your model layer.
The View layer is only concerned with presentation.
MVC Router is independent of the technology used to implement your View layer. You could use React.js or Handlebars templates, for example.
Controllers fetch data to be displayed, making ajax calls if necessary, render the appropriate View objects, and set up event handlers in order to handle user actions. In the course of handling user actions, controllers may cause the application to navigate to other pages. Controllers also handle modifying and persisting application state, making ajax calls if necessary.
Controller objects inherit from MvcRouter.Controller. Each URL path in your app will have a corresponding controller. MVC Router creates a new instance of the controller class each time a path is loaded. Because of this, data that must live longer than the page must be stored in your Application object, not in the controller.
In the simple example below, the controller renders the page using jQuery. (You may want to use more modern libraries such as React.js instead, to implement the View layer.)
class ProductController extends MvcRouter.Controller {
constructor(private app: MyApp) {
super();
}
public load(params: MvcRouter.QueryParams) {
super.load(params);
const productPageTemplate = $("#product-page-template").html();
$(this.app.getAppBody()).empty().html(productPageTemplate);
}
}You can have a hierarchy of controllers, with parts that are common to all pages rendered by the base controller.
Your application navigates from one page to the next in one of two ways:
-
The user clicks on a link. Links to other parts of the application must have the "appnav" class, like this:
<a href="/products" class="appnav">Products</a>Ifappnavis specified then MVC Router will use HTML5 pushState API to perform the navigation. If theappnavclass is not specified then clicking on the link will cause the entire site to reload. -
You can also programmatically navigate to another page by calling
this.app.navigate('/new/path')in your controller. Internally, MVC Router uses HTML5 pushState API to perform the navigation.
In React Router, parts of a page are implemented as components that encapsulate both presentation and behavior. Behavior is distributed across the component tree. While components compose structurally, coordinating behavior across them requires explicit mechanisms such as prop drilling, context, or shared state stores.
In MVC the entire page is handled by a single controller. Even though multiple components are used to render the page, the behavior is in a single controller, with functionality common to multiple pages being handled by a base controller. Communication between parts of the page is natural. Accessing functionality owned by an outer component is as simple as calling a base class method. Listening to events in an outer component is as simple as overriding a base class method.
React Router can swap just a nested section of the page on navigation, whereas MVC Router renders the full page on each route change. In practice this is rarely a disadvantage — in traditional web applications, a URL change implies a full page transition, which matches users' expectations.
The main tradeoff is ecosystem: React Router has a larger community and most third-party libraries assume its conventions. MVC Router's strength is simplicity — not just the framework itself, which is just 500 lines of code, but the applications built with it, which tend to be far simpler than typical React apps due to the clear separation of concerns and centralized controller logic.
For a real-world application written using MVC Router see eureka!
The included demo application uses React.js in the View layer, and has a master page that includes a top bar and left panel, dropdown menu, and dialogs.
