• Home
  • Why Erstwhile?
  • Getting Started
  • Learn Erstwhile
  • API Docs
  • Home
  • Why Erstwhile?
  • Getting Started
  • Learn Erstwhile
  • API Docs
  • Learning Erstwhile
  • File Structure
  • Compilation Process
  • Routing
  • Request Flow
  • Components and ErML
    • How Components are Compiled
    • Themes
    • Layouts
  • Scoped Variables
  • Modals
  • Models
    • Using an Erstwhile-aware Backend
    • Creating Your Own Description.json
    • Using Models from the Controller

Learning Erstwhile#

Learning a new framework can be a challenge, as they each have their own perspective on what an application should look like.

This section will walk you through several topics of interest with the framework, and will explain how Erstwhile handles them.

File Structure#

By running through the Getting Started process, the framework will create the basic file structure for your application:

Due to its design goal of easy discoverability, Erstwhile is somewhat opinionated about how developers organize their code. The idea is that a new developer should be able to simply jump into a project and be able to figure out where everything is within 15 minutes.

Here is a little more detail of what goes into each of these directories:

  • /app/components: Any application-specific custom components go in this directory. A component is identified by its directory name (which, by convention, is CamelCapped and must contain a file called component.js that defines a subclass of ErstwhileComponent.
  • /app/config: This directory contains the configuration files. app.js is required, and (if the application defines its models locally) a description.json file could also be present.
  • /app/controllers: This directory contains the controllers, each of which is a subclass of ErstwhileController. Also each filename should end with Controller.js.
  • /app/models: This directory contains the models.js file, which defines the API calls the application makes to the backend.
  • /app/public: This directory contains all of the files that get copied into the document root of the distributable application. Erstwhile typically uses an /assets directory with separate subdirectories for /css, /fonts, /js and /images.
  • /app/views: This directory contains all of the views. There is a subdirectory called /layouts for storing the application’s layouts, and subdirectories named after each controller for storing the views related to their actions and modals. Each of these files is a .ejs file and is named in relation to the action or modal they correspond to.
  • /app/App.js: This file contains the application itself, a subclass or ErstwhileApp.

Outside of the application, there is also a /themes directory, which houses the themes to be used with the application. Most likely each application uses a single theme but a developer may have other themes in here that they are either developing or which contain similar-enough components that they can be toggled between. The /app/config/app.js file has a theme specified in it that tells the compiler which theme is currently used.

Finally, applications also have /build and /dist directories. /build is used as working space during the compilation process, and /dist contains the running application.

Compilation Process#

Erstwhile uses a compilation process to turn the application living in the /app directory into an actual functional website.

This compilation process starts when you run npm run start from your application root, and consists of several different directory watches recompiling things as you modify your application code:

  • watch:styles: This process watches your theme and /app/components directory for changes that could result in stylesheet updates.
  • watch:application: This process watches for changes within your theme and /app directory that could affect any other part of the application behavior.
  • webpack: As the other processes move files and compile into the /build directory, webpack will scoop up those newly updated files to create the javascript bundle for the application.

The end result of all of this is that your compiled application lives in /dist. Webpack also (conveniently) runs a web server at port 8080 so that you can see your application in the browser, and it will reload it after any changes. When it all looks good you can shut down the npm run start process and copy the /dist directory up to a web server to deploy.

Routing#

Erstwhile has two ways of defining an application’s routing behavior:

  1. The default method, which creates routes automatically based on naming conventions.
  2. The explicit method, where a developer overrides the ErstwhileController‘s getRoutes() method and explicitly lays out which paths point to which actions in a given controller.

Erstwhile currently builds the application’s routing table at compilation time, but we are investigating of how to update it within a running application. That will likely be added in a future release.

Default Method

By default Erstwhile will build the application’s routing table by iterating through your controllers and building routes for each method that has a name ending in Action. For example, if the website visitor goes to the path /automobiles/view/1234, they would see the output of the AutomobileController‘s method viewAction, with an argument of { "id": "1234" }

If you’d like to use a different base path for a given controller (to have AutomobileController base its paths off of /car rather than /automobile, for example) you can override the getControllerPath() method in the controller.

There are a few special cases to be aware of:

  • A controller called IndexController will not have a path. If it has an action called loginAction(), for example, that route would be /login rather than /index/login
  • An action called indexAction will listen at the root of a controller. If PathController has an indexAction, the route would be /path rather than /path/index. Similarly, the overall root route is defined in IndexController‘s indexAction method.
  • By default, the 3rd item in a path is passed in as an argument called id. If you need multiple wildcard items, it’s time to look at explicit routing.

Explicit Method

If you require more fine-grained control over routing, you can override the ErstwhileController‘s getRoutes() method and have it return an explicit routing table for the controller.

For example, if that method returns this:

  {
    "/login": {
      "action": "login"
    },
    "/cars/view/:id/:englishname": {
      "action": "view"
    }
  }

There would be two routes defined for this controller.

Some caveats to be aware of:

  • Each wildcard specified in a route is passed in by name to the action’s args parameter.
  • Specifying explicit routes means the default behavior is abandoned altogether. The default routes will not be created.
  • The developer can define routes for any path, even some which conflict with other controllers. In this example, if the IndexContoller had a loginAction, it is undefined what the routing table would look like. Be careful to avoid conflicts.

Request Flow#

When the user visits a URL in their browser, an Erstwhile application goes through the following steps:

  1. Boots: This only happens when the page first loads, where the pre-compiled routes and templates are loaded into the application. Subsequent page loads use this cached information.
  2. Check the path in the routing table: The framework attempts to look up the path in the routing table. Assuming it finds it…
  3. Clears the page scope: The framework empties the page scope, getting it ready for the next page to load.
  4. Run the controller’s preAction: If one is provided.
  5. Run the specified route action: This is the big one, and can include things like the following.
    • Setting Scoped Variables needed by the view layer to properly render the page.
    • Defining event handlers that can be used within the views.
    • Handling query string arguments that can change page behavior.
    • Accessing data via the model layer to either do lookups or updating operations.
    • Registering deferred functions that need to be run after the DOM is rendered.
  6. Run the controller’s postAction: If one is provided.
  7. Renders the ErML layout: If necessary. If the layout hasn’t changed, it keeps it as is.
  8. Renders the ErML page view: Based on the controller and action name.
  9. Registers the ErstwhileComponents present in the layout and page.
  10. Initializes the ErstwhileComponents.
  11. Registers the listeners for the Scoped Variables.
  12. Runs the deferred functions.

Steps 2-13 are repeated whenever a user goes to a new URL, which typically happens through the application’s redirect() method.

Components and ErML#

Components (which are subclasses of ErstwhileComponent) are Erstwhile’s way of building reusable UI elements of arbitrary complexity. They let a developer essentially create their own HTML tags (which we call ErML) and then drop them in their view templates as needed to create an easily-reusable components.

They can be simple inline elements:

<TextInput name="username" label="Username/Email Address" />

Or slightly more complicated containers, like <Card /> or <Alert /> here:

<div class="row">
  <div class="col-lg-6 col-md-8 offset-3">
    <Card>
      <div class="row">
        <h2 class="pb-2">Login</h2>
        <hr />
        <Alert id="login-alert" color="danger" light="true" dismissible="true" hidden="true"><%= (page.message || "The username/password combination entered doesn't match.") %></Alert>
        <Form id="login-form" >
          <TextInput name="username" label="Username/Email Address" />
          <PasswordInput name="password" label="Password" />
          <Button color="primary" onclick="@.page.submitForm">Login</Button> 
          <p class="mt-3">
            Need an account? Click <a href="/signup">here</a> to create an account.
          </p>
        </form>
      </div>
    </Card>
  </div>
</div>

Or simple tags that can produce more elaborate user interfaces. For example this:

<TodoCalendar todos="@.page.todos" month="@.page.calendarMonth" year="@.page.calendarYear" />

Becomes this:

You can also create components with their own internal structure, including other ErML:

<DataTable id="todo-list-table" url="<%= $App.getConfig('baseUrl') %>/todo/list" sort="date" sortDir="asc" filter="list-filter">
  <columns>
    <col key="color" title="Color" sortable="true" width="50px">
      <ColorBox color="{{color}}" />
    </col>
    <col key="title" title="Title" sortable="true" />
    <col key="date" title="Date" sortable="true" type="date" />
    <col key="description" title="Description" sortable="false" />
    <col key="done_flag" title="Done?" sortable="true" type="boolean" />
    <col title="Actions" key="id" sortable="false" width="150px">
      <Button color="success" size="sm" data-id="{{id}}" onclick="@.page.markDone">Mark Done</Button> <Button class="mx-2" color="light" size="sm" data-id="{{id}}" onclick="@.page.editTodo">Edit</Button>
    </col>
  </columns>
</DataTable>

Erstwhile components and ErML provide you with the opportunity to encapsulate a lot of complex logic in a very elegant package.

 

How Components are Compiled#

There are two places where components can live:

  • In a theme
  • In the application

Within the application, the components share a single namespace; if there is a component with the same name in both the theme and in the application, the application version gets used. This makes it very simple to make local tweaks to theme components if needed; you can just copy them from the theme into the application and modify whatever you need to, and it will just work.

During the compilation process, a list of all available component tags (based on the directory names they inhabit, either in the theme or application) is built and passed in to the ErstwhileApplication during the bootstrap process. It’s worth mentioning that ErML tags (like HTML) are not case sensitive; the convention is to use them CamelCapped to differentiate from surrounding HTML.

When rendering ErML, the view templates and layouts are converted into an XML document and traversed by the renderer; any tag that is found to be on the component list is then instantiated as a component and (depending on how the developer implements it) is turned into HTML and added to the DOM. This HTML is the output of the component’s getHTML() which, if not overridden, will render the component’s EJS file if one is specified.

If that component is a container (based on having isContainer() overridden to return true) it is further traversed and anything inside of it also rendered and added to the DOM at the parent component’s <InnerContent /> tag.

Once the DOM is fully rendered, each component added has its initialize() method called.

Themes#

Erstwhile bundles Components into Themes. At the moment, there is a simple and incomplete one called Mazer (based on the MIT licensed Mazer Dashboard Bootstrap theme) to serve as a starting point.

Themes live in directories (Mazer is here) which in turn contain child directories for each component, as well as a reserved directory called /theme (that contains things like SCSS and Javascript files used theme-wide) and a file called theme.js that contains the subclass of ErstwhileTheme needed to compile the theme.

Each component directory typically contains at least two files, component.js (which contains the subclass of ErstwhileComponent that defines the theme) and component.ejs that contains the EJS template of the component. Only component.js is required. For custom components you can add additional files like SCSS if needed, or additional Javascript files.

Layouts#

Layouts are specialized components that are subclasses of ErstwhileLayout. They are meant to serve as frames for your action templates, and typically include things like navigation, sidebars, headers, footers, and things like that. Quite often they will use Scoped Variables in the session scope to show things like user names or photos, or render navigation that’s present across multiple pages.

Fat Layouts

Erstwhile believes in getting things done in as little code as possible. It’s a fact that HTML is a fairly verbose markup language, particularly when it comes to things like navigation where you often need to use additional tags to move things around and position them correctly across different breakpoints. Here is where the concept of Fat Layouts comes in.

Using Mazer’s <FullLayout /> as an example, the code is pretty weighty:

<%
  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 %> &copy; <%- args.copyrightName %></p>
          <% } %>
          
        </div>
        <div class="float-end">
          <% if (args.footerMessage) { %>
            <p>
              <%- args.footerMessage %>
            </p>
          <% } %>
          
        </div>
      </div>
    </footer>
  </div>
