DEV Community

Cover image for SPAC: Controller Implementation
Sebastian
Sebastian

Posted on • Updated on

SPAC: Controller Implementation

SPAC is a custom JavaScript framework for client-side, single-page web applications. It stands for "Stateful Pages, Actions and Components". Its design goal is to provide robust and simple entities that help you to structure apps. Pages and components provide the HTML, JavaScript functions and UI interactions. Actions govern external API calls. You define these entities in plain JS, load up the central controller, and your app is ready to be served. Read the development journey of SPAC in my series: https://admantium.com/category/spac-framework/.

In this article, I explain the central controller object. We cover the design goals, then discuss its features and implementation.

This article originally appeared at my blog.

Designing the Controller

The controller is the central entity of the framework. Its design encompasses the following goals:

  • Self initializing, single point of entry: The controller is the only JavaScript module that you need to load with a <script> tag from your index.html. From thereon, it handles the loading and unloading of all other JavaScript functions that are needed for the application.
  • Rendering Pages: The controller uses page classes for delivering the HTML to the clients. Pages expose a display() method that will render HTML and attach the output to the DOM. Pages themselves consist of static HTML and components that are added via mount().
  • Handling Actions: Actions are client-side external interactions like calling a backend or an external API. An action receives an args object and a updateState callback that receives the result of the action.

Now, let’s detail how the controller works.

Self-Initialization

The controller is self-initializing. You don't need any configuration file. By following the convention over configuration paradigm, you place pages, components and actions at the appropriate directories. The controller starts, parses the directories, initializes the objects. If it encounters any errors, it complains, but continues to load the rest of the application state.

To initialize your application, you need to do these steps:

  1. Create a script for loading the controller, e.g. in js/app.js
  // js/app.js
import { Controller } from 'spacjs';

const controller = new Controller();

controller.init();
Enter fullscreen mode Exit fullscreen mode
  1. Include this script in your index.html
<script src="js/controller.js" type="module"></script>
Enter fullscreen mode Exit fullscreen mode

That is all.

Render Pages

During the setup phase, the controller creates a map of all pages. Consider that you have the following page files.

.
└── pages
    ├── IndexPage.js
    ├── SearchApiElementsPage.js
    └── SearchApiSpecPage.js
Enter fullscreen mode Exit fullscreen mode

From this structure, the following map will be created:

const pages = {
  Index: {
    route: '/index',
    obj: IndexPage()
  },
  SearchApiElements: {
    route: '/search_api_elements',
    obj: SearchApiElementsPage()
  },
  SearchApiSpec: {
    route: '/search_api_spec',
    obj: SearchApiSpecPage()
  }
}
Enter fullscreen mode Exit fullscreen mode

At the moment, this map is used only to resolve routes and creating page instances when they should be rendered. An ongoing feature is to simplify imports, so instead of importing components directly in page declarations, the page object could request components from the controller.

Handling Actions

Actions involve application-external backends or APIs. They are defined separately in action files. These actions are initialized by the controller and result in a similar map object.

const actions = {
  loadAPI: {
    obj: LoadApiAction()
  }
}
Enter fullscreen mode Exit fullscreen mode

The controller exposes the actions object. Then, any page or component can execute an action with this syntax:

this.controller.action('loadAPI', {state: this.getState()}
Enter fullscreen mode Exit fullscreen mode

This is all! The action object will access the state of the calling component, execute the defined application-external call, and pass the results as the new state object to the receiving component(s). Decoupling the participating components is a challenging aspect, a future article will explain how to works.

Conclusion

My custom JavaScript framework is a generic approach to structure client-side applications that need to provide complex UI interactions. This article covered the framework's heart: The controller. I explained the main motivation of the controller - a central, self-initializing JavaScript module. It loads all pages and actions of the app, then serves the Index page. From there on, it handles routing and exposes actions that are triggered by pages. The next article details the self-initialization feature.

Top comments (0)