Brookjs components strawman proposal

import { h, component } from 'brookjs'
import { editClick } from '../actions'
// Changes only occur when bound to a stream
export default component({
render: props$ => (
<li>
{/* Text changes when a value is emitted. */}
<p class="todo__name">
{props$.map(props => props.text)}
</p>
{/* Maps a stream of events to actions. */}
<button onClick={event$ => event$.map(editClick)}>
{'Edit todo'}
</button>
</li>
)
})
TodoItem.js
import { h, component } from 'brookjs'
import TodoItem from './TodoItem'
export default component({
render: props$ => (
<div>
<h2>My Todos</h2>
{/**
* `order` provides sequence todos should appear
* `dict` provides a performant instance lookup
*/}
<ul>
{props$.map(todos => todos.order.map(key => (
<TodoItem
props$={props$.map(todos => todos.dict[key])}
key={key}
preplug={instance$ => instance$.map(action => ({
...action,
meta: { key }
})} />
)))}
<ul>
</div>
)
})
TodoList.js
import { h, component } from 'brookjs'
import TodoList from './TodoList'
export default component({
render: props$ => (
<div>
<h1>Todo App</h1>
{/**
* Individual attributes respond to observables
* Performance optimized inline
*/}
<input value={props$.map(props => props.editing).skipDuplicates()}
onInput={event$ => event$.map(event => editTodo(event.target.value))} />
<button onClick={event$ => event$.map(addTodo)}>Add Todo</button>
<TodoList props$={props$.map(props => props.todos)} />
</div>
)
})
App.js
import { createStore, applyMiddleware } from 'redux'
import { h, observeDelta, Kefir } from brookjs
import { App } from './components'
import { selectProps } from './selectors'
const store = createStore(
(state, action) => state, // reducer
window.__INITIAL_STATE__ || {},
applyMiddleware(observeDelta(
/* register deltas here */
)
)
const state$ = Kefir.fromESObservable(store)
/**
* `mount` thus takes the DOM to mount
* and the element to bind it to, and
* returns a stream. Note that because
* of how streams work, nothing happens
* until the stream is observed.
*/
const view$ = mount(
<App props$={selectProps(state$)} />,
document.getElementById('app')
)
view$.observe(store.dispatch)
client.js
import { h, component, list } from 'brookjs'
import TodoItem from './TodoItem'
export default component({
render: props$ => (
<div>
<h2>My Todos</h2>
<ul>
{/* Must be a stream of objects with `order` & `dict` */}
{list(props$, (props$, key) => (
<TodoItem
props$={props$}
key={key}
preplug={instance$ => instance$.map(action => ({
...action,
meta: { key }
})} />
)}
<ul>
</div>
)
})
TodoListWithIterationHelper.js
import { createStore, applyMiddleware } from 'redux'
import { h, observeDelta, Kefir } from brookjs
import { App } from './components'
import { selectProps } from './selectors'
const el = document.getElementById('app')
const store = createStore(
(state, action) => state,
window.__INITIAL_STATE__ || {},
applyMiddleware(observeDelta(
/* register deltas here */
domDelta({ el, selectProps, view: App })
)
)
// Everything is bound to the store immediately,
// but an init action makes sure everything waits
// until the store is fully instantiated.
store.dispatch(init())
client-with-domDelta.js

Pipe & Compose - Functional Light JS v2 from Front-End Masters

const pipe = (...funcs) => (...args) =>
funcs.reduce((args, func) => [func(...args)], args)[0];
pipe.js
// just reverse the funcs array...?
const compose = (...funcs) => (...args) =>
funcs.reverse().reduce((args, func) => [func(...args)], args)[0];
compose.js

Point-Free Style - Functional Light JS v2 from Front-End Masters

function output(txt) {
console.log(txt);
}
function printIf(predicate) {
return function(msg) {
if (predicate(msg)) {
output(msg);
}
};
}
function isShortEnough(str) {
return str.length <= 5;
}
function isLongEnough(str) {
return !isShortEnough(str);
}
var msg1 = "Hello";
var msg2 = msg1 + " World";
printIf(isShortEnough)(msg1); // Hello
printIf(isShortEnough)(msg2);
printIf(isLongEnough)(msg1);
printIf(isLongEnough)(msg2); // Hello World
problem.js
const output = console.log.bind(console);
const when = func => pred => val => {
if (predicate(val)) {
func(val);
}
};
const not = func => val => !func(val);
const printIf = when(output);
const isShortEnough = str => str.length <= 5;
const isLongEnough = not(isShortEnough);
point-free.js

Purifying Functions - Functional Light JS v2 from Front-End Masters

function foo(x) {
y++;
z = x * y;
}
var y = 5, z;
foo(20);
z; // 120
foo(25);
z; // 175
problem.js
function Foo(x, y) {
var z;
function foo(x) {
y++;
z = x * y;
}
foo(x);
return [y, z];
}
wraps.js
function foo(x) {
y++;
z = x * y;
}
function Foo(x) {
var [origY, origZ] = [y,z];
foo(x);
var [newY, newZ] = [y, z];
[y, z] = [origY, origZ];
return [newY, newZ];
}
adapts.js

Simple Path

const getPath = (path, target) =>
path.split('.')
.reduce((value, key) => value[key], target);
// Usage
const path = 'a.b.c';
const target = {
a: {
b: {
c: true
}
}
};
assert(true === getPath(path, target));
get.js

Removed brookjs Actions

/**
* Value change Action type.
*
* @type {string}
*/
export const VALUE_CHANGE = 'VALUE_CHANGE';
/**
* Create a new Value Change action.
*
* @param {string} value - Target value.
* @returns {Action} Value Change action object.
*/
export function valueEventAction(value) {
return {
type: VALUE_CHANGE,
payload: { value }
};
}
/**
* Field focus event action type.
*
* @type {string}
*/
export const FIELD_FOCUS = 'FIELD_FOCUS';
/**
* Create a FIELD_FOCUS action object.
*
* @param {string} name - Field name.
* @returns {Action} FIELD_FOCUS action.
*/
export function fieldFocusAction(name) {
return {
type: FIELD_FOCUS,
payload: { name }
};
}
/**
* Checked change constant.
*
* @type {string}
*/
export const CHECKED_CHANGE = 'CHECKED_CHANGE';
/**
* Create a new Checked Change action.
*
* @param {boolean} value - Target checked.
* @returns {Action} Checked Change action object.
*/
export function checkedEventAction(value) {
return {
type: CHECKED_CHANGE,
payload: { value }
};
}
/**
* Click event Action type.
*
* @type {string}
*/
export const CLICK = 'CLICK';
/**
* Create a new Clicked Action
*
* @returns {Action} Clicked action object.
*/
export function clickedEventAction() {
return { type: CLICK };
}
removed-actions.js

Observable Rendering

import Kefir from 'kefir';
export default function EditorComponent(el, props$) {
const keyup$ = Kefir.fromEvents(el, 'keyup');
const keydown$ = Kefir.fromEvents(el, 'keydown');
return props$.sampledBy(keyup$.debounce(150))
.flatMapLatest(props =>
createRenderStream(props).takeUntil(keydown$)
);
function createRenderStream(props) {
return Kefir.stream(emitter => {
const loop = requestAnimationFrame(() => {
// Update the element
emitter.end();
});
return () => cancelAnimationFrame(loop);
});
}
};
component.js

IDs & Classes: Problem

<!DOCTYPE html>
<html>
<head>
<title>Movies and Shows</title>
</head>
<body>
<h1>Amazing Movies and TV Shows Ever!</h1>
<ol>
<li>Finding Dory</li>
<li>Game of Thrones</li>
<li>Star Wars: The Force Awakens</li>
<li>Life of Pi</li>
<li>Dancing with the Stars</li>
<li>The Walking Dead</li>
</ol>
</body>
</html>
html

Developer /etc/hosts

192.30.253.113 github.com
151.101.44.133 assets-cdn.github.com
54.236.140.90 collector.githubapp.com
192.30.253.116 api.github.com
192.30.253.122 ssh.github.com
151.101.44.133 avatars0.githubusercontent.com
151.101.44.133 avatars1.githubusercontent.com
151.101.44.133 avatars2.githubusercontent.com
151.101.44.133 avatars3.githubusercontent.com
github
151.101.92.162 registry.npmjs.com
52.27.70.152 npmjs.com
npm
104.244.42.65 twitter.com
twitter
151.101.65.69 stackoverflow.com
stackoverflow

Use Anchor Tag to Parse URL

function getURLObject(url) {
const parser = document.createElement('a');
parser.href = url;
return {
protocol: parser.protocol,
hostname: parser.hostname,
port: parser.port,
pathname: parser.pathname,
search: parser.search,
hash: parser.hash,
host: parser.host
};
}
geturlobject.js