UNPKG

keet

Version:

Minimalist view layer for the web

297 lines (221 loc) 8.5 kB
<h1 align="center"> <!-- Logo --> <a href="https://syarul.github.io/keet/" alt="keet.js"> <img src="website/static/img/keet_temp_logo.png" width="150" alt="Keet Logo"/> </a> <br/> <!-- AUTO-GENERATED-CONTENT:START (VER) --> # Keet v4.2.4 <!-- AUTO-GENERATED-CONTENT:END --> </h1> <!-- AUTO-GENERATED-CONTENT:START (SHEILDS) --> [![npm package](https://img.shields.io/badge/npm-4.2.4-blue.svg)](https://www.npmjs.com/package/keet) [![browser build](https://img.shields.io/badge/rawgit-4.2.4-ff69b4.svg)](https://cdn.rawgit.com/syarul/keet/master/keet-min.js) [![npm module downloads](https://img.shields.io/npm/dt/keet.svg)](https://www.npmjs.com/package/keet) [![Build Status](https://travis-ci.org/syarul/keet.svg?branch=master)](https://travis-ci.org/syarul/keet) [![Coverage Status](https://coveralls.io/repos/github/syarul/keet/badge.svg?branch=master)](https://coveralls.io/github/syarul/keet?branch=master) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) <!-- AUTO-GENERATED-CONTENT:START (SHEILDS) --> <!-- AUTO-GENERATED-CONTENT:END --> Minimalist view layer for the web. ## What is Keet > *Keet* specific goal is to offer freedom of [cumbersome frameworks](https://medium.com/dailyjs/the-deepest-reason-why-modern-javascript-frameworks-exist-933b86ebc445), familiar/vanilla code structures and a possible remedy to [*choice paralysis*](https://the-pastry-box-project.net/addy-osmani/2014-January-19). Generally *Keet* is more flexible, decent [render performance](https://raw.githubusercontent.com/syarul/preact-perf/master/img/2018-18-08.png), loosely coupled and has shallow learning curve. It's also only 6kb gzip in size. ## Getting Started To try out Keet is to include it from a CDN or npm. Create a HTML file:- <!-- AUTO-GENERATED-CONTENT:START (HTML) --> ```html <html> <head> <script src="//cdn.rawgit.com/syarul/keet/esm-4/keet-min.js"></script> </head> <body> <div id="app"></div> </body> <script> // your codes goes here </script> </html> ``` <!-- AUTO-GENERATED-CONTENT:END --> Or from npm:- ```npm install keet``` ## Resources <!-- AUTO-GENERATED-CONTENT:START (RESOURCES) --> - Usage/API Examples - https://github.com/syarul/keet/tree/esm-5/examples - TodoMVC Implementation - https://github.com/syarul/keetjs-todomvc - Performance (opinionated) - https://github.com/syarul/preact-perf <!-- AUTO-GENERATED-CONTENT:END --> ## Sample Usage ### Hello World Start by constructing a class expression as child of ```Keet```. Supply a string argument to the component method ```mount```. Within the string, you can assign a state within handlebars i.e: ```{{<myState>}}```. > NOTE: You also may use ternary expression as your state i.e: ```{{<ternaryState>?show:hide}}``` where ```<ternaryState>``` is a ```boolean``` value <!-- AUTO-GENERATED-CONTENT:START (HELLO) --> ```js import Keet from 'keet' import { getId } from 'keet/utils' class App extends Keet { el = 'app' state = 'World' componentDidMount () { console.assert(getId('app').innerHTML === 'Hello World', 'hello test') } render () { return 'Hello {{state}}' } } export default new App() ``` <!-- AUTO-GENERATED-CONTENT:END --> ### Counter Basic idea how we can create a simple counter <!-- AUTO-GENERATED-CONTENT:START (COUNTER) --> ```js /* global Event */ import Keet, { html } from 'keet' import { getId } from 'keet/utils' let counter class App extends Keet { el = 'app' count = 0 add (evt) { this.count++ } componentDidMount () { const click = new Event('click', { bubbles: true, cancelable: true }) counter = getId('counter') counter.dispatchEvent(click) } componentDidUpdate () { console.assert(counter.innerHTML === '1', 'counter test') } render () { return html` <button id="counter" k-click="add()"> {{count}} </button> ` } } export default new App() ``` <!-- AUTO-GENERATED-CONTENT:END --> ## Dynamic Nodes The traditional way, is you assign ```display:none``` to style attributes or use css, which still use resources. To use it wrap your html string with ```{{?<state>}}<myDynamicNode>{{/<state>}}``` and assign boolean value to the state. > NOTE: With dynamic nodes it complete remove your node from the DOM and free up your resources which is good on mobile devices. <!-- AUTO-GENERATED-CONTENT:START (CONDITIONAL_NODES) --> ```js import Keet, { html } from 'keet' class App extends Keet { el = 'app' show = true toggle () { this.show = !this.show } render () { return html` <button id="toggle" k-click="toggle()" attr="{{show?foo:bar}}" style="color: {{show?red:blue}};" {{show?testme:test}}>toggle</button> <div id="1">one</div> <!-- {{?show}} --> <div id="2">two</div> <div id="3">three</div> <div id="4">four</div> <!-- {{/show}} --> <div id="5">five</div> ` } } export default new App() ``` <!-- AUTO-GENERATED-CONTENT:END --> ## List Rendering To map an array to elements use the ```{{model:<myModelName>}}<myModelTemplate>{{/model:<myModelName>}}```. It has basic methods add/update/remove. To go beyond that requirement you could ```extend``` the ```class``` method of ```createModel``` > NOTE: Only mutation methods has attached listener, so usage of ```map``` ```filter``` ```reduce``` ```concat``` or directly reassigned new value to the ```list``` is encouraged and does not affect the ```dom-diffing``` efficiency. <!-- AUTO-GENERATED-CONTENT:START (MODEL) --> ```js import Keet, { html, CreateModel } from 'keet' import { getId } from 'keet/utils' class App extends Keet { el = 'app' color = 'red' bold = 'bold' task = new CreateModel() componentWillMount () { this.task.subscribe(model => this.callBatchPoolUpdate() ) } componentDidMount () { let taskList = JSON.parse(JSON.stringify(this.task.list)) // update a task this.task.update({ ...taskList[0], taskName: 'sleep', complete: true }) this.task.destroy(taskList[taskList.length - 1]) } componentDidUpdate () { console.assert(getId('list').childNodes.length === 4) } changeColor(color) { this.color = color } render () { let name = 'myModel' let taskName = ['run', 'jog', 'walk', 'swim', 'roll'] taskName.map((task, i) => { this.task.add({ taskName: task, complete: i % 2 !== 0 }) }) return html` <h1>${name}</h1> <ul id="list" style="color: {{color}}; font-weight: {{bold}};"> <!-- {{model:task}} --> <li> {{taskName}} <input type="checkbox" checked="{{complete?checked:null}}"> </li> <!-- {{/model:task}} --> </ul> ` } } const app = new App() setTimeout(() => { app.changeColor('blue') }, 1000) export default app ``` <!-- AUTO-GENERATED-CONTENT:END --> ## Sub Component Writing everything in a single file is not advisable, where you should split multiple components. To have multiple components together, use the sub-component feature with ```{{component:<mySubComponent>}}```. > NOTE: Always has a root-element with id on sub-components html template literal, so the main component able to lookup for insertion. <!-- AUTO-GENERATED-CONTENT:START (SUB_COMPONENT) --> ```js import Keet from 'keet' import { html } from 'keet/utils' class Sub extends Keet { // provide the node id where this sub will rendered el = 'sub' } const sub = new Sub() sub.mount(html` <div id="sub"> this is a sub-component </div> `) class App extends Keet { subc = sub } const app = new App() app.mount(html` <div id="container"> <div>parent</div> <!-- {{component:subc}} --> </div> `).link('app') ``` <!-- AUTO-GENERATED-CONTENT:END --> ## Roadmap - Adding documentation ( sorry for the time being ) - Arbiter custom option ( support for node diffing algorithm by checking boolean attributes added as option rather than hard coded ) - do [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark) test ## License The MIT License (MIT) Copyright (c) 2018 Shahrul Nizam Selamat