Skip to content

Commit

Permalink
Initial version.
Browse files Browse the repository at this point in the history
  • Loading branch information
foxdonut committed Jul 6, 2018
1 parent faaa5f0 commit ec2c5b2
Show file tree
Hide file tree
Showing 12 changed files with 3,055 additions and 2 deletions.
37 changes: 37 additions & 0 deletions .eslintrc.js
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"
]
}
};
7 changes: 7 additions & 0 deletions .npmignore
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
87 changes: 85 additions & 2 deletions README.md
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._
247 changes: 247 additions & 0 deletions dist/seview.js
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 });

})));
1 change: 1 addition & 0 deletions dist/seview.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ec2c5b2

Please sign in to comment.