This section of the documentation describes in detail the different classes used in Erstwhile, and goes into detail about how to extend them within your applications.
This section of the documentation describes in detail the different classes used in Erstwhile, and goes into detail about how to extend them within your applications.
ErstwhileApp is the base class that Erstwhile applications built off of. The bootstrap process creates an instance of your subclass of ErstwhileApp, and then makes it available as a variable called $App.
There are a variety of methods in ErstwhileApp that you can use to access your components, models, etc, and also some others to do things like redirect to other pages, or set up functions for deferred execution after the DOM renders. In addition to these, there are a number of undocumented methods that can be used to change the behavior of your application. These won’t be mentioned here because they may change in the future.
ErstwhileComponent is the base class for each of the visual (and pseudo-visual) elements in Erstwhile. ErstwhileControl, ErstwhileForm, and ErstwhileLayout are all subclasses of ErstwhileComponent, and each of your custom components should also be a subclass of one of these.
Technically, you don’t need to override any methods in ErstwhileComponent when creating your own components; the default functionality will render the component’s component.ejs file, and if you don’t need anything more than that you’re good to go. You can pick and choose which of these methods to override as you need to change how your component works.
Some components contain other Erstwhile components (or just plain HTML) that you want to render inside your component. If your component needs this to happen, you can override the isContainer() method and insert a special element <innerContent /> in your component.ejs file to indicate where you would like the contained components/HTML placed.
When rendering your component, the ErML engine will, by default, create a <div> element with the component’s id attribute and all of the attributes of your component. If you specify a class attribute on your component, that same class attribute would show up on the <div> when rendered, making it a convenient pass through. The ErML engine will then render your component’s .ejs file and insert that into the <div>.
You have fine-grained control to change this, though.
Overriding getTag(), for instance, lets your component specify a tag other than <div> to use. Overriding prepareAttributes() will let you redefine the behavior for how your component’s attributes turn into HTML attributes.
Moving on to more advanced behavior, you can override the initialize() method to specify Javascript that should run post-DOM render. This will let you instantiate other libraries if needed and direct them to elements in your new DOM.
Finally, it’s sometimes necessary to do something really crazy and implement your own XML structure within your component so consumers can specify different types of data structures not available via simple attributes. If this is you, you can override the getHTML() method. This method has the inner content (as a JSONed XML document) as its argument, and you can freely iterate through that document to fine-tune component behavior.
When consuming your components in views, users can specify two different types of attributes:
value="somevalue"value="@.page.value"From your component’s perspective, both of these are exist in the component’s args member, and can have their present value seen by getProperty(argName). Scoped variables have some bonuses though.
First of all, if the scoped variable is a function, and the attribute it is assigned to is an on* attribute, the scoped function becomes an event handler.
Secondly, if the scoped variable is not a function, your component will receive a callback to receiveUpdate(key, value) every time the scoped variable changes. By overriding this method you will be able to tell your component how to handle that new value.
ErstwhileForm is a simple component that basically just exists to provide some convenience functions for dealing with ErstwhileControl subclass components.
It’s actually implemented as an HTML <form> element. Typically you will give it an explicit id attribute to make it easier to reference in the within your code via $App.getComponent().
It is unlikely you will need to subclass it.
ErstwhileControl is the base class for form control components in Erstwhile. These exist mostly to gather input from users.
Following the HTML convention it is recommended that each of these have a name attribute indicating the name to be used within the context of the entity their data represents.
Note: It is important to remember that the getValue() method returns an object rather than a single value, with (by default) the name attribute defining the key and the control’s value as the value. This is to enable more complicated custom controls that may need to return multiple values at once, like a date control that has each of the date parts in separate fields with names like yourname_month, yourname_date, and yourname_year.
ErstwhileLayout is the base class for layouts. In Erstwhile, layouts are essentially the “frame” of the page, the part of the page that stays (generally) consistent as the user moves around the application. This can include the header, navigation, footer, and similar elements to that.
Layouts are implmented just like other ErstwhileComponents, but typically contain a lot of HTML boilerplate due to the nature how web pages are constructed. We call these “Fat Layouts;” Erstwhile lets you place a lot of this non-elegant code in one place and hide it behind a simple component with well-defined integration points in attributes to access its functionality.
For example, the Mazer theme has a ErstwhileLayout component called <FullLayout> that isn’t very pretty:
<%
function renderSidebarMenuItem( menuItem, submenu = false ) {
%>
<li class="<%= (submenu ? "submenu-item" : "sidebar-item") %> <%= (menuItem.id && menuItem.id == args.sidebarMenuActive ? "active" : "") %> <%= (menuItem.children ? "has-sub" : "") %>" <%= (menuItem.id ? `id="sidebar-menu-${menuItem.id}"` : "") %>>
<a href="<%= (menuItem.link ? menuItem.link : "#") %>" class="<%= (submenu ? "submenu-link" : "sidebar-link") %>">
<% if(menuItem.biIcon) { %><i class="<%= `bi bi-${menuItem.biIcon}` %>"></i><% } %>
<span><%= (menuItem.label ? menuItem.label : "Item") %></span>
</a>
<% if(menuItem.children) { %>
<ul class="submenu">
<% for(let j in menuItem.children) {
renderSidebarMenuItem(menuItem.children[j], true);
} %>
</ul>
<% } %>
</li>
<%
}
%>
<div id="app">
<div id="sidebar">
<div class="sidebar-wrapper active">
<div class="sidebar-header position-relative">
<div class="d-flex justify-content-between align-items-center">
<div class="logo">
<a href="/"
><img
src="<%- (args.logo ? args.logo : "https://placeholder.pics/svg/500x200") %>"
alt="Logo"
srcset=""
/></a>
</div>
<div class="sidebar-toggler x">
<a href="#" class="sidebar-hide d-xl-none d-block"
><i class="bi bi-x bi-middle"></i
></a>
</div>
</div>
</div>
<div class="sidebar-menu">
<ul class="menu">
<% if(args.sidebarMenu && args.sidebarMenu.length > 0) { %>
<% for(let i in args.sidebarMenu) {
if(args.sidebarMenu[i].label) { %><li class="sidebar-title"><%= args.sidebarMenu[i].label %></li><% } %>
<%
for(let j = 0; j < args.sidebarMenu[i].items.length; j++) {
renderSidebarMenuItem(args.sidebarMenu[i].items[j]);
}
%>
<% }} %>
</ul>
</div>
</div>
</div>
<div id="main">
<header class="mb-3">
<a href="#" class="burger-btn d-block d-xl-none">
<i class="bi bi-justify fs-3"></i>
</a>
</header>
<div class="page-heading">
<div class="page-title">
<div class="row">
<div class="col-12 col-md-6 order-md-1 order-last">
<h3 id="page-title"><%- args.pageTitle %></h3>
<p id="page-intro" class="text-subtitle text-muted">
<%- args.pageIntro %>
</p>
</div>
<div class="col-12 col-md-6 order-md-2 order-first">
<nav
aria-label="breadcrumb"
class="breadcrumb-header float-start float-lg-end"
id="breadcrumb-container"
><% if(args.breadcrumbs) { %>
<ol class="breadcrumb">
<% for(let i in args.breadcrumbs) { %>
<li class="breadcrumb-item <%= (i == args.breadcrumbs.length - 1 ? 'active' : "") %>" <%= (i == args.breadcrumbs.length - 1 ? 'aria-current="page"' : "") %>>
<% if(args.breadcrumbs[i].link) { %>
<a href="<%- args.breadcrumbs[i].link %>"><%- args.breadcrumbs[i].label %></a>
<% } else { %>
<%- args.breadcrumbs[i].label %>
<% } %>
</li>
<% } %>
</ol>
<% } %>
</nav>
</div>
</div>
</div>
<pagecontent />
</div>
<footer>
<div class="footer clearfix mb-0 text-muted">
<div class="float-start">
<% if(args.copyrightYear || args.copyrightName) {%>
<p><%- args.copyrightYear %> © <%- args.copyrightName %></p>
<% } %>
</div>
<div class="float-end">
<% if (args.footerMessage) { %>
<p>
<%- args.footerMessage %>
</p>
<% } %>
</div>
</div>
</footer>
</div>
</div>Should you choose to include it within your application, though, you can consume it like this:
<FullLayout logo="/assets/images/todos-logo.png" sidebarMenu="@.session.sidebarMenu" sidebarMenuActive="@.page.sidebarMenuActive" breadcrumbs="@.page.breadcrumbs" copyrightYear="<%= (new Date()).getFullYear() %>" copyrightName="<a class='external' target='_blank' href='https://www.restlessdev.com'>RestlessDev</a>" footerMessage="Made with <span class='text-danger'><i class='bi bi-heart-fill icon-mid'></i></span> in New Orleans" pageTitle="@.page.title" pageIntro="@.page.intro" appName="Todos" meta="@.page.meta" > <pagecontent /> </FullLayout>
You can ignore the unsightly parts of the code and just work with the wrapper.
At this time, ErstwhileLayout doesn’t have any specific methods that can be overwritten. When creating layouts, you can denote the place you’d like to insert the page content with a <pagecontent /> tag.
The ErstwhileTheme is the base class for themes in Erstwhile. When making a new theme, you create a subclass of this class and place it a file called theme.js in the root of your theme’s directory.
This class is used at compile time by the Erstwhile compiler to integrate your theme into the Erstwhile application and satisfy all theme dependencies. Consequently, most of the methods in this class are meant to be static.
ErstwhileController is the base class for themes in Erstwhile. It doesn’t have many specific methods to be overridden, but it does have certain conventions to follow when using it.
There are two primary types of methods specified in controllers:
/<controllerName>/<actionName>. Actions always have method names ending with Action.openModal() method. They have a special scope called modal that can store variables outside of session and page. Modals always have method names ending in Modal.There are also two reserved methods that can be overreidden called preAction() and postAction(), which are called as part of the request lifecycle.
ErstwhileModel is the base class for models in Erstwhile.
Of the different parts of the MVC framework, the Model layer is the most flexible. The only strict requirement with models is that they are exported by class name in the file /app/models/model.js, and that any methods on them are called statically. How you store data internally to the models isn’t important to Erstwhile.
If you choose to do things The Erstwhile Way™ you’ll receive some benefits, though.
Typically with web applications, the model layer is used to interact with backend systems. This involves hitting various API endpoints on a server to fetch and update data, authenticate, etc. To ease this process, Erstwhile uses a configuration file to describe the endpoints available to it, structures of entities coming out of the server, as well as certain metadata about how authentication happens.
This file is called description.json.
When working with an Erstwhile-aware backend, this file is published by the server at a known endpoint (during development. It isn’t necessary once the application is in production) and the framework is able to fetch this file and build out its model layer through a command line command. As the API changes, the people maintaining it will keep description.json up to date with the changes, and tell you to rebuild your model when needed.
If you are running an ExpressJS-based backend, there is an npm package called erstwhile-backend you can use to help you get up and running with publishing your description.json. It also includes a handy HTML output version so that you can point developers consuming your API to constantly-updated documentation on what your API offers.
If you don’t have an Erstwhile-aware backend, you can create your own description.json and store it in your /app/config directory, and the model build script will use that instead. To get started, we have a handy description.json builder.