Skip to content

Commit d29fdef

Browse files
author
Vlad Balin
authored
Merge pull request #2 from gaperton/develop
Develop
2 parents 84a137c + 0dc1905 commit d29fdef

File tree

11 files changed

+240
-35
lines changed

11 files changed

+240
-35
lines changed

README.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,53 @@ An application for the Fitbit Ionic can quickly become a mess. This micro-framew
44

55
## Installation
66

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

910
## API
1011

1112
### DOM selectors
1213

13-
#### `function` $( selector )
14+
#### `function` $( query, [ element ] )
1415

15-
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:
16+
jQuery-style `$` query to access SVG DOM elements. No wrapping is performed, the raw element or elements array is returned.
17+
If an `element` argument is provided, the element's subtree will be searched; otherwise the search will be global.
1618

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

20-
When called without arguments, returns the `document`.
21+
- `#id-of-an-element` - will call `element.getElementById( 'id-of-an-element' )` and return en element.
22+
- `.class-name` - will call `element.getElementsByClassName( 'class-name' )` and return elements array.
23+
- `element-type` - will call `element.getElementsByTypeName( 'element-type' )` and return elements array.
24+
25+
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.
2126

2227
```javascript
2328
import { $ } from './view'
2429

2530
// Will search for #element globally
2631
$( '#element' ).style.display = 'inline';
32+
33+
// Find the root element of the screen, then show all underlying elements having "hidden" class.
34+
$( '#my-screen .hidden' ).forEach( ({ style }) => style.display = 'inline' );
35+
36+
// The same sequence made in two steps. See `$at()` function.
37+
const screen = $( '#my-screen' );
38+
$( '.hidden', screen ).forEach( ({ style }) => style.display = 'inline' );
39+
2740
```
2841

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

31-
Create the $-function to search in the given DOM subtree.
32-
Used to enforce DOM elements isolation for different views.
46+
Create the $-function to search in the given DOM subtree. Used to enforce DOM elements isolation for different views.
3347

3448
When called without arguments, returns the root element.
3549

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

39-
const $ = $at( '#myscreen' );
53+
const $ = $at( '#my-screen' );
4054

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

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

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

5266
```javascript
53-
const $ = $wrap( document );
5467
const $at = selector => $wrap( $( selector ) );
5568
```
5669

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

