Skip to content

Commit

Permalink
Merge branch 'bind-switch'
Browse files Browse the repository at this point in the history
Close #18 and close #1.
  • Loading branch information
noyainrain committed Mar 1, 2018
2 parents 95a94d9 + 0dcb8ca commit 9acec06
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ rules:
- error
- allow:
- __bound__
- __template__
- __templates__
- __type__
allowAfterThis: true
# Make consistent with other brackets
Expand Down
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ jobs:
env: BROWSER=chrome PLATFORM="Windows 10"
- <<: *test-ui
env: BROWSER=MicrosoftEdge PLATFORM="Windows 10"
- <<: *test-ui
env: BROWSER=firefox PLATFORM="Windows 10"
- <<: *test-ui
env: BROWSER=safari PLATFORM="macOS 10.12"
2 changes: 2 additions & 0 deletions boilerplate/.travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ jobs:
env: BROWSER=chrome PLATFORM="Windows 10"
- <<: *test-ui
env: BROWSER=MicrosoftEdge PLATFORM="Windows 10"
- <<: *test-ui
env: BROWSER=firefox PLATFORM="Windows 10"
- <<: *test-ui
env: BROWSER=safari PLATFORM="macOS 10.12"
2 changes: 1 addition & 1 deletion boilerplate/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"clean": "rm -rf node_modules"
},
"dependencies": {
"@noyainrain/micro": "^0.11"
"@noyainrain/micro": "^0.12"
},
"devDependencies": {
"eslint": "^4.0",
Expand Down
2 changes: 1 addition & 1 deletion boilerplate/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
git+https://github.com/noyainrain/micro.git@0.11.0
git+https://github.com/noyainrain/micro.git@0.12.0
71 changes: 54 additions & 17 deletions client/bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,16 @@ micro.bind.transforms = {
let scopes = new Map();

function create(item) {
let child = document.importNode(ctx.elem.__template__.content, true).querySelector("*");
let child =
document.importNode(ctx.elem.__templates__[0].content, true).querySelector("*");
let scope = new micro.bind.Watchable({[itemName]: item});
scopes.set(child, scope);
micro.bind.bind(child, [scope].concat(ctx.data));
return child;
}

if (!ctx.elem.__template__) {
ctx.elem.__template__ = ctx.elem.firstElementChild;
ctx.elem.__template__.remove();
if (!ctx.elem.__templates__) {
ctx.elem.__templates__ = Array.from(ctx.elem.querySelectorAll("template"));
}

let fragment = document.createDocumentFragment();
Expand Down Expand Up @@ -467,9 +467,8 @@ micro.bind.transforms = {
* arguments of :func:`micro.bind.list`.
*/
join(ctx, arr, itemName, separator = ", ", transform, ...args) {
if (!ctx.elem.__template__) {
ctx.elem.__template__ = ctx.elem.firstElementChild;
ctx.elem.__template__.remove();
if (!ctx.elem.__templates__) {
ctx.elem.__templates__ = Array.from(ctx.elem.querySelectorAll("template"));
}

let fragment = document.createDocumentFragment();
Expand All @@ -483,7 +482,8 @@ micro.bind.transforms = {
if (i > 0) {
fragment.appendChild(document.createTextNode(separator));
}
let child = document.importNode(ctx.elem.__template__.content, true).querySelector("*");
let child =
document.importNode(ctx.elem.__templates__[0].content, true).querySelector("*");
let scope = {[itemName]: item};
micro.bind.bind(child, [scope].concat(ctx.data));
fragment.appendChild(child);
Expand All @@ -493,7 +493,46 @@ micro.bind.transforms = {
return fragment;
},

filter: micro.bind.filter
filter: micro.bind.filter,

/**
* Test if the array *arr* includes a certain item.
*
* *searchElement* and *fromIndex* are equivalent to the arguments of :func:`Array.includes`.
*/
includes(ctx, arr, searchElement, fromIndex) {
return arr ? arr.includes(searchElement, fromIndex) : false;
},

/**
* Select and render a template associated with a case by matching against *value*.
*
* *cases* is a list of conditions corresponding to a list of templates. There may be an
* additional default template. If *value* does not match any case, the default template is
* rendered, or nothing if there is no default template.
*/
switch(ctx, value, ...cases) {
if (!ctx.elem.__templates__) {
ctx.elem.__templates__ = Array.from(ctx.elem.querySelectorAll("template"));
}

if (cases.length === 0) {
cases = [true];
}
if (!(ctx.elem.__templates__.length >= cases.length &&
ctx.elem.__templates__.length <= cases.length + 1)) {
throw new Error("templates-do-not-match-cases");
}

let i = cases.indexOf(value);
let template = ctx.elem.__templates__[i === -1 ? cases.length : i];
if (!template) {
return document.createDocumentFragment();
}
let node = document.importNode(template.content, true);
micro.bind.bind(node, ctx.data);
return node;
}
};

/**
Expand All @@ -508,14 +547,13 @@ micro.bind.transforms = {
*/
micro.bind.list = function(elem, arr, itemName, transform, ...args) {
function create(item) {
let child = document.importNode(elem.__template__.content, true).querySelector("*");
let child = document.importNode(elem.__templates__[0].content, true).querySelector("*");
child[itemName] = item;
return child;
}

if (!elem.__template__) {
elem.__template__ = elem.firstElementChild;
elem.__template__.remove();
if (!elem.__templates__) {
elem.__templates__ = Array.from(elem.querySelectorAll("template"));
}

let fragment = document.createDocumentFragment();
Expand Down Expand Up @@ -549,9 +587,8 @@ micro.bind.list = function(elem, arr, itemName, transform, ...args) {
* Use :func:`micro.bind.transforms.join`.
*/
micro.bind.join = function(elem, arr, itemName, separator = ", ", transform, ...args) {
if (!elem.__template__) {
elem.__template__ = elem.firstElementChild;
elem.__template__.remove();
if (!elem.__templates__) {
elem.__templates__ = Array.from(elem.querySelectorAll("template"));
}

let fragment = document.createDocumentFragment();
Expand All @@ -565,7 +602,7 @@ micro.bind.join = function(elem, arr, itemName, separator = ", ", transform, ...
if (i > 0) {
fragment.appendChild(document.createTextNode(separator));
}
let child = document.importNode(elem.__template__.content, true).querySelector("*");
let child = document.importNode(elem.__templates__[0].content, true).querySelector("*");
child[itemName] = item;
fragment.appendChild(child);
}
Expand Down
40 changes: 33 additions & 7 deletions client/micro.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ html {
--micro-color-delimiter: #ddd;
--micro-color-border: #bbb;
--micro-color-primary: #08f;
--micro-color-primary-shade: #005aa8;
--micro-color-primary-tint: #77c0ff; /* Same lightness as border */
--micro-color-primary-shade: #005aa8; /* Lightness: 66% */
--micro-color-primary-tint: #77c0ff; /* Lightness: border */
--micro-color-secondary: #f80;
--micro-color-mono-shade: #545454;
--micro-color-mono-shade: #545454; /* Lightness: primary-shade */
--micro-focus-shadow: inset 0 0 0 1px var(--micro-color-primary-tint);
}

Expand Down Expand Up @@ -250,7 +250,7 @@ h1 .link {

/* ---- Forms ---- */

input,
input:not([type=checkbox]),
textarea,
select {
padding: calc(1.5rem / 4);
Expand All @@ -265,11 +265,20 @@ select:invalid:not(:focus) {
box-shadow: none;
}

input[type=checkbox] {
margin: 0;
}

input[type=checkbox]:focus {
outline: 1px solid var(--micro-color-primary-tint);
box-shadow: none;
}

label {
display: block;
}

label > input,
label > input:not([type=checkbox]),
label > textarea,
label > select {
display: block;
Expand Down Expand Up @@ -301,6 +310,20 @@ form > label {
margin: calc(0.25 * 1.5rem) 0;
}

fieldset {
padding: 0;
border: none;
margin: 1.5rem 0;
}

legend {
padding: 0;
}

legend::after {
content: ':';
}

/* ---- Logo ---- */

.micro-logo {
Expand Down Expand Up @@ -380,7 +403,6 @@ form > label {
[is=micro-menu] li .link,
[is=micro-menu] li .action {
display: block;
min-width: calc(1em + 1.5em / 2);
/* Computed line height between adjacent items may not match exactly */
height: 100%;
padding: calc(1.5em / 4);
Expand Down Expand Up @@ -412,6 +434,10 @@ form > label {
background: white;
}

[is=micro-menu] li:first-child .link {
border-left: 1px solid var(--micro-color-delimiter);
}

[is=micro-menu] li .link:hover,
[is=micro-menu] li .link:focus,
[is=micro-menu] li.micro-menu-expanded .link {
Expand All @@ -433,7 +459,7 @@ form > label {

[is=micro-menu].micro-secondary > li > .link,
[is=micro-menu].micro-secondary > li > .action {
padding: calc(1.5em / 4) calc(1.5em / 8);
padding: calc(1.5em / 4);
border: none;
border-radius: 0;
line-height: 1.5;
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@noyainrain/micro",
"version": "0.11.0",
"version": "0.12.0",
"description": "Toolkit for social micro web apps.",
"repository": "noyainrain/micro",
"license": "LGPL-3.0",
Expand Down
40 changes: 40 additions & 0 deletions client/tests/test_bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ describe("bind()", function() {
return [arr, ul];
}

function setupDOMWithSwitch(state, defaultTemplate = true) {
document.body.innerHTML = `
<p data-content="switch state 'a' 'b'">
<template>1: <span data-content="state"></span></template>
<template>2: <span data-content="state"></span></template>
<template>Default: <span data-content="state"></span></template>
</p>
`;
let p = document.body.firstElementChild;
if (!defaultTemplate) {
p.lastElementChild.remove();
}
micro.bind.bind(p, {state});
return p;
}

it("should update DOM", function() {
document.body.innerHTML = '<span data-title="value"></span>';
let span = document.body.firstElementChild;
Expand Down Expand Up @@ -191,6 +207,21 @@ describe("bind()", function() {
expect(nodes).to.deep.equal(["a", ", ", "b", ", ", "c"]);
});

it("should update DOM with switch", function() {
let p = setupDOMWithSwitch("b");
expect(p.textContent).to.equal("2: b");
});

it("should update DOM with switch for non-matching value and default template", function() {
let p = setupDOMWithSwitch("x");
expect(p.textContent).to.equal("Default: x");
});

it("should update DOM with switch for non-matching value and no default template", function() {
let p = setupDOMWithSwitch("x", false);
expect(p.textContent).to.be.empty;
});

it("should update DOM with nested binding", function() {
document.body.innerHTML = '<p data-title="outer"><span data-title="inner"></span></p>';
let p = document.body.firstElementChild;
Expand Down Expand Up @@ -249,3 +280,12 @@ describe("parse()", function() {
{name: "x.y", tokens: ["x", "y"]}]);
});
});

describe("transforms", function() {
describe("includes()", function() {
it("should return true for searchElement in arr", function() {
let includes = micro.bind.transforms.includes(null, ["a", "b"], "b");
expect(includes).to.be.true;
});
});
});
34 changes: 34 additions & 0 deletions client/tests/test_util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* micro
* Copyright (C) 2018 micro contributors
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/

/* eslint-env mocha */
/* global chai, expect */
/* eslint-disable prefer-arrow-callback */

"use strict";

window.expect = window.expect || chai.expect;

describe("dispatchEvent()", function() {
it("should dispatch event", function() {
let calls = [];
let span = document.createElement("span");
span.addEventListener("poke", event => calls.push(["listener", event.type]));
span.onpoke = event => calls.push(["on", event.type]);
micro.util.dispatchEvent(span, new CustomEvent("poke"));
expect(calls).to.deep.equal([["listener", "poke"], ["on", "poke"]]);
});
});
13 changes: 13 additions & 0 deletions client/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@

micro.util = {};

/**
* Dispatch an *event* at the specified *target*.
*
* If defined, the related on-event handler is called.
*/
micro.util.dispatchEvent = function(target, event) {
target.dispatchEvent(event);
let on = target[`on${event.type}`];
if (on) {
on.call(target, event);
}
};

/**
* Convert *str* to a slug, i.e. a human readable URL path segment.
*
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='micro',
version='0.11.0',
version='0.12.0',
url='https://github.com/noyainrain/micro',
maintainer='Sven James',
maintainer_email='sven.jms AT gmail.com',
Expand Down

0 comments on commit 9acec06

Please sign in to comment.