Skip to content

Commit

Permalink
Merge pull request #2 from gaperton/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Vlad Balin authored Jan 8, 2018
2 parents 84a137c + 0dc1905 commit d29fdef
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 35 deletions.
43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,53 @@ An application for the Fitbit Ionic can quickly become a mess. This micro-framew

## Installation

Copy `view.js` file to your project.
This repository contains the starting boilerplate for the multi-screen project. You could copy all the files as they are,
or just copy the `/app/view.js` file to your project.

## API

### DOM selectors

#### `function` $( selector )
#### `function` $( query, [ element ] )

Global jQuery-style `$` selector to access SVG DOM elements returning raw elements. No wrapping is performed, the raw element or elements array is returned. Just two simple selectors are supported:
jQuery-style `$` query to access SVG DOM elements. No wrapping is performed, the raw element or elements array is returned.
If an `element` argument is provided, the element's subtree will be searched; otherwise the search will be global.

- `$( '#id-of-an-element' )` - will call `document.getElementById( 'id-of-an-element' )`
- `$( '.class-name' )` - will call `document.getElementsByClassName( 'class-name' )`
The `query` is the space-separated sequence of the following simple selectors:

When called without arguments, returns the `document`.
- `#id-of-an-element` - will call `element.getElementById( 'id-of-an-element' )` and return en element.
- `.class-name` - will call `element.getElementsByClassName( 'class-name' )` and return elements array.
- `element-type` - will call `element.getElementsByTypeName( 'element-type' )` and return elements array.

If all of the selectors in the query are `#id` selectors, the single element will be returned. Otherwise, an array of elements will be returned.

```javascript
import { $ } from './view'

// Will search for #element globally
$( '#element' ).style.display = 'inline';

// Find the root element of the screen, then show all underlying elements having "hidden" class.
$( '#my-screen .hidden' ).forEach( ({ style }) => style.display = 'inline' );

// The same sequence made in two steps. See `$at()` function.
const screen = $( '#my-screen' );
$( '.hidden', screen ).forEach( ({ style }) => style.display = 'inline' );

```

> Avoid repeated ad-hoc $-queries. Cache found elements when possible. See Elements Group pattern.
#### `function` $at( id-selector )

Create the $-function to search in the given DOM subtree.
Used to enforce DOM elements isolation for different views.
Create the $-function to search in the given DOM subtree. Used to enforce DOM elements isolation for different views.

When called without arguments, returns the root element.

```javascript
import { $at } from './view'

const $ = $at( '#myscreen' );
const $ = $at( '#my-screen' );

// Make #myscreen visible
$().style.display = 'inline';
Expand All @@ -47,10 +61,9 @@ $( '#element' ).style.display = 'inline';

#### `function` $wrap( element )

Create the $-function to search in the given DOM subtree wrapping the given element. Internally,
Create the $-function to search in the given DOM subtree wrapping the given element.

```javascript
const $ = $wrap( document );
const $at = selector => $wrap( $( selector ) );
```

Expand Down Expand Up @@ -92,7 +105,8 @@ View is the stateful group of elements. The difference from the elements group i
- `view.el` - optional root view element. Used to show and hide the view when its mounted and unmounted.
- `view.mount()` - make the `subview.el` visible, call the `subview.onMount()` hook.
- `view.onMount()` - place to insert subviews and register events listeners.
- `view.render()` - render the view and all of its subviews.
- `view.render()` - render the view and all of its subviews if the display is on. No-op otherwise.
- `view.onRender()` - place actual UI update code here.
- `view.unmount()` - hide the `subview.el`, unmount all the subviews, call the `view.onUnmount()` hook.
- `view.onUnmount()` - place to unregister events listeners.
- `view.insert( subview )` - insert and mount the subview.
Expand Down Expand Up @@ -120,7 +134,8 @@ class Timer extends View {

minutes = $( '#minutes' );
seconds = $( '#seconds' );
render(){

onRender(){
const { ticks } = this;
this.minutes.text = Math.floor( ticks / 60 );
this.seconds.text = ( ticks % 60 ).toFixed( 2 );
Expand All @@ -142,7 +157,7 @@ It's the singleton which is globally accessible through the `Application.instanc
- `Application.instance` - access an application instance.
- `Application.switchTo( 'screen' )` - switch to the screen which is the member of an application.
- `app.screen` - property used to retrieve and set current screen view.
- `app.render()` - render all the subviews, _if display is on_. Is called automaticaly when display goes on.
- `app.render()` - render all the subviews, _if display is on_. It's called automaticaly when display goes on.