</div>

We call this a Fat Layout because it contains a bunch of code you don’t want to have to look at every day. You want to figure it out once and then stick it in a library somewhere to not have to think about again.

By sticking it in an Erstwhile component, the application can have a layout (under /app/views/layouts/full.ejs, for example) that looks 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>

Your application is now only concerned with the data that needs to get to the layout and not all of the unsightly markup. It’s a cabbage soup diet for your code.

Scoped Variables#

Scoped Variables are the method that Erstwhile uses to pass variables and functions between the controller and view layers.

At the present time, Erstwhile has three different scopes available:

  • session: session scoped variables are tied to the browser session
  • page: page scoped variables are tied to the current page
  • modal: modal scoped variables are tied to the modal

When dealing with data, the developer should generally choose a scope that most closely matches when the data needs to be available. If the data lives as long as the user is logged in to your site like a username, it makes sense to use the session scope. Items that will need to be refreshed each time a user revisits a page should be at the page scope. Makes sense, right?

Controller Level

Within the controller, developers can assign and update Scoped Variables like this:

// assigning a single variable
$App.scopes.page.title = "Login";

// assigning an function (to serve as an event handler)
$App.scopes.page.submitForm = function() {
    $App.getComponent("login-alert").hide();
    let formValues = $App.getComponent("login-form").getValues();
    let model = $App.getModel('Authentication');
    model.login(formValues).then(function(response) {
      if(response.data.success) {
        model.currentUser().then(function(response) {
          $App.scopes.session.user = response.data;
          let queryParams = new URLSearchParams(location.search);;
          if(queryParams.get('redirect')) {
            $App.redirect(queryParams.get('redirect'))
          } else {
            $App.redirect("/todos/list")
          }
        }).catch(function(e) {
          console.log(e)
        });
      } else {
        $App.getComponent("login-alert").show();
      }
    }).catch(function(e) {
      console.log("error", e)
    });
    return false;
  }
}

