• Stars
    star
    270
  • Rank 152,189 (Top 3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Your first choice for hybrid mobile applications

Erste

npm version GitHub license API Reference Documentation

JavaScript view library for building performant hybrid mobile applications

erste.js is a zero-hype view library with an attitude. It’s built for achieving maximum performance on mobile devices.

Features

  • Lightweight, 7kb minified & gzipped
  • No dependencies
  • No magic—as declarative as it should be and no more
  • No pitfalls—use good old DOM APIs, HTML5 & CSS3 to build modern apps
  • Clean, structured and approachable API

Overview

// 1. Import erste,
import {Component} from 'erste';

// 2. Create your application,
class App extends Component {
    constructor() {
        super();
        this.counter = 0;
    }

    // 3. Arrange your view,
    template() {
        return `
        <div>
            <h1>${this.counter}</h1>
            <button class="increment">Increment</button>
            <button class="decrement">Decrement</button>
        </div>
        `;
    }
    // 4. Create your methods,
    increment() { this.$('h1').innerText = ++this.counter; }
    decrement() { this.$('h1').innerText = --this.counter; }

    // 5. Bind your events.
    get events() {
        return {
            'tap': {
                '.increment': this.increment,
                '.decrement': this.decrement
            }
        }
    }
}

// 6. Make your application run.
new App().render(document.body);

Table of Contents

Motivation

Building applications should be straightforward and simple. Most of the frameworks used today fail hard at being simple, and they make the wrong compromises for marginal gains. A super declarative framework with a megabyte of size, one second boot time and thousands of questions on StackOverflow due to its obscure and unfamiliar API... is this familiar?

We as application developers don't need fancy features that are only good on the paper or in meetup talks. We need an easy-to-use, reasonable API that gets out of the way. The cognitive load for the framework used should ideally be 0. Good luck with that when you want to distinguish between '<' and '&'.

erste.js is a solemn approach to application development. It gives you the barebones to get started and doesn't try to steal the show from your application. It lets you focus on your own source code and gets out of the way.

Installation

Direct download

Using npm

npm install --save erste

Builds with Closure Compiler

erste.js plays really well with Google Closure Compiler. It's actually built with Closure Compiler, so if you use Closure Compiler for your own application, you can also goog.require or import the source code of erste.js and use and compile it with your source code right away for minimal footprint and maximal performance.

Example application

Head over to erste.js-demo for an example Cordova application built with erste.js that showcases all the features of erste.js. It's fortunately not a to do app, but an almost real life multi-view app for displaying fan posters of popular tv shows. You can learn how to build and manage complex view hierarchies, handle user events and make use of the included tab bar, navigation bar, side bar, pull to refresh component and infinite scroll component. The repository also features a Gulpfile.js that includes common tasks for building the application with ES6 and transpiling it through Babel.

Building your first application

GUI applications are built with component architectures in mind. This is not a latest trend but the way GUI architecture was defined over 40 years ago. So in erste.js, there is one single and simple building block — the Component class. Everything you see and touch in an application is a component in erste.js, but it also provides some special constructs that ease your development. The most imminent of these is the View class, which is the main class for presenting a full view — a container that fills the screen and hosts other components — with its own lifecycle.

The root view

Every application starts with a root view. It is the first thing you put in your <body> tag, a single view that includes all of your application.

Write your root view by extending from the View class in erste.js;

root-view.js:

import {View} from 'erste';

class RootView extends View {
    template() {
        return `
        <root-view>
            <h1>Hello world!</h1>
        </root-view>
        `;
    }
}

The only thing you need to override here is the template method. Note that templates are only markups in erste.js. They are not parsed for declarative syntax, so here the <root-view> is just a tag. Since we are targeting modern, HTML5-compatible browsers, you can actually use custom tags for distinguishable markup. Otherwise, you can just use plain <div>s. Actually, any element will do fine as a template, and a block element makes sense as the root view.

You now should insert this view into the DOM. There are two ways to do this, the simplest being;

index.js:

import RootView from './root-view';

document.body.innerHTML = new RootView();

This simply inserts the template of your component to the body. A more involved alternative is to manually render the DOM element for the view as in;

index.js:

import RootView from './root-view';

new RootView().render();

Note that, the render method, when provided no arguments, renders this view directly into document.body. Alternatively, one may wish to pass in the desired host element as the first argument to the render method as in new RootView().render(document.body);

We will discuss the implications of both approaches in a further topic.

DOM Events

Handling DOM events is completely automated in erste.js. We acknowledge that most of the bugs and problems arise due to poor handling of DOM events (especially when one forgets to remove them which leads to memory leaks). Moreover, manual DOM listeners hinder the performance of your application. Therefore, erste.js provides a complete event management system that fixes all of these problems for you in a declarative and extremely performant way.

erste.js also has a built-in gesture recognizer that provides touch events like tap, longTap, swipe and more.

Let's listen to the tap event on the button in our root view and do something meaningless with it;

root-view.js:

import {View} from 'erste';

class RootView extends View {
    template() {
        return `
        <root-view>
            <h1>Hello world!</h1>
            <button>Tap me!</button>
        </root-view>
        `;
    }

    onTapButton() {
        this.$('h1').innerText = 'Thanks for the tap!';
    }

    get events() {
        return {
            'tap': {
                'button': this.onTapButton
            }
        }
    }
}

The first thing you'll notice here is the declaration of the events property. It's an object whose keys are event types and values are another object with keys corresponding to CSS selectors and values corresponding to event handlers.

Secondly, the manual DOM update. erste.js doesn't provide you with any data-binding functionality. Data-binding is, no matter what technique you employ, always a poor performer. Since the goal of erste.js is to be the most performant way of building apps, we decided against using declarative DOM updates and went for manual updates.

However, this brings about the question of efficacy, as the horrible problems due to poor handling of jQuery code is still fresh in some memories. There are indeed horrible ways of managing the DOM manually, and we want you to stick to the best practices without compromising convenience. Therefore we provide two helper methods, $ and $$. As you might have already guessed, these are simple wrappers around querySelector and querySelectorAll DOM APIs. These calls are also scoped, meaning this.$('button') actually translates to this.el.querySelector('button') where this.el is the DOM element of the component. This is a very efficient and straightforward way of referencing DOM elements.

How DOM event management works

erste.js provides a declarative method for managing DOM events, by heavily utilising event delegation. In erste.js, there's one global event handler for each DOM event type. When an event occurs, its global handler receives it and checks if there are any appropriate handlers defined in a component. If such a component is found, the event is forwarded to the designated handler. Although event management is delegated to global handlers on the body, event propagation still works as it's supposed to. This lets you use the regular event handling approach you are accustomed to from classical web development where parent components may listen to events that happen on their children.

Creating other components

erste.js doesn't mess with your lifecycle management. Creation of additional views and components is strictly imperative, meaning you get to instantiate your views manually and whenever you want. We acknowledge that a key step in optimization of mobile apps is manually managing the instantiation and disposal of hefty components, so we simply leave it to you.

Let’s turn our simple button and label into a standalone component.

button-with-label.js:

import {Component} from 'erste';

class ButtonWithLabel extends Component {
    template() {
        return `
        <button-with-label>
            <h1>Hello world!</h1>
            <button>Tap me!</button>
        </button-with-label>
        `;
    }

    onTapButton() {
        this.$('h1').innerText = 'Thanks for the tap!';
    }

    get events() {
        return {
            'tap': {
                'button': this.onTapButton
            }
        }
    }
}

We basically moved all the logic into a reusable component. An important thing to note here is, the template method should return a single HTML element. Therefore, we wrapped our <h1> and <button> in <button-with-label>.

Then the root view simply becomes;

root-view.js:

import {View} from 'erste';
import ButtonWithLabel from './button-with-label';

class RootView extends View {
    constructor() {
        super();

        this.buttonWithLabel = new ButtonWithLabel();
    }

    template() {
        return `
        <root-view>
            ${this.buttonWithLabel}
        </root-view>
        `;
    }
}

Here is how declarative erste.js is; views and components can include other components by simply including them within the template literals.

Lifecycle management of components

In this example we chose to instantiate the child component within the constructor of the RootView. While this is a very common scenario, for some reason we may want to defer the initialization of the child component.

Option 1: Declarative

We could create the component within the template method so that it would be created only when the view would be rendered. This wouldn’t be extremely maintainable, but could be a fair trade off for certain cases. Then the template method in RootView could look like this:

root-view.js:

/* … previous code … */

    template() {
        this.buttonWithLabel = new ButtonWithLabel();

        return `
        <root-view>
            ${this.buttonWithLabel}
        </root-view>
        `;
    }

Option 2: Imperative with erste.js API

The child component may require its parent to be in the DOM when it’s instantiated. Under those circumstances, it would make sense to imperatively append the child into the parent after the parent is rendered into the DOM. In this case, the RootView would look like:

root-view.js:

import {View} from 'erste';
import ButtonWithLabel from './button-with-label';

class RootView extends View {
    onAfterRender() {
        this.buttonWithLabel = new ButtonWithLabel();

        this.buttonWithLabel.render(this.el);
    }

    template() {
        return `<root-view></root-view>`;
    }
}

Of course, you could just as well instantiate ButtonWithLabel in RootView’s constructor and render it within onAfterRender.

Option 3: Imperative with DOM API

If you don’t like to remember custom render methods and such, you can also use the native appendChild DOM API.

In this case, the RootView would look like:

root-view.js:

import {View} from 'erste';
import ButtonWithLabel from './button-with-label';

class RootView extends View {
    onAfterRender() {
        this.buttonWithLabel = new ButtonWithLabel();

        this.el.appendChild(this.buttonWithLabel.el);
    }

    template() {
        return `<root-view></root-view>`;
    }
}

Notice that here we need to access the el property of the buttonWithLabel component, which gives us the DOM element. Also, there is an implicit rendering happening when you access el for the first time. Since it creates a lot of buggy scenarios when el might be null, erste.js just assumes that whenever you want to refer to el, you actually want to have your component rendered. So if the component hasn’t been rendered before, for your convenience, erste.js first renders it into a DOM element before returning it to you.

Creating master and detail views, or introducing the ViewManager

Mobile apps make extensive use of the master / detail view scheme, and it’s a first citizen in erste.js as well.

ViewManager is a class that orchestrates the introduction of detail views and manages a view hierarchy with history support for going back to previous views altogether with touch gestures.

Let’s revise our root view to make it a master view that displays a list of items, and introduce a detail view. In order to facilitate the internal view hierarchy, we have to make use of the ViewManager class. Let’s start by adapting our index.js for this;

index.js:

import {ViewManager} from 'erste';
import RootView from './root-view';

var vm = new ViewManager();
var rootView = new RootView();
rootView.vm = vm;

vm.setCurrentView(rootView);

setCurrentView method renders the view into the root element of vm, which is the default one, the body, in this case. We then also hand vm onto rootView, because it will later utilise this view manager to show detail views.

Let’s first build our detail view.

detail-view.js:

import {View} from 'erste';

class DetailView extends View {
    constructor(item) {
        super();

        this.item = item;
    }

    template() {
        return `
        <detail-view>
            <p>${this.item}</p>
        </detail-view>
        `;
    }
}

It simply receives an item in its constructor and prints it in the template.

Here is a sample master view implementation and how we can make use of our new detail view;

root-view.js:

import {View} from 'erste';
import DetailView from './detail-view';

class RootView extends View {
    constructor() {
        super();

        this.items = [1, 2, 3];
    }

    onItemTap(e) {
        var targetIndex = e.targetEl.getAttribute('data-index');
        var item = this.items[targetIndex];

        var detailView = new DetailView(item);

        this.vm.pull(detailView);
    }

    template_item(item, index) {
        return `<div data-index=${index}>${item}</div>`;
    }

    template() {
        return `
        <root-view>
            ${this.items.map(this.template_item).join('')}
        </root-view>
        `;
    }

    get events() {
        return {
            'tap': {
                'div': this.onItemTap
            }
        };
    }
}

The most interesting bit here is how we get the information about the tapped item and how we create the detail view. Although there may be various implementations for this, we chose to embed the index of each item within its template. Then in the tap event handler, we fetch this index attribute and instantiate a DetailView with the corresponding item. The last thing is to tell the view manager to pull this new detail view onto the screen.

Congratulations! Now you have a sample master / detail view application! Read on for more advanced use cases!

Going back to the master view

In the simplest scenario, the detail view is a final view and a certain while after navigating to the detail view, the master view is disposed. This also prevents memory leaks by default. If you wish to keep the history of the previous views, the pull method accepts a second optional argument opt_canGoBack of type boolean. When passed in true, the view manager saves the first view in its history and doesn’t dispose it.

Later, you can call vm.push() whenever you want, and the view manager will go back to the master view, disposing the detail view.

The back gesture

erste.js also features the swiping gesture from iOS for view navigation. You can drag from the left edge of the screen towards the right and it will reveal the master view below.

This gesture recognition is not enabled by default, and you need to enable it explicitly for the detail view. Modify DetailView constructor and set supportsBackGesture to true. By default back gesture touch target width is 100px on the left side of the view. To alter it you can set backGestureTouchTargetWidth.

detail-view.js:

    /* … previous code … */
    constructor(item) {
        super();

        this.item = item;

        this.supportsBackGesture = true;
        this.backGestureTouchTargetWidth = window.innerWidth / 2; // you can set as half of the screen's width.
    }

Now you will be able to navigate back to the original view with a swipe!

Conclusion

erste.js has a lot more to offer in terms of application development. You can check out the various built-in components — such as the navigation bar, the tab bar and more — and learn how you can make use of the advanced features. Make sure to try the demo over at erste.js-demo to see it all in action.

License

MIT License

Copyright (c) 2017 Armagan Amcalar

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

cote

A Node.js library for building zero-configuration microservices.
JavaScript
2,302
star
2

pedalboard.js

Open source JavaScript framework for developing audio effects for guitars using the Web Audio API.
JavaScript
830
star
3

mogollar

A MongoDB UI built with Electron
JavaScript
279
star
4

cote-workshop

Microservices case study with cote.js
JavaScript
254
star
5

brain-bits

A P300 online spelling mechanism for Emotiv headsets. It's completely written in Node.js, and the GUI is based on Electron and Vue.
JavaScript
168
star
6

biri

A unique, static client ID generator for browser applications
JavaScript
139
star
7

brain-monitor

A terminal app written in Node.js to monitor brain signals in real-time
JavaScript
133
star
8

recht

A concise rule engine to express and enforce rules for selections, permissions and the like
JavaScript
103
star
9

wits

A Node.js library that reads your mind with Emotiv EPOC EEG headset
C++
90
star
10

docker-node-pm2

A pm2 application container for docker.
Shell
75
star
11

stack

A starter repository for MongoDB, Node.js, and Vue.js, with a local environment based on Docker.
JavaScript
57
star
12

microservices-workshop

An example microservices implementation with Node.js and Docker
JavaScript
54
star
13

regie

An observable state management tool for vanilla JS applications based on Proxies
JavaScript
49
star
14

vuelve

A declarative syntax for the Composition API in Vue 3.
JavaScript
46
star
15

hakki

An opinionated, modern, and scalable alternative to node_acl.
JavaScript
35
star
16

docker-nextjs

JavaScript
29
star
17

tombala

A simple tombola game
JavaScript
27
star
18

geneJS

Code generator for PlantUML
JavaScript
24
star
19

vue-node-starter

JavaScript
20
star
20

erste-demo

A sample app that showcases how to use erste
CSS
18
star
21

vue-starter

Vue
16
star
22

plantuml

Git mirror of plantuml's SVN repo. Updated seldomly, whenever I need the new source.
Java
13
star
23

aktivite-akis-ornegi

Node.js ile aktivite akış örneği
JavaScript
10
star
24

jira-bot

Jira bot is a library that bridges Jira and XMPP chat.
JavaScript
10
star
25

hax.js

Haxball clone with JavaScript; Canvas and Node.js
JavaScript
8
star
26

puckjs-automatic-page-turner

An automatic page turner BLE HID Peripheral for Puck.js
JavaScript
8
star
27

dockercloud-microservices

An example workflow to build & deploy Node.js microservices to Docker Cloud.
JavaScript
7
star
28

kotelett

Simplest microservices ever.
JavaScript
7
star
29

mindy

JavaScript
6
star
30

wtmbjs-4

JavaScript
6
star
31

midi-experiments

JavaScript
6
star
32

dashMVC

MVC doodlings with Javascript
JavaScript
5
star
33

node-scale

Examples for Scaling Node.js applications with Redis, RabbitMQ and cote.js
JavaScript
5
star
34

cote-examples-currency-conversion

An example microservices application with cote
JavaScript
5
star
35

use-the-force-luke

A brain-wave app that lets you use the force
JavaScript
4
star
36

erste-starter

A starter repository for erste
CSS
4
star
37

BoilerPlate

Shell
4
star
38

IT537

JavaScript
4
star
39

epocx-experiments

Experiments with Emotiv EPOC X headset
3
star
40

wtmbjsa

JavaScript
3
star
41

fse-visualizer

A visual engine for footballSimulationEngine
JavaScript
3
star
42

node-closure-compiler

JavaScript
3
star
43

ibwturkey

JavaScript
3
star
44

wain

A topic-based news aggregator with AI.
JavaScript
3
star
45

docker-node-pm2-keymetrics

A docker image for PM2 and Keymetrics
Shell
3
star
46

node-webinar-examples

Examples for the webinar Scaling and Managing Node js Applications with Microservices
JavaScript
3
star
47

berlin-nodejs-meetup-cote

Examples for the talk "Implementing Microservices With Cote"
JavaScript
2
star
48

wtmjs

CSS
2
star
49

PapazKacti

2
star
50

docker-spa-server

2
star
51

angular-seed

2
star
52

wtmbjsa-3

Bridging APIs
JavaScript
2
star
53

tartjs-presentation

JavaScript
2
star
54

baking-soda-paste

The definitive solvent for disgusting rubber-band scrolling on iOS.
2
star
55

politburo

A micro library for building applications with Vieux architecture
JavaScript
2
star
56

docker-nodejs-build-tools

A docker image that includes various build tools for Node.js projects.
2
star
57

nodeRemote

2
star
58

Spicefinder

JavaScript
1
star
59

existing-repo

1
star
60

fs2017-microservices

Microservices example used in FullStack 2017 microservices workshop
JavaScript
1
star
61

multi-host-docker-cloud

JavaScript
1
star
62

docker-cloud-multicast

C
1
star
63

multicast-problem

JavaScript
1
star
64

rabbit-fn

JavaScript
1
star
65

wtmbjsa-5

MongoDB examples
JavaScript
1
star
66

devopspro-workshop

JavaScript
1
star
67

erste-boilerplate

HTML
1
star
68

google-maps-tsp-solver

Automatically exported from code.google.com/p/google-maps-tsp-solver
JavaScript
1
star
69

closure-test

JavaScript
1
star
70

gitfstest

1
star
71

docker-cote-monitoring-tool

Docker image for cote monitoring GUI
JavaScript
1
star
72

nomadcommerce

creative.nmdapps
CSS
1
star
73

web

JavaScript
1
star
74

colors

Rethinking Colors in Design Systems: A CSS Approach to Idiomatic Design
HTML
1
star