Getting Started

This page describes how you can start developing an app with Open Cells.

To create an application with Open Cells, run:

npm init @open-cells/app

This will run an application generator that will ask you if you want an empty application (a minimal sample) or a full sample application (cooking recipes application).

Next, enter a name for the application and once you confirm it, a folder will be created with the application inside.

Root Directory/
|── package.json
|── tsconfig.json
|── index.html
|── images/
|   └── favicon.svg
└── src/
    |── components/
    |   |── app-index.ts
    |   └── app-index.css.js
    |── pages/
    |   └── home/
    |   |   └── home-page.ts
    |   └── second/
    |       └── second-page.ts
    |── css/
    |   |── home.css
    |   |── main.css
    |   └── second.css
    └── router/
        └── routes.ts

Now you can go to the application folder and install the dependencies:

npm install

After dependencies installation has finished, serve and try the application running:

npm run dev

Once you have created the application following the steps above, you can start developing your app.

The index.html file is the document in which the app will be mounted on. Its body contains the <app-index id="app-content"> element that will contain the app pages, and the <script> tag that invokes all the Open Cells logic.

The src/components/app-index.ts file includes the imports of Open Cells core library and the app initialization.

import { startApp } from '@open-cells/core';
import { routes } from '../router/routes.js';

startApp({
  routes,
  mainNode: 'app-content',
});

From there, working with an Open Cells application involves a few key relations:

  • Pages and routes: Define the pages and routes to navigate within your app.
  • Pages and components: Create the pages and components for your application.

The router/routes.ts file contains the list of available pages and routes in your app. This list is provided to the Open Cells core startApp method.

Each item in this list is an object that maps a path or route to its respective page. It's important to note that there must always be an initial page configured at the root path /.

export const routes: object[] = [
  {
    path: '/',
    name: 'home',
    component: 'home-page',
    action: async () => {
      await import('../pages/home/home-page.js');
    },
  },
  {
    path: '/second',
    name: 'second',
    component: 'second-page',
    action: async () => {
      await import('../pages/second/second-page.js');
    },
  },
];

The routes list can also accept a page that will be shown when the route is invalid; see Handling invalid routes.

In addition, Open Cells offers a transition system that animates page navigation when switching between active and inactive states. This provides a more user-friendly experience by simplifying tasks for the developer (see transitions section for more detail).

The initial blank scaffold contains two pages, home-page and second-page, but your app could need more pages to display additional content. Next, you'll find how to create a page and add it to the routes.ts file to make it accessible from the application.

  1. Create the page file: In the pages directory, create a new folder and file for your new page. For example, you could create about/about-page.ts if you want to create an "About" page.

  2. Develop the page content: Inside the newly created file, you can develop the content of the page using HTML, CSS and JavaScript as needed. This may include descriptive text, images, or any other elements you want to display on the page.

In Open Cells, a page is typically represented by a custom element that extends LitElement. You can create a new TypeScript file for your page component and define the logic and rendering code for the page.

import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('about-page')
export class AboutPage extends LitElement {
  static styles = css`
    /* CSS styles for your page */
  `;

  render() {
    return html` <!-- HTML content for your page --> `;
  }
}

Pages can leverage Lit capabilities in their class definition to:

  • Define page structure, content and styles: The render() method of the component returns the HTML structure and contents for the page using Lit html tagged template literals. The styles static field allows to include CSS styles using the css tag function provided by Lit.

  • Handle interactivity and data: You can add interactivity to your page by handling user events (e.g., clicks, inputs) and updating the state or properties of your component accordingly. Lit provides decorators like @property and @state to define reactive properties and state within your component.

  • Add lifecycle hooks: Lit exposes lifecycle hooks such as connectedCallback, disconnectedCallback and firstUpdated that you can use to perform initialization, cleanup or other actions when your component is added to or removed from the DOM or when it's first updated.

Once you've created the page, it's time to add it to the routes.ts file so that the application can navigate to it properly.

  1. Open the routes.ts file: Navigate to the routes directory and open the routes.ts file in your code editor.

  2. Add a new route: Within the routes array, add a new object representing the route for your second page. For example:

{
  path: '/about',
  name: 'about',
  component: 'about-page',
  action: async () => {
    await import('../pages/about/about-page.js');
  },
},