```javascript
class MyApp extends Application {
Expand Down
29 changes: 29 additions & 0 deletions app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import document from "document";
import { Application } from './view'
import { Screen1 } from './screen1'
import { Screen2 } from './screen2'

class MultiScreenApp extends Application {
screen1 = new Screen1();
screen2 = new Screen2();

// Called once on application's start...
onMount(){
// Set initial screen.
// Same as Application.switchTo( 'screen1' ), which might be used to switch screen from anywhere.
this.screen = this.screen2;

document.onkeypress = this.onKeyPress;
}

// Event handler, must be pinned down to the class to preserve `this`.
onKeyPress = ({ key }) => {
if( key === 'down' ){
// Just switch between two screens we have.
Application.switchTo( this.screen === this.screen1 ? 'screen2' : 'screen1' );
}
}
}

// Create and start the application.
MultiScreenApp.start();
53 changes: 53 additions & 0 deletions app/screen1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { View, $at } from './view'
import clock from 'clock'

const $ = $at( '#screen-1' );

export class Screen1 extends View {
// Root view element used to show/hide the view.
el = $(); // Just the #screen-1 element.

// Element group.
time = new Time();

// The view state.
seconds = 0;

onMount(){
// Subscribe for the clock...
clock.granularity = 'seconds';
clock.ontick = this.onTick;
}

onTick = () => {
// Update the state and force render.
this.seconds++;
this.render();
}

onRender(){
// Render the elements group.
this.time.render( this.seconds );
}

onUnmount(){
// Unsubscribe from the clock
clock.granularity = 'off';
clock.ontick = null;
}
}

// Elements group. Used to group the DOM elements and their update logic together.
class Time {
// Elements...
minutes = $( '#minutes' );
seconds = $( '#seconds' );

// UI update method(s). Can have any name, it's just the pattern.
// Element groups have no lifecycle hooks, thus all the data required for UI update
// must be passed as arguments.
render( seconds ){
this.minutes.text = ( seconds / 60 ) | 0;
this.seconds.text = seconds % 60;
}
}
28 changes: 28 additions & 0 deletions app/screen2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { View, $at } from './view'

// Create the root selector for the view...
const $ = $at( '#screen-2' );

export class Screen2 extends View {
// Specify the root view element.
// When set, it will be used to show/hide the view on mount and unmount.
el = $();

// Lifecycle hook executed on `view.mount()`.
onMount(){
// TODO: insert subviews...
// TODO: subscribe for events...
}

// Lifecycle hook executed on `view.unmount()`.
onUnmount(){
// TODO: unsubscribe from events...
}

// Custom UI update logic, executed on `view.render()`.
onRender(){
// TODO: put DOM manipulations here...
// Call this.render() to update UI.
}
}

83 changes: 62 additions & 21 deletions view.js → app/view.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,19 +1,58 @@
import document from "document";
import { display } from "display";

export const $ = $wrap( document );
const querySplitter = /\.|#|\S+/g;

export function $wrap( element ){
return selector => {
if( selector ){
const symbol = selector.substr( 1 );
return selector[ 0 ] === '.' ?
element.getElementsByClassName( symbol ) :
element.getElementById( symbol );
// Main DOM search method.
export function $( query, el ){
const selectors = query.match( querySplitter );
let root = el || document;

for( let i = 0; root && i < selectors.length; i++ ){
const s = selectors[ i ];
root = s === '#' ? $id( selectors[ ++i ], root ) :
s === '.' ? $classAndType( 'getElementsByClassName', selectors[ ++i ], root ) :
$classAndType( 'getElementsByTypeName', s, root );
}

return root;
}

// Search subtrees by id...
function $id( id, arr ){
if( Array.isArray( arr ) ){
const res = [];

for( let el of arr ){
const x = el.getElementById( id );
if( x ) res.push( x );
}

return res;
}

return element;
return arr.getElementById( id );
}

// Search subtrees by class or type...
function $classAndType( method, arg, arr ){
if( Array.isArray( arr ) ){
const res = [];

for( let el of arr ){
for( let el2 of el[ method ]( arg ) ){
res.push( el2 );
}
}

return res;
}

return arr[ method ]( arg );
}

export function $wrap( element ){
return selector => selector ? $( selector, element ) : element;
}

export function $at( selector ){
Expand Down Expand Up @@ -66,36 +105,38 @@ export class View {
}

render(){
for( let subview of this._subviews ){
subview.render();
if( display.on ){
for( let subview of this._subviews ){
subview.render();
}

this.onRender();
}
}
}

// Callback called on render
onRender(){}
}

export class Application extends View {
// Current application screen.
set screen( view ){
if( this._screen ) this.remove( this._screen );

// Poke the display so it will be on after the screen switch...
display.poke();

this.insert( this._screen = view ).render();
}

get screen(){ return this._screen; }

// Switch the screen
static switchTo( screenName ){
// Poke the display so it will be on after the screen switch...
display.poke();
const { instance } = Application;
instance.screen = instance[ screenName ];
}

render(){
// Prevent render when screen is off.
if( display.on ){
super.render();
}
}

// Application is the singleton. Here's the instance.
static instance = null;

Expand Down
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"fitbit": {
"appUUID": "d84e4e76-0ded-40f2-98f7-f4de4dc27fc4",
"appType": "app",
"appDisplayName": "boilerplate",
"iconFile": "resources/icon.png",
"wipeColor": "#607d8b",
"requestedPermissions": [],
"i18n": {
"en": {
"name": "boilerplate"
}
}
}
}
4 changes: 4 additions & 0 deletions resources/index.gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<svg>
<link rel="import" href="screen1.gui" />
<link rel="import" href="screen2.gui" />
</svg>
4 changes: 4 additions & 0 deletions resources/screen1.gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<svg id="screen-1" display="none">
<text id="minutes">--</text>
<text id="seconds" x="50">--</text>
</svg>
3 changes: 3 additions & 0 deletions resources/screen2.gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<svg id="screen-2" display="none">
<text>Screen 2</text>
</svg>
7 changes: 7 additions & 0 deletions resources/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
text {
font-size: 32;
font-family: System-Regular;
font-weight: regular;
text-length: 32;
fill: white;
}
6 changes: 6 additions & 0 deletions resources/widgets.gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<svg>
<defs>
<link rel="stylesheet" href="styles.css" />
<link rel="import" href="/mnt/sysassets/widgets_common.gui" />
</defs>
</svg>

0 comments on commit d29fdef

Please sign in to comment.