-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
3,055 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module.exports = { | ||
"env": { | ||
"browser": true, | ||
"commonjs": true, | ||
"es6": true, | ||
"mocha": true | ||
}, | ||
"extends": ["eslint:recommended"], | ||
"parserOptions": { | ||
"sourceType": "module" | ||
}, | ||
"rules": { | ||
"indent": [ | ||
"error", | ||
2 | ||
], | ||
"linebreak-style": [ | ||
"error", | ||
"unix" | ||
], | ||
"no-unused-vars": [ | ||
"error", | ||
{ | ||
"args": "after-used", | ||
"argsIgnorePattern": "^_" | ||
} | ||
], | ||
"quotes": [ | ||
"error", | ||
"double" | ||
], | ||
"semi": [ | ||
"error", | ||
"never" | ||
] | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.eslintrc.js | ||
.gitignore | ||
.vscode | ||
rollup.config.js | ||
test | ||
LICENSE | ||
README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,85 @@ | ||
# seview | ||
S-Expression View | ||
# seview: S-Expression View | ||
|
||
A simple way of expressing views, meant to be used with a virtual DOM library. | ||
|
||
## Why? | ||
|
||
Because plain JavaScript is simpler to write and build than JSX, and it's great to | ||
write views in a way that is independent of the virtual DOM library being used. | ||
|
||
## Example | ||
|
||
Instead of writing this in JSX: | ||
|
||
```jsx | ||
<div id="home"> | ||
<span className="instruction">Enter your name:</span> | ||
<input type="text" id="username" name="username" size="10"/> | ||
{isMessage ? <div className={"message" + (isError ? " error" : "")}>{message}</div> : null} | ||
</div> | ||
``` | ||
|
||
Or even this in hyperscript: | ||
|
||
```javascript | ||
h("div", { id: "home"}, [ | ||
h("span", { className: "instruction" }, "Enter your name:"), | ||
h("input", { type: "text", id: "username", name: "username", size: 10 }), | ||
isMessage ? h("div", { className: "message" + (isError ? " error" : "") }, message) : null | ||
]) | ||
``` | ||
|
||
You would write this with `seview`: | ||
|
||
```javascript | ||
["div#home", | ||
["span.instruction", "Enter your name:"], | ||
["input:text#username[name=username][size=10]"], | ||
isMessage && ["div.message", { className: { "error": isError } }, message] | ||
]) | ||
``` | ||
|
||
Besides the conveniences of the syntax, you also have no `h` anywhere. To switching from one virtual | ||
DOM library to another, you only need to make changes in **one** place. All your views can remain | ||
the same. | ||
|
||
## More Content to come | ||
|
||
_This README is a work-in-progress, more content is forthcoming._ | ||
|
||
## Varargs and text nodes | ||
|
||
The problem with supporting varargs is, how do you differentiate a single element from two text nodes? | ||
|
||
For example: | ||
|
||
```js | ||
["div", ["b", "hello"]] | ||
``` | ||
|
||
vs | ||
|
||
```js | ||
["div", ["hello", "there"]] | ||
``` | ||
|
||
For the second case, varargs MUST be used: | ||
|
||
```js | ||
["div", "hello", "there"] | ||
``` | ||
|
||
## Credits | ||
|
||
`seview` is inspired by the following. Credit goes to the authors and their communities. | ||
|
||
- [How to UI in 2018](https://medium.com/@thi.ng/how-to-ui-in-2018-ac2ae02acdf3) | ||
- [ijk](https://github.com/lukejacksonn/ijk) | ||
- [JSnoX](https://github.com/af/JSnoX) | ||
- [Mithril](http://mithril.js.org) | ||
- [domvm](https://domvm.github.io/domvm/) | ||
|
||
---- | ||
|
||
_seview is developed by [foxdonut](https://github.com/foxdonut) | ||
([@foxdonut00](http://twitter.com/foxdonut00)) and is released under the MIT license._ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
(factory((global.seview = {}))); | ||
}(this, (function (exports) { 'use strict'; | ||
|
||
var isString = function (x) { return typeof x === "string"; }; | ||
var isNumber = function (x) { return typeof x === "number"; }; | ||
var isBoolean = function (x) { return typeof x === "boolean"; }; | ||
var isArray = function (x) { return Array.isArray(x); }; | ||
var isObject = function (x) { return typeof x === "object" && !isArray(x) && x !== null && x !== undefined; }; | ||
|
||
var getString = function (value) { | ||
var result = undefined; | ||
|
||
if (isString(value) && value.length > 0) { | ||
result = value; | ||
} | ||
else if (isNumber(value)) { | ||
result = String(value); | ||
} | ||
else if (isBoolean(value) && value) { | ||
result = String(value); | ||
} | ||
return result | ||
}; | ||
|
||
var get = function (object, path) { return object == null | ||
? undefined | ||
: path.length === 1 | ||
? object[path[0]] | ||
: get(object[path[0]], path.slice(1)); }; | ||
|
||
var set = function (object, path, value) { | ||
if (path.length === 1) { | ||
if (isObject(object[path[0]])) { | ||
Object.assign(object[path[0]], value); | ||
} | ||
else { | ||
object[path[0]] = value; | ||
} | ||
} | ||
else { | ||
if (object[[path[0]]] == null) { | ||
object[[path[0]]] = {}; | ||
} | ||
set(object[path[0]], path.slice(1), value); | ||
} | ||
return object | ||
}; | ||
|
||
// Credit: JSnoX https://github.com/af/JSnoX/blob/master/jsnox.js | ||
|
||
// matches "input", "input:text" | ||
var tagTypeRegex = /^([a-z1-6]+)(?::([a-z]+))?/; | ||
|
||
// matches "#id", ".class", "[name=value]", "[required]" | ||
var propsRegex = /((?:#|\.|@)[\w-]+)|(\[.*?\])/g; | ||
|
||
// matches "[name=value]" or "[required]" | ||
var attrRegex = /\[([\w-]+)(?:=([^\]]+))?\]/; | ||
|
||
/* | ||
returns tag properties: for example, "input:password#duck.quack.yellow[name=pwd][required]" | ||
{ | ||
tag: "input", | ||
className: "quack yellow", | ||
attrs: { type: "password", id: "duck", name: "pwd", required: true } | ||
} | ||
*/ | ||
var getTagProperties = function (selector) { | ||
var result = {}; | ||
|
||
var tagType = selector.match(tagTypeRegex); | ||
|
||
// Use div by default | ||
if (!tagType) { | ||
tagType = ["div", "div"]; | ||
} | ||
result.tag = tagType[1]; | ||
|
||
if (tagType[2]) { | ||
result.attrs = { type: tagType[2] }; | ||
} | ||
|
||
var tagProps = selector.match(propsRegex); | ||
|
||
if (tagProps) { | ||
var classes =[]; | ||
|
||
tagProps.forEach(function (tagProp) { | ||
var ch = tagProp[0]; | ||
var prop = tagProp.slice(1); | ||
|
||
if (ch === "#") { | ||
set(result, ["attrs", "id"], prop); | ||
} | ||
else if (ch === ".") { | ||
classes.push(prop); | ||
} | ||
else if (ch === "[") { | ||
var attrs = tagProp.match(attrRegex); | ||
set(result, ["attrs", attrs[1]], (attrs[2] || true)); | ||
} | ||
}); | ||
|
||
if (classes.length > 0) { | ||
set(result, ["attrs", "className"], classes.join(" ")); | ||
} | ||
} | ||
|
||
return result | ||
}; | ||
|
||
/* | ||
returns node definition, expanding on the above tag properties and adding to obtain: | ||
{ | ||
tag: "input", | ||
className: "quack yellow", | ||
attrs: { type: "password", id: "duck", name: "pwd", required: true, onClick: ... }, | ||
children: [ { tag: ... }, "text", ... ] | ||
} | ||
*/ | ||
var processChildren = function (rest) { | ||
var ch = []; | ||
rest.forEach(function (child) { | ||
// Text node | ||
if (getString(child)) { | ||
ch.push(getString(child)); | ||
} | ||
else if (isArray(child)) { | ||
ch.push(nodeDef(child)); | ||
} | ||
}); | ||
return ch | ||
}; | ||
|
||
var nodeDef = function (node) { | ||
// Tag | ||
var rest = node[2]; | ||
var varArgsLimit = 3; | ||
|
||
// Process tag | ||
var result = getTagProperties(node[0]); | ||
|
||
// Process attrs | ||
if (isObject(node[1])) { | ||
var attrs = node[1]; | ||
|
||
// Process className | ||
if (attrs["className"] !== undefined) { | ||
var classAttr = attrs["className"]; | ||
delete attrs["className"]; | ||
|
||
var addClasses = []; | ||
if (isString(classAttr)) { | ||
addClasses = classAttr.split(" "); | ||
} | ||
else if (isObject(classAttr)) { | ||
Object.keys(classAttr).forEach(function (key) { | ||
if (classAttr[key]) { | ||
addClasses.push(key); | ||
} | ||
}); | ||
} | ||
if (addClasses.length > 0) { | ||
var existingClassName = get(result, ["attrs", "className"]); | ||
var addClassName = addClasses.join(" "); | ||
set(result, ["attrs", "className"], | ||
(existingClassName ? existingClassName + " " : "") | ||
+ addClassName | ||
); | ||
} | ||
} | ||
|
||
// Add remaining attributes | ||
if (Object.keys(attrs).length > 0) { | ||
if (result.attrs === undefined) { | ||
result.attrs = attrs; | ||
} | ||
else { | ||
result.attrs = Object.assign(result.attrs, attrs); | ||
} | ||
} | ||
} | ||
// No attrs, use second argument as rest | ||
else { | ||
rest = node[1]; | ||
varArgsLimit = 2; | ||
} | ||
|
||
// Process children: varargs | ||
if (node.length > varArgsLimit) { | ||
result.children = processChildren(node.slice(varArgsLimit - 1)); | ||
} | ||
// Process children: one child arg | ||
else { | ||
// Text node | ||
if (getString(rest)) { | ||
result.children = [ getString(rest) ]; | ||
} | ||
|
||
if (isArray(rest)) { | ||
// One child node vs Array of children | ||
result.children = processChildren( isString(rest[0]) ? [ rest ] : rest ); | ||
} | ||
} | ||
return result | ||
}; | ||
|
||
var transformNodeDef = function (transform, def) { | ||
if (isArray(def.children)) { | ||
var result = []; | ||
def.children.forEach(function (child) { | ||
result.push(isString(child) ? child : transformNodeDef(transform, child)); | ||
}); | ||
def.children = result; | ||
} | ||
return transform(def) | ||
}; | ||
|
||
var sv = function (transform) { return function (node) { | ||
var def = nodeDef(node); | ||
return transformNodeDef(transform, def) | ||
}; }; | ||
|
||
var mapKeys = function (mappings) { return function (object) { | ||
var result = {}; | ||
|
||
Object.keys(mappings).forEach(function (key) { | ||
var from = key.split("."); | ||
var to = mappings[key].split("."); | ||
var value = get(object, from); | ||
if (value != null) { | ||
set(result, to, value); | ||
} | ||
}); | ||
|
||
return result | ||
}; }; | ||
|
||
exports.sv = sv; | ||
exports.mapKeys = mapKeys; | ||
|
||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
|
||
}))); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.