Under $App.scopes there are the three available scopes: session, page, and modal.

View Level

Within the views (and layouts) you can pass your Scoped Variables/Functions to your ErstwhileComponents like this:

<!-- assigning a regular variable -->
<Form id="edit-todo">
  <TextInput name="title" label="Title" required="true" value="@.page.todo.title" />
  <DateInput name="date" label="Date" value="@.page.todo.date" />
  <SelectInput name="color" label="Color" value="@.page.todo.color">
    <option value="green">Green</option>
    <option value="blue">Blue</option>
    <option value="orange">Orange</option>
    <option value="red">Red</option>
    <option value="yellow">Yellow</option>
  </SelectInput>
  <TextareaInput name="description" label="Description" helpText="This should be a description if the todo if necessary." rows="10" required="true" value="@.page.todo.description" /> 
  <SwitchInput name="done_flag" label="Completed" value="@.page.todo.done_flag" />
  <TextareaInput name="completion_notes" label="Completion Notes" helpText="Anything else to add about this todo?" rows="10" required="false" value="@.page.todo.completion_notes" /> 
</Form>

<!-- assigning an event handler -->
<Button color="primary" onclick="@.page.submitForm">Login</Button>

The syntax @.page (or @.session or @.modal) indicates that the variable is meant to be accessed as a Scoped Variable and not as just a standard string.

