{"id":13802175,"url":"https://github.com/dashersw/erste","last_synced_at":"2026-03-17T20:36:16.788Z","repository":{"id":40593385,"uuid":"83065336","full_name":"dashersw/erste","owner":"dashersw","description":"Your first choice for hybrid mobile applications","archived":false,"fork":false,"pushed_at":"2023-04-10T08:21:29.000Z","size":923,"stargazers_count":275,"open_issues_count":5,"forks_count":30,"subscribers_count":14,"default_branch":"master","last_synced_at":"2026-02-15T00:44:28.800Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dashersw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-02-24T17:17:52.000Z","updated_at":"2025-10-10T09:11:56.000Z","dependencies_parsed_at":"2023-02-09T02:01:29.268Z","dependency_job_id":"a8e335c5-3fce-44f5-a94f-be64a3e818ac","html_url":"https://github.com/dashersw/erste","commit_stats":{"total_commits":202,"total_committers":13,"mean_commits":"15.538461538461538","dds":0.09405940594059403,"last_synced_commit":"154aaff9a0aabed5322f896dec2123774a8fe76b"},"previous_names":["dashersw/erste.js"],"tags_count":53,"template":false,"template_full_name":null,"purl":"pkg:github/dashersw/erste","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Ferste","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Ferste/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Ferste/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Ferste/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dashersw","download_url":"https://codeload.github.com/dashersw/erste/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashersw%2Ferste/sbom","scorecard":{"id":323712,"data":{"date":"2025-08-11","repo":{"name":"github.com/dashersw/erste","commit":"154aaff9a0aabed5322f896dec2123774a8fe76b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.9,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 3/29 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"39 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-897m-rjf5-jp39","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-v4rh-8p82-6h5w","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T01:58:38.776Z","repository_id":40593385,"created_at":"2025-08-18T01:58:38.777Z","updated_at":"2025-08-18T01:58:38.777Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30631384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T17:32:55.572Z","status":"ssl_error","status_checked_at":"2026-03-17T17:32:38.732Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T00:01:38.146Z","updated_at":"2026-03-17T20:36:16.759Z","avatar_url":"https://github.com/dashersw.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://raw.githubusercontent.com/dashersw/erste/master/resources/logo.png\" height=\"180\" alt=\"Erste\" /\u003e\n\n[![npm version](https://badge.fury.io/js/erste.svg)](https://badge.fury.io/js/erste)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dashersw/erste.js/master/LICENSE)\n[![API Reference Documentation](https://doxdox.org/images/badge-flat.svg)](http://doxdox.org/dashersw/erste/master)\n\n# JavaScript view library for building performant hybrid mobile applications\n\n**erste.js is a zero-hype view library with an attitude. It’s built for achieving maximum performance on mobile devices.**\n\n## Features\n- Lightweight, 7kb minified \u0026 gzipped\n- No dependencies\n- No magic—as declarative as it should be and no more\n- No pitfalls—use good old DOM APIs, HTML5 \u0026 CSS3 to build modern apps\n- Clean, structured and approachable API\n\n## Overview\n\n```js\n// 1. Import erste,\nimport {Component} from 'erste';\n\n// 2. Create your application,\nclass App extends Component {\n    constructor() {\n        super();\n        this.counter = 0;\n    }\n\n    // 3. Arrange your view,\n    template() {\n        return `\n        \u003cdiv\u003e\n            \u003ch1\u003e${this.counter}\u003c/h1\u003e\n            \u003cbutton class=\"increment\"\u003eIncrement\u003c/button\u003e\n            \u003cbutton class=\"decrement\"\u003eDecrement\u003c/button\u003e\n        \u003c/div\u003e\n        `;\n    }\n    // 4. Create your methods,\n    increment() { this.$('h1').innerText = ++this.counter; }\n    decrement() { this.$('h1').innerText = --this.counter; }\n\n    // 5. Bind your events.\n    get events() {\n        return {\n            'tap': {\n                '.increment': this.increment,\n                '.decrement': this.decrement\n            }\n        }\n    }\n}\n\n// 6. Make your application run.\nnew App().render(document.body);\n```\n\n## Table of Contents\n\n  * [Motivation](#motivation)\n  * [Installation](#installation)\n     * [Direct download](#direct-download)\n     * [Using npm](#using-npm)\n     * [Builds with Closure Compiler](#builds-with-closure-compiler)\n  * [Example application](#example-application)\n  * [Building your first application](#building-your-first-application)\n     * [The root view](#the-root-view)\n     * [DOM Events](#dom-events)\n     * [How DOM event management works](#how-dom-event-management-works)\n  * [Creating other components](#creating-other-components)\n     * [Lifecycle management of components](#lifecycle-management-of-components)\n        * [Option 1: Declarative](#option-1-declarative)\n        * [Option 2: Imperative with erste.js API](#option-2-imperative-with-erstejs-api)\n        * [Option 3: Imperative with DOM API](#option-3-imperative-with-dom-api)\n  * [Creating master and detail views, or introducing the ViewManager](#creating-master-and-detail-views-or-introducing-the-viewmanager)\n     * [Going back to the master view](#going-back-to-the-master-view)\n     * [The back gesture](#the-back-gesture)\n  * [Conclusion](#conclusion)\n  * [License](#license)\n\n## Motivation\nBuilding 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?\n\nWe 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 '\u003c' and '\u0026'.\n\nerste.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.\n\n## Installation\n### Direct download\n- [Minified version - 7kb gzipped](https://raw.githubusercontent.com/dashersw/erste.js/master/dist/erste.js)\n\n### Using npm\n```bash\nnpm install --save erste\n```\n\n### Builds with Closure Compiler\nerste.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.\n\n## Example application\n\nHead over to [erste.js-demo](https://github.com/dashersw/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.\n\n## Building your first application\nGUI 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.\n\n### The root view\nEvery application starts with a root view. It is the first thing you put in your `\u003cbody\u003e` tag, a single view that includes all of your application.\n\nWrite your root view by extending from the `View` class in erste.js;\n\n`root-view.js`:\n\n```js\nimport {View} from 'erste';\n\nclass RootView extends View {\n    template() {\n        return `\n        \u003croot-view\u003e\n            \u003ch1\u003eHello world!\u003c/h1\u003e\n        \u003c/root-view\u003e\n        `;\n    }\n}\n```\n\nThe 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 `\u003croot-view\u003e` 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 `\u003cdiv\u003e`s. Actually, any element will do fine as a template, and a block element makes sense as the root view.\n\nYou now should insert this view into the DOM. There are two ways to do this, the simplest being;\n\n`index.js`:\n\n```js\nimport RootView from './root-view';\n\ndocument.body.innerHTML = new RootView();\n```\n\nThis 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;\n\n`index.js`:\n\n```\nimport RootView from './root-view';\n\nnew RootView().render();\n```\n\nNote 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);`\n\nWe will discuss the implications of both approaches in a further topic.\n\n### DOM Events\nHandling 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.\n\nerste.js also has a built-in gesture recognizer that provides touch events like `tap`, `longTap`, `swipe` and more.\n\nLet's listen to the tap event on the button in our root view and do something meaningless with it;\n\n`root-view.js`:\n\n```js\nimport {View} from 'erste';\n\nclass RootView extends View {\n    template() {\n        return `\n        \u003croot-view\u003e\n            \u003ch1\u003eHello world!\u003c/h1\u003e\n            \u003cbutton\u003eTap me!\u003c/button\u003e\n        \u003c/root-view\u003e\n        `;\n    }\n\n    onTapButton() {\n        this.$('h1').innerText = 'Thanks for the tap!';\n    }\n\n    get events() {\n        return {\n            'tap': {\n                'button': this.onTapButton\n            }\n        }\n    }\n}\n```\n\nThe 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.\n\nSecondly, 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.\n\nHowever, 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.\n\n### How DOM event management works\nerste.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.\n\n## Creating other components\nerste.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.\n\nLet’s turn our simple button and label into a standalone component.\n\n`button-with-label.js`:\n\n```js\nimport {Component} from 'erste';\n\nclass ButtonWithLabel extends Component {\n    template() {\n        return `\n        \u003cbutton-with-label\u003e\n            \u003ch1\u003eHello world!\u003c/h1\u003e\n            \u003cbutton\u003eTap me!\u003c/button\u003e\n        \u003c/button-with-label\u003e\n        `;\n    }\n\n    onTapButton() {\n        this.$('h1').innerText = 'Thanks for the tap!';\n    }\n\n    get events() {\n        return {\n            'tap': {\n                'button': this.onTapButton\n            }\n        }\n    }\n}\n```\n\nWe 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 `\u003ch1\u003e` and `\u003cbutton\u003e` in `\u003cbutton-with-label\u003e`.\n\nThen the root view simply becomes;\n\n`root-view.js`:\n\n```js\nimport {View} from 'erste';\nimport ButtonWithLabel from './button-with-label';\n\nclass RootView extends View {\n    constructor() {\n        super();\n\n        this.buttonWithLabel = new ButtonWithLabel();\n    }\n\n    template() {\n        return `\n        \u003croot-view\u003e\n            ${this.buttonWithLabel}\n        \u003c/root-view\u003e\n        `;\n    }\n}\n```\n\nHere is how declarative erste.js is; views and components can include other components by simply including them within the template literals.\n\n### Lifecycle management of components\nIn 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.\n\n#### Option 1: Declarative\nWe 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:\n\n`root-view.js`:\n\n```js\n/* … previous code … */\n\n    template() {\n        this.buttonWithLabel = new ButtonWithLabel();\n\n        return `\n        \u003croot-view\u003e\n            ${this.buttonWithLabel}\n        \u003c/root-view\u003e\n        `;\n    }\n```\n\n#### Option 2: Imperative with erste.js API\nThe 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:\n\n`root-view.js`:\n\n```js\nimport {View} from 'erste';\nimport ButtonWithLabel from './button-with-label';\n\nclass RootView extends View {\n    onAfterRender() {\n        this.buttonWithLabel = new ButtonWithLabel();\n\n        this.buttonWithLabel.render(this.el);\n    }\n\n    template() {\n        return `\u003croot-view\u003e\u003c/root-view\u003e`;\n    }\n}\n```\n\nOf course, you could just as well instantiate `ButtonWithLabel` in `RootView`’s constructor and render it within `onAfterRender`.\n\n#### Option 3: Imperative with DOM API\nIf you don’t like to remember custom `render` methods and such, you can also use the native `appendChild` DOM API.\n\nIn this case, the `RootView` would look like:\n\n`root-view.js`:\n\n```js\nimport {View} from 'erste';\nimport ButtonWithLabel from './button-with-label';\n\nclass RootView extends View {\n    onAfterRender() {\n        this.buttonWithLabel = new ButtonWithLabel();\n\n        this.el.appendChild(this.buttonWithLabel.el);\n    }\n\n    template() {\n        return `\u003croot-view\u003e\u003c/root-view\u003e`;\n    }\n}\n```\n\nNotice 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.\n\n## Creating master and detail views, or introducing the ViewManager\nMobile apps make extensive use of the master / detail view scheme, and it’s a first citizen in erste.js as well.\n\n`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.\n\nLet’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;\n\n`index.js`:\n\n```js\nimport {ViewManager} from 'erste';\nimport RootView from './root-view';\n\nvar vm = new ViewManager();\nvar rootView = new RootView();\nrootView.vm = vm;\n\nvm.setCurrentView(rootView);\n```\n\n`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.\n\nLet’s first build our detail view.\n\n`detail-view.js`:\n\n```js\nimport {View} from 'erste';\n\nclass DetailView extends View {\n    constructor(item) {\n        super();\n\n        this.item = item;\n    }\n\n    template() {\n        return `\n        \u003cdetail-view\u003e\n            \u003cp\u003e${this.item}\u003c/p\u003e\n        \u003c/detail-view\u003e\n        `;\n    }\n}\n```\n\nIt simply receives an item in its constructor and prints it in the template.\n\nHere is a sample master view implementation and how we can make use of our new detail view;\n\n`root-view.js`:\n\n```js\nimport {View} from 'erste';\nimport DetailView from './detail-view';\n\nclass RootView extends View {\n    constructor() {\n        super();\n\n        this.items = [1, 2, 3];\n    }\n\n    onItemTap(e) {\n        var targetIndex = e.targetEl.getAttribute('data-index');\n        var item = this.items[targetIndex];\n\n        var detailView = new DetailView(item);\n\n        this.vm.pull(detailView);\n    }\n\n    template_item(item, index) {\n        return `\u003cdiv data-index=${index}\u003e${item}\u003c/div\u003e`;\n    }\n\n    template() {\n        return `\n        \u003croot-view\u003e\n            ${this.items.map(this.template_item).join('')}\n        \u003c/root-view\u003e\n        `;\n    }\n\n    get events() {\n        return {\n            'tap': {\n                'div': this.onItemTap\n            }\n        };\n    }\n}\n```\n\nThe 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.\n\nCongratulations! Now you have a sample master / detail view application! Read on for more advanced use cases!\n\n### Going back to the master view\nIn 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.\n\nLater, you can call `vm.push()` whenever you want, and the view manager will go back to the master view, disposing the detail view.\n\n### The back gesture\nerste.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.\n\nThis 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`.\nBy default back gesture touch target width is 100px on the left side of the view. To alter it you can set `backGestureTouchTargetWidth`.\n\n`detail-view.js`:\n\n```js\n    /* … previous code … */\n    constructor(item) {\n        super();\n\n        this.item = item;\n\n        this.supportsBackGesture = true;\n        this.backGestureTouchTargetWidth = window.innerWidth / 2; // you can set as half of the screen's width.\n    }\n```\n\nNow you will be able to navigate back to the original view with a swipe!\n\n## Conclusion\nerste.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](https://github.com/dashersw/erste.js-demo) to see it all in action.\n\n## License\n\nMIT License\n\nCopyright (c) 2017 Armagan Amcalar\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdashersw%2Ferste","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdashersw%2Ferste","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdashersw%2Ferste/lists"}