vsn 0.1.51 → 0.1.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -56
- package/dist/attributes/ScopeAttribute.d.ts +4 -0
- package/dist/attributes/ScopeAttribute.js +75 -0
- package/dist/attributes/ScopeAttribute.js.map +1 -1
- package/package.json +7 -2
- package/src/Tag.ts +11 -5
- package/src/attributes/ScopeAttribute.ts +25 -0
- package/test/attributes/ScopeAttribute.spec.ts +35 -0
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
[](https://badge.fury.io/js/vsn) [](https://travis-ci.org/malero/vsn) [](https://codecov.io/gh/malero/vsn) []() [](https://unpkg.com/vsn)
|
|
2
|
+
|
|
1
3
|
# VisionJS Framework
|
|
2
4
|
|
|
3
5
|
Simple Javascript Framework built from the ground up with eCommerce and SEO in mind. VisionJS is meant to be used with server side rendered websites. Rather than dynamically rendering component templates like most javascript frameworks, VisionJS uses the html rendered by your server to add functionality to your website.
|
|
@@ -8,60 +10,6 @@ Use NPM to install VisionJS with the following command:
|
|
|
8
10
|
npm i vsn
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
##
|
|
12
|
-
### Set A Scope Variable
|
|
13
|
-
Use `vsn-set:variable_name="value|type"` to set a variable in the scope. `vsn-set` is only used to initialize a value and will only be evaluated once. Use `vsn-bind` if you would like to bind the element to the scope variable.
|
|
14
|
-
|
|
15
|
-
<div vsn-set:my_string="42"></div>
|
|
16
|
-
<div vsn-set:my_int="42|int"></div>
|
|
17
|
-
<div vsn-set:my_float="42.3|float"></div>
|
|
18
|
-
<div vsn-set:my_bool="false|bool"></div>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
### Attribute Binding
|
|
22
|
-
Use `vsn-bind:attribute` to bind a scope variable to the element's attribute. Using `vsn-bind` on an input will bind the input's value to the scope variable.
|
|
23
|
-
|
|
24
|
-
<a href="/index.html" id="link">Home</a>
|
|
25
|
-
<input type="text" vsn-bind="#link.@text" />
|
|
26
|
-
<input type="text" vsn-bind="#link.@href" />
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
### Bind to Element Events
|
|
30
|
-
Use `vsn-on` on an element to execute some code when the specified event is triggered. Here we have a button that toggles the root scope variable `show` between false and true when the element is clicked.
|
|
31
|
-
|
|
32
|
-
<button vsn-on:click="show = !show" vsn-set:show="false|bool">Toggle</button>
|
|
33
|
-
<span vsn-bind="show"></span>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
### Conditional Elements
|
|
37
|
-
Use `vsn-if` if you would only like to show an element if a certain condition is met.
|
|
38
|
-
|
|
39
|
-
<button vsn-on:click="show = !show" vsn-set:show="false|bool">Toggle</button>
|
|
40
|
-
<span vsn-if="show">Show is true</span>
|
|
41
|
-
<span vsn-if="!show">Show is false</span>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
### Controllers
|
|
45
|
-
Use `vsn-controller:variable_name="ClassName"` to bind an element to a controller class.
|
|
46
|
-
|
|
47
|
-
Typescript class controller:
|
|
48
|
-
|
|
49
|
-
class Controller {
|
|
50
|
-
public on: boolean = false;
|
|
51
|
-
|
|
52
|
-
doSomething($event, value) {
|
|
53
|
-
$event.preventDefault();
|
|
54
|
-
this.on = value;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
vision.registerClass(Controller, 'Controller');
|
|
58
|
-
|
|
59
|
-
HTML to use the above controller:
|
|
60
|
-
|
|
61
|
-
<div vsn-controller:controller="Controller">
|
|
62
|
-
<span vsn-if="controller.on">It's on!</span>
|
|
63
|
-
<span vsn-if="!controller.on">It's off...</span>
|
|
64
|
-
<a href="/" vsn-on:click="controller.doSomething($event, !controller.on)">Click Me</a>
|
|
65
|
-
</div>
|
|
13
|
+
## Usage
|
|
66
14
|
|
|
67
|
-
|
|
15
|
+
Please visit the [docs](https://www.vsnjs.org) for more information.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Attribute } from "../Attribute";
|
|
2
|
+
import { Tree } from "../AST";
|
|
2
3
|
export declare class ScopeAttribute extends Attribute {
|
|
3
4
|
static readonly canDefer: boolean;
|
|
4
5
|
static readonly scoped: boolean;
|
|
6
|
+
protected tree: Tree;
|
|
7
|
+
compile(): Promise<void>;
|
|
8
|
+
extract(): Promise<void>;
|
|
5
9
|
}
|
|
@@ -20,15 +20,90 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
20
20
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
21
21
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
22
22
|
};
|
|
23
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
24
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
25
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
26
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
27
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
28
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
29
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
33
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
34
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
35
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
36
|
+
function step(op) {
|
|
37
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
38
|
+
while (_) try {
|
|
39
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
40
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
41
|
+
switch (op[0]) {
|
|
42
|
+
case 0: case 1: t = op; break;
|
|
43
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
44
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
45
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
46
|
+
default:
|
|
47
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
48
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
49
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
50
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
51
|
+
if (t[2]) _.ops.pop();
|
|
52
|
+
_.trys.pop(); continue;
|
|
53
|
+
}
|
|
54
|
+
op = body.call(thisArg, _);
|
|
55
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
56
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
57
|
+
}
|
|
58
|
+
};
|
|
23
59
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
60
|
exports.ScopeAttribute = void 0;
|
|
25
61
|
var Attribute_1 = require("../Attribute");
|
|
26
62
|
var Registry_1 = require("../Registry");
|
|
63
|
+
var AST_1 = require("../AST");
|
|
64
|
+
var Scope_1 = require("../Scope");
|
|
27
65
|
var ScopeAttribute = /** @class */ (function (_super) {
|
|
28
66
|
__extends(ScopeAttribute, _super);
|
|
29
67
|
function ScopeAttribute() {
|
|
30
68
|
return _super !== null && _super.apply(this, arguments) || this;
|
|
31
69
|
}
|
|
70
|
+
ScopeAttribute.prototype.compile = function () {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
72
|
+
return __generator(this, function (_a) {
|
|
73
|
+
switch (_a.label) {
|
|
74
|
+
case 0:
|
|
75
|
+
this.tree = new AST_1.Tree(this.getAttributeValue());
|
|
76
|
+
return [4 /*yield*/, this.tree.prepare(this.tag.scope, this.tag.dom, this.tag)];
|
|
77
|
+
case 1:
|
|
78
|
+
_a.sent();
|
|
79
|
+
return [4 /*yield*/, _super.prototype.compile.call(this)];
|
|
80
|
+
case 2:
|
|
81
|
+
_a.sent();
|
|
82
|
+
return [2 /*return*/];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
ScopeAttribute.prototype.extract = function () {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
89
|
+
var value, _i, _a, key;
|
|
90
|
+
return __generator(this, function (_b) {
|
|
91
|
+
switch (_b.label) {
|
|
92
|
+
case 0: return [4 /*yield*/, this.tree.evaluate(this.tag.scope, this.tag.dom, this.tag)];
|
|
93
|
+
case 1:
|
|
94
|
+
value = _b.sent();
|
|
95
|
+
if (!(value instanceof Scope_1.Scope)) {
|
|
96
|
+
throw new Error("Scope value must be an object, got " + typeof value);
|
|
97
|
+
}
|
|
98
|
+
for (_i = 0, _a = value.data.keys; _i < _a.length; _i++) {
|
|
99
|
+
key = _a[_i];
|
|
100
|
+
this.tag.scope.set(key, value.data[key]);
|
|
101
|
+
}
|
|
102
|
+
return [2 /*return*/];
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
32
107
|
ScopeAttribute.canDefer = false;
|
|
33
108
|
ScopeAttribute.scoped = true;
|
|
34
109
|
ScopeAttribute = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScopeAttribute.js","sourceRoot":"","sources":["../../src/attributes/ScopeAttribute.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ScopeAttribute.js","sourceRoot":"","sources":["../../src/attributes/ScopeAttribute.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0CAAuC;AACvC,wCAAqC;AACrC,8BAA4B;AAC5B,kCAA+B;AAG/B;IAAoC,kCAAS;IAA7C;;IAoBA,CAAC;IAfgB,gCAAO,GAApB;;;;;wBACI,IAAI,CAAC,IAAI,GAAG,IAAI,UAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;wBAC/C,qBAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EAAA;;wBAA/D,SAA+D,CAAC;wBAChE,qBAAM,iBAAM,OAAO,WAAE,EAAA;;wBAArB,SAAqB,CAAC;;;;;KACzB;IAEY,gCAAO,GAApB;;;;;4BACkB,qBAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EAAA;;wBAAxE,KAAK,GAAG,SAAgE;wBAC9E,IAAI,CAAC,CAAC,KAAK,YAAY,aAAK,CAAC,EAAE;4BAC3B,MAAM,IAAI,KAAK,CAAC,wCAAsC,OAAO,KAAO,CAAC,CAAC;yBACzE;wBACD,WAAiC,EAAf,KAAA,KAAK,CAAC,IAAI,CAAC,IAAI,EAAf,cAAe,EAAf,IAAe,EAAE;4BAAxB,GAAG;4BACV,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;yBAC5C;;;;;KACJ;IAlBsB,uBAAQ,GAAY,KAAK,CAAC;IAC1B,qBAAM,GAAY,IAAI,CAAC;IAFrC,cAAc;QAD1B,mBAAQ,CAAC,SAAS,CAAC,WAAW,CAAC;OACnB,cAAc,CAoB1B;IAAD,qBAAC;CAAA,AApBD,CAAoC,qBAAS,GAoB5C;AApBY,wCAAc"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vsn",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
4
4
|
"description": "SEO Friendly Javascript/Typescript Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
7
|
-
"typescript"
|
|
7
|
+
"typescript",
|
|
8
|
+
"html",
|
|
9
|
+
"ajax",
|
|
10
|
+
"hateoas",
|
|
11
|
+
"rest",
|
|
12
|
+
"seo"
|
|
8
13
|
],
|
|
9
14
|
"main": "./dist/vsn.js",
|
|
10
15
|
"scripts": {
|
package/src/Tag.ts
CHANGED
|
@@ -250,6 +250,9 @@ export class Tag extends DOMObject {
|
|
|
250
250
|
if (!!this._scope)
|
|
251
251
|
return this._scope;
|
|
252
252
|
|
|
253
|
+
if (this.uniqueScope)
|
|
254
|
+
return this.createScope();
|
|
255
|
+
|
|
253
256
|
if (!!this._parentTag)
|
|
254
257
|
return this._parentTag.scope;
|
|
255
258
|
|
|
@@ -432,7 +435,6 @@ export class Tag extends DOMObject {
|
|
|
432
435
|
|
|
433
436
|
if (requiresScope && !this.uniqueScope) {
|
|
434
437
|
this._uniqueScope = true;
|
|
435
|
-
this._scope = new Scope();
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
this._state = TagState.AttributesBuilt;
|
|
@@ -569,17 +571,21 @@ export class Tag extends DOMObject {
|
|
|
569
571
|
}
|
|
570
572
|
}
|
|
571
573
|
|
|
572
|
-
public createScope() {
|
|
574
|
+
public createScope(force: boolean = false): Scope {
|
|
573
575
|
// Standard attribute requires a unique scope
|
|
574
576
|
// @todo: Does this cause any issues with attribute bindings on the parent scope prior to having its own scope? hmm...
|
|
575
|
-
if (!this.uniqueScope) {
|
|
577
|
+
if ((!this.uniqueScope && force) || this.uniqueScope) {
|
|
576
578
|
this._uniqueScope = true;
|
|
577
579
|
this._scope = new Scope();
|
|
578
580
|
|
|
579
581
|
if (this.parentTag) {
|
|
580
582
|
this.scope.parentScope = this.parentTag.scope;
|
|
581
583
|
}
|
|
584
|
+
|
|
585
|
+
return this._scope;
|
|
582
586
|
}
|
|
587
|
+
|
|
588
|
+
return null;
|
|
583
589
|
}
|
|
584
590
|
|
|
585
591
|
async watchAttribute(attributeName: string) {
|
|
@@ -589,7 +595,7 @@ export class Tag extends DOMObject {
|
|
|
589
595
|
}
|
|
590
596
|
}
|
|
591
597
|
|
|
592
|
-
this.createScope();
|
|
598
|
+
this.createScope(true);
|
|
593
599
|
|
|
594
600
|
const standardAttribute = new StandardAttribute(this, attributeName);
|
|
595
601
|
this.attributes.push(standardAttribute);
|
|
@@ -605,7 +611,7 @@ export class Tag extends DOMObject {
|
|
|
605
611
|
}
|
|
606
612
|
}
|
|
607
613
|
|
|
608
|
-
this.createScope();
|
|
614
|
+
this.createScope(true);
|
|
609
615
|
|
|
610
616
|
const styleAttribute = new StyleAttribute(this, 'style');
|
|
611
617
|
this.attributes.push(styleAttribute);
|
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import {Attribute} from "../Attribute";
|
|
2
2
|
import {Registry} from "../Registry";
|
|
3
|
+
import {Tree} from "../AST";
|
|
4
|
+
import {Scope} from "../Scope";
|
|
3
5
|
|
|
4
6
|
@Registry.attribute('vsn-scope')
|
|
5
7
|
export class ScopeAttribute extends Attribute {
|
|
6
8
|
public static readonly canDefer: boolean = false;
|
|
7
9
|
public static readonly scoped: boolean = true;
|
|
10
|
+
protected tree: Tree;
|
|
11
|
+
|
|
12
|
+
public async compile() {
|
|
13
|
+
const code = this.getAttributeValue();
|
|
14
|
+
if (code) {
|
|
15
|
+
this.tree = new Tree(code);
|
|
16
|
+
await this.tree.prepare(this.tag.scope, this.tag.dom, this.tag);
|
|
17
|
+
}
|
|
18
|
+
await super.compile();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async extract() {
|
|
22
|
+
if (this.tree) {
|
|
23
|
+
const value = await this.tree.evaluate(this.tag.scope, this.tag.dom, this.tag);
|
|
24
|
+
if (!(value instanceof Scope)) {
|
|
25
|
+
throw new Error(`vsn-scope value must be an object, got ${typeof value}`);
|
|
26
|
+
}
|
|
27
|
+
for (const key of value.data.keys) {
|
|
28
|
+
this.tag.scope.set(key, value.data[key]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
await super.extract();
|
|
32
|
+
}
|
|
8
33
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {DOM} from "../../src/DOM";
|
|
2
|
+
import "../../src/Types";
|
|
3
|
+
import "../../src/attributes/_imports";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe('ScopeAttribute', () => {
|
|
7
|
+
it("vsn-scope should set simple values correctly", (done) => {
|
|
8
|
+
document.body.innerHTML = `
|
|
9
|
+
<div vsn-scope="{'asd':123, 'sdf': 'asd'}">
|
|
10
|
+
<span vsn-bind="asd"></span>
|
|
11
|
+
</div>
|
|
12
|
+
`;
|
|
13
|
+
const dom = new DOM(document);
|
|
14
|
+
dom.once('built', async () => {
|
|
15
|
+
const element = (await dom.exec('?(div)'))[0];
|
|
16
|
+
expect(element.scope.get('asd')).toBe(123);
|
|
17
|
+
expect(element.scope.get('sdf')).toBe('asd');
|
|
18
|
+
const span = (await element.exec('?>(:first-child)'))[0];
|
|
19
|
+
expect(span.element.innerText).toBe('123');
|
|
20
|
+
done();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("vsn-scope should allow empty value to create a scope", (done) => {
|
|
25
|
+
document.body.innerHTML = `
|
|
26
|
+
<div vsn-scope></div>
|
|
27
|
+
`;
|
|
28
|
+
const dom = new DOM(document);
|
|
29
|
+
dom.once('built', async () => {
|
|
30
|
+
const element = (await dom.exec('?(div)'))[0];
|
|
31
|
+
expect(element.uniqueScope).toBe(true);
|
|
32
|
+
done();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|