What’s Happening Under the Hood

When a Scoped Variable is assigned to a component in the view, it creates a watch for that component. Any subsequent updates to that variable within the controller (or within an event handler) will be transmitted to the component via the component’s receiveUpdate() method; this gives the component the ability to decide on what it wants to do with that update.

Event handlers (that is, attributes that start with on*) are handled slightly differently. If they are assigned a Scoped Function, that function will be added as the named handler for that component.

Once the scope expires (for example, when a new page is loaded in the case of page scope), the watches are removed for that scope.

Modals#

Modals are treated very similarly to Actions in Erstwhile.

The are implemented as methods in the Controller, but their names end with Modal rather than Action. When the function $App.openModal() is called, the framework will run the modal’s controller function and render its view template in much the same manner as it does controller actions, but rather than attaching the output to the overall browser frame it adds it to a Bootstrap modal, and then displays it.

Modals, since they live on top of a page, are given their own scope for Scoped Variables called (oddly) modal. Within your controller’s modal function you can assign Scoped Variables to that scope and then they become available in your view.

When you’d like to close the modal you can call the function $App.closeModal().

Controlling the Modal Frame

The modal frame itself can be controlled via the function $App.setModalAttributes(args). This call can happen within your modal function in the controller.

Properties that can be specified in the args object include the following:

  • title: The title of the modal
  • size: The size of the modal (sm, lg, xl)
  • centered: Vertically centers the modal
  • scroll: Sets the modal to be scrollable
  • theme: Sets the color for the modal frame. (Bootstrap colors)
  • buttons: The array of buttons that should be added to the right of Cancel. Each has the following format:
    • label: The label of the button
    • func: The function to run when clicked
    • color: The Bootstrap color to use for the button

Importantly, the function that is included as part of the button definition doesn’t have to be a Scoped Function.

Models#

Models are the means by which Erstwhile applications connect with backend systems.

Importantly, using Erstwhile’s model layer is optional. While it does attempt to make things easier, you can connect to other services using Axios or whichever other library you prefer, and manage sessions like you do with other frameworks.

If you can, though, using Erstwhile’s models should make your life a little easier.

If you go the Erstwhile Model route….

There are two primary ways of building out your model layer:

  1. Use an Erstwhile-aware backend
  2. Create a local API description file

Using an Erstwhile-aware backend is definitely preferred (we even have a Node module to help out!) but since it’s still early on in the Erstwhile Revolution it’s possible you may need to make your own description file.

Using an Erstwhile-aware Backend#

If you’re using an Erstwhile-aware backend things are really easy. Ask the backend developer for the URL of the backend server and where they host the API description.

Then set up some environment variables in your .env:

# Erstwhile
ERSTWHILE_API_URL=http://localhost:8022
ERSTWHILE_API_DESCRIPTION_PATH=/describe