121135
minutes = $( '#minutes' );
122136
seconds = $( '#seconds' );
123-
render(){
137+
138+
onRender(){
124139
const { ticks } = this;
125140
this.minutes.text = Math.floor( ticks / 60 );
126141
this.seconds.text = ( ticks % 60 ).toFixed( 2 );
@@ -142,7 +157,7 @@ It's the singleton which is globally accessible through the `Application.instanc
142157
- `Application.instance` - access an application instance.
143158
- `Application.switchTo( 'screen' )` - switch to the screen which is the member of an application.
144159
- `app.screen` - property used to retrieve and set current screen view.
145-
- `app.render()` - render all the subviews, _if display is on_. Is called automaticaly when display goes on.
160+
- `app.render()` - render all the subviews, _if display is on_. It's called automaticaly when display goes on.
146161

147162
```javascript
148163
class MyApp extends Application {

app/index.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import document from "document";
2+
import { Application } from './view'
3+
import { Screen1 } from './screen1'
4+
import { Screen2 } from './screen2'
5+
6+
class MultiScreenApp extends Application {
7+
screen1 = new Screen1();
8+
screen2 = new Screen2();
9+
10+
// Called once on application's start...
11+
onMount(){
12+
// Set initial screen.
13+
// Same as Application.switchTo( 'screen1' ), which might be used to switch screen from anywhere.
14+
this.screen = this.screen2;
15+
16+
document.onkeypress = this.onKeyPress;
17+
}
18+
19+
// Event handler, must be pinned down to the class to preserve `this`.
20+
onKeyPress = ({ key }) => {
21+
if( key === 'down' ){
22+
// Just switch between two screens we have.
23+
Application.switchTo( this.screen === this.screen1 ? 'screen2' : 'screen1' );
24+
}
25+
}
26+
}
27+
28+
// Create and start the application.
29+
MultiScreenApp.start();

app/screen1.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { View, $at } from './view'
2+
import clock from 'clock'
3+
4+
const $ = $at( '#screen-1' );
5+
6+
export class Screen1 extends View {
7+
// Root view element used to show/hide the view.
8+
el = $(); // Just the #screen-1 element.
9+
10+
// Element group.
11+
time = new Time();
12+
13+
// The view state.
14+
seconds = 0;
15+
16+
onMount(){
17+
// Subscribe for the clock...
18+
clock.granularity = 'seconds';
19+
clock.ontick = this.onTick;
20+
}
21+
22+
onTick = () => {
23+
// Update the state and force render.
24+
this.seconds++;
25+
this.render();
26+
}
27+
28+
onRender(){
29+
// Render the elements group.
30+
this.time.render( this.seconds );
31+
}
32+
33+
onUnmount(){
34+
// Unsubscribe from the clock
35+
clock.granularity = 'off';
36+
clock.ontick = null;
37+
}
38+
}
39+
40+
// Elements group. Used to group the DOM elements and their update logic together.
41+
class Time {
42+
// Elements...
43+
minutes = $( '#minutes' );
44+
seconds = $( '#seconds' );
45+
46+
// UI update method(s). Can have any name, it's just the pattern.
47+
// Element groups have no lifecycle hooks, thus all the data required for UI update
48+
// must be passed as arguments.
49+
render( seconds ){
50+
this.minutes.text = ( seconds / 60 ) | 0;
51+
this.seconds.text = seconds % 60;
52+
}
53+
}

app/screen2.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { View, $at } from './view'
2+
3+
// Create the root selector for the view...
4+
const $ = $at( '#screen-2' );
5+
6+
export class Screen2 extends View {
7+
// Specify the root view element.
8+
// When set, it will be used to show/hide the view on mount and unmount.
9+
el = $();
10+
11+
// Lifecycle hook executed on `view.mount()`.
12+
onMount(){
13+
// TODO: insert subviews...
14+
// TODO: subscribe for events...
15+
}
16+
17+
// Lifecycle hook executed on `view.unmount()`.
18+
onUnmount(){
19+
// TODO: unsubscribe from events...
20+
}
21+
22+
// Custom UI update logic, executed on `view.render()`.
23+
onRender(){
24+
// TODO: put DOM manipulations here...
25+
// Call this.render() to update UI.
26+
}
27+
}
28+

view.js renamed to app/view.js

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,58 @@
11
import document from "document";
22
import { display } from "display";
33

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

6-
export function $wrap( element ){
7-
return selector => {
8-
if( selector ){
9-
const symbol = selector.substr( 1 );
10-
return selector[ 0 ] === '.' ?
11-
element.getElementsByClassName( symbol ) :
12-
element.getElementById( symbol );
6+
// Main DOM search method.
7+
export function $( query, el ){
8+
const selectors = query.match( querySplitter );
9+
let root = el || document;
10+
11+
for( let i = 0; root && i < selectors.length; i++ ){
12+
const s = selectors[ i ];
13+
root = s === '#' ? $id( selectors[ ++i ], root ) :
14+
s === '.' ? $classAndType( 'getElementsByClassName', selectors[ ++i ], root ) :
15+
$classAndType( 'getElementsByTypeName', s, root );
16+
}
17+
18+
return root;
19+
}
20+
21+
// Search subtrees by id...
22+
function $id( id, arr ){
23+
if( Array.isArray( arr ) ){
24+
const res = [];
25+
26+
for( let el of arr ){
27+
const x = el.getElementById( id );
28+
if( x ) res.push( x );
1329
}
30+
31+
return res;
32+
}
1433

15-
return element;
34+
return arr.getElementById( id );
35+
}
36+
37+
// Search subtrees by class or type...
38+
function $classAndType( method, arg, arr ){
39+
if( Array.isArray( arr ) ){
40+
const res = [];
41+
42+
for( let el of arr ){
43+
for( let el2 of el[ method ]( arg ) ){
44+
res.push( el2 );
45+
}
46+
}
47+
48+
return res;
1649
}
50+
51+
return arr[ method ]( arg );
52+
}
53+
54+
export function $wrap( element ){
55+
return selector => selector ? $( selector, element ) : element;
1756
}
1857

1958
export function $at( selector ){
@@ -66,36 +105,38 @@ export class View {
66105
}
67106

68107
render(){
69-
for( let subview of this._subviews ){
70-
subview.render();
108+
if( display.on ){
109+
for( let subview of this._subviews ){
110+
subview.render();
111+
}
112+
113+
this.onRender();
71114
}
72-
}
115+
}
116+
117+
// Callback called on render
118+
onRender(){}
73119
}
74120

75121
export class Application extends View {
76122
// Current application screen.
77123
set screen( view ){
78124
if( this._screen ) this.remove( this._screen );
125+
126+
// Poke the display so it will be on after the screen switch...
127+
display.poke();
128+
79129
this.insert( this._screen = view ).render();
80130
}
81131

82132
get screen(){ return this._screen; }
83133

84134
// Switch the screen
85135
static switchTo( screenName ){
86-
// Poke the display so it will be on after the screen switch...
87-
display.poke();
88136
const { instance } = Application;
89137
instance.screen = instance[ screenName ];
90138
}
91139

92-
render(){
93-
// Prevent render when screen is off.
94-
if( display.on ){
95-
super.render();
96-
}
97-
}
98-
99140
// Application is the singleton. Here's the instance.
100141
static instance = null;
101142

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"fitbit": {
3+
"appUUID": "d84e4e76-0ded-40f2-98f7-f4de4dc27fc4",
4+
"appType": "app",
5+
"appDisplayName": "boilerplate",
6+
"iconFile": "resources/icon.png",
7+
"wipeColor": "#607d8b",
8+
"requestedPermissions": [],
9+
"i18n": {
10+
"en": {
11+
"name": "boilerplate"
12+
}
13+
}
14+
}
15+
}

resources/index.gui

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<svg>
2+
<link rel="import" href="screen1.gui" />
3+
<link rel="import" href="screen2.gui" />
4+
</svg>

resources/screen1.gui

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<svg id="screen-1" display="none">
2+
<text id="minutes">--</text>
3+
<text id="seconds" x="50">--</text>
4+
</svg>

resources/screen2.gui

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svg id="screen-2" display="none">
2+
<text>Screen 2</text>
3+
</svg>

resources/styles.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
text {
2+
font-size: 32;
3+
font-family: System-Regular;
4+
font-weight: regular;
5+
text-length: 32;
6+
fill: white;
7+
}

0 commit comments

Comments
 (0)