Components are modular and reusable elements that can contain content, interactive functionalities and specific styles. By adding components to our pages, we can significantly improve the structure, functionality and aesthetics of the website.

As page content is just HTML, you can use other Web Components custom elements in them, as well as any standard HTML tag.

You can create your own custom elements by defining them in the "components" folder of your project. Alternatively, you can use custom elements from popular libraries such as Material Design or others.

For example, adding a button from Material Design can be as simple as follows:

In your project, install the "@material/web" library using npm:

npm i --save @material/web

2. Import the custom element definition in your page

Permalink to “2. Import the custom element definition in your page”

For example, for using the Outlined button add its import at the start of your page file:

import '@material/web/button/outlined-button.js';

Use the button as any other HTML tag in the page render method:

render() {
  return html`
    <h2>About page with a button</h2>
    <md-outlined-button>Material button</md-outlined-button>
  `;
}

While pages are simple Lit Web Components, Open Cells provides page controllers and element controllers that enable pages to subscribe to information using the pub/sub pattern, navigate to other pages and use the new lifecycle hooks onPageEnter and onPageLeave.

With the page controllers, developers can easily manage the state and behavior of pages within the application. These controllers facilitate communication between different parts of the application by allowing components to subscribe to specific data updates or events using the publish/subscribe pattern. This enables a more modular and decoupled architecture, enhancing the maintainability and scalability of the application.

Additionally, the introduction of lifecycle hooks such as onPageEnter and onPageLeave offers developers more control over the initialization and cleanup processes of individual pages. These hooks allow a page to execute specific actions when it is entered or left, enabling tasks such as data fetching, animations or resetting state.

Overall, the combination of page controllers, element controllers and lifecycle hooks provided by Open Cells empowers developers to build more dynamic and responsive web applications with enhanced control over page navigation and lifecycle management (see state for more detail).

Example:

import { PageController } from '@open-cells/page-controller';
import { html, LitElement, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@material/web/button/outlined-button.js';

@customElement('about-page')
export class AboutPage extends LitElement {
  pageController = new PageController(this);

  @state()
    protected _randomData = null;

  onPageEnter() {
    this.pageController.subscribe('ch-custom-data', data => {
      this._randomData = data;
    });
  }

  onPageLeave() {
    this.pageController.unsubscribe('ch-custom-data');
  }

  static styles = css`
    /* CSS styles for your page */
  `;

  render() {
    return html`
      <h2>About page with a button</h2>
      <md-outlined-button @click="${() => this.pageController.navigate('home')}">Go to home page</md-outlined-button>
      <p>My _randomData: ${this._randomData}</p>
    `;
  }
}

In the example above, the page subscribes to a channel named ch-custom-data using the subscribe method of pageController object. When data is emitted in the ch-custom-data channel, it is captured and assigned to the variable _randomData.

This is an example of channels usage, Open Cells provides a channel-based system built on top of RxJS to manage the application state. This enables components to receive the information they need and update accordingly (see Channels for more detail).

Now, to see the general operation of the controllers, we will update the Home page to publish a value for the ch-custom-data channel and add a button to navigate to the About page.

/* Publish ch-custom-data channel with a value */
onPageEnter() {
  this.pageController.publish('ch-custom-data', "Hello! I'm a channel value!");
}

render() {
  return html`
    <button @click="${() => this.pageController.navigate('second')}">Go to second page</button>
    <!-- add button to navigate to the About page -->
    <button @click="${() => this.pageController.navigate('about')}">Go to about page</button>
  `;
}

The startApp method, in the src/components/app-index.ts file, expects a configuration object with two mandatory nodes: routes and mainNode.

You can also use appConfig to further setup your project (see configuration and bootstrapping section for more detail).

import { startApp } from '@open-cells/core';
import { routes } from '../router/routes.js';

startApp({
  routes,
  mainNode: 'app-content',
  appConfig: {},
});

As part of the workflow, as usual in any framework, we can make use of commands to see the application running in the browser:

  • Building: Build your application to prepare it for deployment.
  • Serving: Serve your application locally to test it before deployment.

Open Cells uses Vite for building applications.

To build the application, use the command:

npm run build

To serve the application, use the command:

npm run dev