After that, run npm run models. Done.

Servers using our module to publish their desciption.json file will also be self-documenting; you can go to the URL of the description file with an extra parameter to see a nice HTML readout of the file:

http://localhost:8022/describe?format=html

If everything goes well, you should see something like this:

Creating Your Own Description.json#

If your backend doesn’t support Erstwhile natively, you can create your own description.json file and use that instead.

This file describes how Erstwhile sees an API. You can see an example here for our Todos sample application. We’ve made a JSON Schema-based description.json editor to help you out.

Generally speaking, the description file contains a few core sections:

  • Version #: A handy number to tell you what revision you’re at.
  • Application Metadata: Includes things like the name, description, and (importantly) the session key header for the API.
  • Authentication Section: Erstwhile separates this out so that developers consuming an API can quickly see where the signin and session management methods are.
  • Enums: Common lists in the application.
  • Entities: These are the models in Erstwhile. Jackpot.

Authentication

When creating your own description.json, you’ll likely need to handle authentication separately, as there are a variety of different schemes for getting access tokens and we can’t account for them all quite yet.

Once you are able to get an access token, please store it in window.localStorage.erstwhileSessionKey.

Using the description.json Editor

Opening the description.json editor, we can start entering the information needed for your local version.

  1. Enter a version number. It’s not important what you enter, but it’s required.
  2. Enter the application name.
  3. Keep the Authentication Scheme as erstwhile-custom-header.
  4. Under Header, set the name of the header the backend is expecting for its session key or access token. Assuming you’re storing it in window.localStorage.erstwhileSessionKey, the framework will pass it along.
  5. As you’re already handling Authentication yourself, you can skip that section.

Now you have some choices to make.

Erstwhile separates APIs into logical entities. For example, a typical ecommerce site might have entities for Items, Orders, and ShoppingCarts. An Order, in turn, might have methods for submit() and cancel().

Looking at your API, you now have to decide how to assign its various endpoints to different entities, and what to call them.

For each entity do the following:

  1. Add the entity under the Entities section.
  2. Enter a Unique ID and the two name fields.
  3. For each endpoint, add an entry under Methods.
    1. Add the Name. Any logical name should work.
    2. Path (the relative path of the API call, like /api/users/:id/update)
    3. HTTP Method
    4. Function Name (what you’d like to call it when calling from your model, like update(). Without the () though)
    5. Whether or not this endpoint requires authentication

That should be all you need.

Now tab over to the “Description.json Output” tab and copy the JSON in there to a file called /app/config/description.json and save.

From your application root, run npm run models and you should have your new models stored in /app/models/models.js.

Using Models from the Controller#

Using Erstwhile models within the controller is pretty straightforward:

// in this case, gather the form data from the form
let formValues = $App.getComponent("create-todo").getValues();

// get a reference to the model
let model = $App.getModel('Todo');

/*
 * Call the create method on the model. The first argument is the data.
 * The return value is an axios promise, so you can handle that however
 * you would like.
 */
model.create(formValues).then(function(response) {
  if(response.data.success === false) {
    $App.scopes.modal.message = "There were errors in your form";
    $App.getComponent('create-todo').showErrors(response.data);
    $App.getComponent("create-todo-alert").show();
  } else {
    $App.getComponent("create-todo-alert").hide();
    $App.getComponent('create-todo').clearErrors();
    $App.closeModal();
    $App.getComponent('todo-list-table').redraw();
  }
}).catch(function(e) {
  console.log("error", e)
});

The developer doesn’t have to worry if the particular endpoint is GET, POST or whatever because the framework takes care of it. The first argument will automatically be the response body or the query string, depending on the need.

Sometimes an endpoint will also require adding parameters to the path. Erstwhile handles this too.

// get a reference to the model
let model = $App.getModel('Todo');

// pass in an empty first argument, and a map with the necessary data in the second
model.get({}, {todoID: args.id}).then(function(todoResponse) {
  $App.scopes.page.todo = todoResponse.data;
  $App.scopes.page.title = `Edit ${todoResponse.data.title}`;
}).catch(function(e) {
  console.log("error", e)
});

 

loader

Join our Mailing List     

  • Privacy Policy
  • Terms of Use
  • © 2025 RestlessDev. Made in New Orleans.

Welcome to Erstwhile

Thank you for checking out Erstwhile. We’re just getting started, so if you’d like to keep up with our progress please register now and we’ll let you know when new releases come out.

loader

Email Address*

Name