diff --git a/README.md b/README.md
index cf5eea1..8e13ea9 100644
--- a/README.md
+++ b/README.md
@@ -10,12 +10,12 @@ npm run build
#### Features
* jsx syntax
* i18n
-* event -> command
+* event -> command
+* controller -> view injection
#### TODO
merge with this repo
* [ ] history
-* [ ] model -> controller to view injection
add example
* [ ] http
diff --git a/example/parcel-babel/.babelrc b/example/parcel-babel/.babelrc
index 55aeb17..00b14c2 100644
--- a/example/parcel-babel/.babelrc
+++ b/example/parcel-babel/.babelrc
@@ -4,6 +4,5 @@
["@babel/plugin-transform-react-jsx", {
"pragma": "huh.eh"
}],
- ["@babel/plugin-proposal-class-properties"]
]
}
diff --git a/example/parcel-babel/assets/i18/en.x b/example/parcel-babel/assets/i18/en.x
new file mode 100644
index 0000000..2c1f88a
--- /dev/null
+++ b/example/parcel-babel/assets/i18/en.x
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "test.btn": "Click me!",
+ "language.label": "Language:",
+ "h1.hello": "Hello",
+ "alert.hi": {
+ "txt": "hi {{click.count}}",
+ "var": [
+ {
+ "key": "click.count",
+ "model": "alert.button"
+ }
+ ]
+ }
+ }
+}
diff --git a/example/parcel-babel/assets/i18/pl.x b/example/parcel-babel/assets/i18/pl.x
new file mode 100644
index 0000000..8dc3a9a
--- /dev/null
+++ b/example/parcel-babel/assets/i18/pl.x
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "test.btn": "Kliknij!",
+ "language.label": "Język:",
+ "h1.hello": "Witaj",
+ "alert.hi": {
+ "txt": "Cześć {{click.count}}",
+ "var": [
+ {
+ "key": "click.count",
+ "model": "alert.button"
+ }
+ ]
+ }
+ }
+}
diff --git a/example/parcel-babel/js/app.js b/example/parcel-babel/js/app.js
index 67a2d03..7b97662 100644
--- a/example/parcel-babel/js/app.js
+++ b/example/parcel-babel/js/app.js
@@ -1,55 +1,87 @@
-import huh, {Random, I18, HFacade, HEvent} from '@szczepano/huh'
+import huh, {Random, I18, HFacade, HEvent, HCtrl, HInjection, HModel} from '@szczepano/huh'
-class Hello {
+// I18n
+I18.registerLanguage('en', {
+ path:'assets/i18/en.x'
+});
+I18.registerLanguage('pl', {
+ path:'assets/i18/pl.x'
+});
+
+// View
+
+class HelloView {
render() {
return
Hello
}
}
-const AlertHiCmd = () => {
- alert(I18.getKey('alert.hi'));
+class AlertButtonView {
+ render() {
+ return
+ }
+}
+
+class LanguageButton {
+ constructor(language) {
+ this.language = language
+ }
+ render() {
+ return
+ }
}
const start = () => {
- const title = new Hello()
const arr = ['w','o','r','l','d']
- const components = []
- console.log(Random.intRange(0, 10))
+ const components = [];
arr.forEach((el) => {
components.push({el})
})
- I18.registerLanguage('en', {
- data: {
- 'alert.hi': 'hi',
- }
- })
- I18.registerLanguage('pl', {
- data: {
- 'test.btn':'Kliknij!',
- 'language.label': 'Język:',
- 'h1.hello': 'Witaj',
- 'alert.hi': 'Cześć',
- }
- })
- const handleLanguageClick = (e) => {
- I18.switchLanguage(e.target.innerText);
- }
- const btn =
- const langs = ['en', 'pl']
- langs.forEach((l, i) => {
- langs[i] =
+ const langs = []
+ new Array('en', 'pl').forEach((l, i) => {
+ langs.push(new LanguageButton(l));
})
return (
- {langs}
+
+ {langs}
- {title}
+ {new HelloView()}
{components}
- {btn}
+ {new AlertButtonView()}
+ {new AlertButtonView()}
)
}
-document.getElementById('main').appendChild(start())
+
+// model
+class AlertButtonModel extends HModel {
+ constructor () {
+ super({
+ 'click.count': 0,
+ })
+ }
+}
+
+// controller
+HCtrl.add('language.button', null, [
+ new HInjection('click', (e) => {
+ I18.switchLanguage(e.target.innerText);
+ }),
+]);
+HCtrl.add('alert.button', new AlertButtonModel(), [
+ new HInjection('click', () => {
+ dispatchEvent(new HEvent('alert.hi'));
+ }),
+]);
+
+// command
+const AlertHiCmd = () => {
+ const model = HCtrl.model('alert.button');
+ const count = model.get('click.count');
+ model.set('click.count', count + 1);
+ alert(I18.getKey('alert.hi', model));
+}
HFacade.register('alert.hi', AlertHiCmd);
-// hack to assign data to all components
-I18.switchLanguage('en');
+I18.loadLanguage('en');
+document.getElementById('main').appendChild(start())
diff --git a/example/parcel-babel/package.json b/example/parcel-babel/package.json
index 363a44a..de18935 100644
--- a/example/parcel-babel/package.json
+++ b/example/parcel-babel/package.json
@@ -5,18 +5,18 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
- "build": "parcel index.html"
+ "dev": "parcel index.html assets/**/*",
+ "build": "parcel build index.html assets/**/*"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.10.3",
- "@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-transform-react-jsx": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"parcel-bundler": "^1.12.4"
},
"dependencies": {
- "@szczepano/huh": "0.0.4"
+ "@szczepano/huh": "0.0.3"
}
}
diff --git a/huh.js b/huh.js
index 35b7dcb..bcc1ae9 100644
--- a/huh.js
+++ b/huh.js
@@ -2,9 +2,11 @@
export default class huh {
static eh(tag, data, ...children) {
let el = document.createElement(tag);
- if(data && 'h-i18' in data) {
- I18.register(data['h-i18'], el);
+ if(data) {
+ if('h-i18' in data) I18.register(data['h-i18'], el);
+ if('h-ctrl' in data) HCtrl.register(data['h-ctrl'], el);
}
+
for(let arg in data) {
if(typeof data[arg] == "function") {
el[arg] = data[arg];
@@ -78,46 +80,6 @@ export const HTTP = (o) => {
});
}
-// Model
-let _minstance = null
-
-export class Model {
- constructor() {
- if(_minstance) {
- throw new Error('Model is Singleton')
- } else {
- this._data = {}
- this._pub = Pub.instance()
- _minstance = this
- }
- }
-
- set(key, value) {
- this._data[key] = value
- this._pub.send(new Sub(`model.${key}`, {type:'set', value}))
- }
-
- get(key) {
- return this._data[key]
- }
-
- update(key, value) {
- if(!(key in this._data)) throw new Error(`Invalid key ${key}`)
- this._data[key] = value
- this._pub.send(new Sub(`model.${key}`, {type:'update', value}))
- }
-
- del(key) {
- delete this._data[key]
- this._pub.send(new Sub(`model.${key}`, {type:'del', value}))
- }
-
- static instance() {
- if (!_minstance) _minstance = new Model()
- return _minstance
- }
-}
-
export class Random {
static intRange(min, max) {
min = Math.ceil(min);
@@ -147,7 +109,7 @@ export class Random {
// Event -> Command pattern
-export class HEvent extends Event{
+export class HEvent extends Event {
constructor(type, data) {
super(type);
this.data = data;
@@ -185,11 +147,91 @@ export class HFacade {
_hlisteners[name] = new CmdWrapper(cmd, once, chain);
addEventListener(name, _hlisteners[name].handler);
}
+
+ static initialize(data) {
+ data.forEach((el) => {
+ _hlisteners[el.name] = new CmdWrapper(el.cmd, el.once, el.chain);
+ addEventListener(el.name, _hlisteners[el.name].handler);
+ })
+ }
}
+// View -> Model -> Controller
+
+export class HModel {
+ constructor(predefined) {
+ this._data = Object.assign({}, predefined);
+ }
+
+ set(key, value) {
+ this._data[key] = value
+ }
+
+ get(key) {
+ return this._data[key]
+ }
+
+ update(key, value) {
+ if(!(key in this._data)) throw new Error(`Invalid key ${key}`)
+ this._data[key] = value
+ }
+
+ del(key) {
+ delete this._data[key]
+ }
+}
+
+export class HInjection {
+ constructor(name, fn) {
+ this.name = name;
+ this.fn = fn;
+ }
+}
+
+const _ctrls = {}
+
+class CtrlWrapper {
+ constructor(name, model, inject) {
+ this.name = name;
+ this.model = model;
+ this.inject = inject;
+ this.register = (view) => {
+ this.inject.forEach((el) => {
+ view.addEventListener(el.name, el.fn);
+ });
+ }
+ }
+}
+
+export class HCtrl {
+ static add(name, model, inject) {
+ const w = new CtrlWrapper(name, model, inject);
+ _ctrls[name] = w;
+ }
+
+ static register(name, view) {
+ _ctrls[name].register(view);
+ }
+
+ static view(name) {
+ if(!(name in _ctrls)) {
+ throw Error(`View named : "${name}" not found.`)
+ }
+ return _ctrls[name].view;
+ }
+ static model(name) {
+ if(!(name in _ctrls)) {
+ throw Error(`View named : "${name}" not found.`)
+ }
+ return _ctrls[name].model
+ }
+}
+
+// i18n
+
const _i18comp = {};
const _i18langs = {};
-let _currentLanguage = null;
+let _currentLanguage = 'en';
export class I18 {
@@ -209,40 +251,97 @@ export class I18 {
throw Error(`Language not found ${lang}`);
}
_currentLanguage = lang;
- const data = _i18langs[lang].data;
+ const ldata = _i18langs[lang];
+ if(ldata.data) {
+ I18.translate(ldata);
+ } else if (ldata.path) {
+ I18.getData(lang, ldata.path, I18.translate);
+ } else {
+ throw Error(`Invalid language definition for language ${lang}`);
+ }
+ }
+
+ static loadLanguage(lang) {
+ const ldata = _i18langs[lang];
+ return I18.getData(lang, ldata.path);
+ }
+
+ static getData(lang, path, handler) {
+ return HTTP({
+ url: path
+ }).then((resp) => {
+ const data = JSON.parse(resp.data);
+ _i18langs[lang].data = data.data;
+ if(handler) handler(data);
+ });
+ }
+
+ static translate(lang) {
+ const data = lang.data;
for(let key in _i18comp) {
if(!(key in data)) {
console.warn(`Missing translation for language "${lang}" key: "${key}" setting current value`);
- _i18langs[lang].data[key] = _i18comp[key].innerText;
- } else {
- I18.t(_i18comp[key], data[key]);
+
}
+ I18.t(_i18comp[key], data[key]);
}
}
static register(key, el) {
- if(key in _i18comp) {
- throw Error(`Duplicate key ${key}`);
+ if(!(key in _i18comp)) {
+ _i18comp[key] = [];
}
- _i18comp[key] = el;
+ _i18comp[key].push(el);
}
- static t(el, data) {
- if(typeof data == "string" || typeof data == "number") {
- el.innerText = data;
+ static t(elarr, data) {
+ if(!data) return;
+ elarr.forEach((el) => {
+ if(typeof data == "string" || typeof data == "number") {
+ el.innerText = data;
+ } else {
+ let txt = data.txt;
+ data.var.forEach((el) => {
+ const model = _ctrls[el.model].model
+ txt = txt.replace(`{{${el.key}}}`, model.get(el.key))
+ });
+ el.innerText = txt;
+ }
+ });
+ }
+
+ static getKey(key, model) {
+ const ldata = _i18langs[_currentLanguage];
+ if (ldata.data) {
+ const t = _i18langs[_currentLanguage].data[key];
+ return I18.tkey(t, model);
} else {
- throw Error(`TODO implement dynamic data type ${data}`);
+ throw Error(`Language not loaded ${_currentLanguage}`);
}
}
- static getKey(key) {
- return _i18langs[_currentLanguage].data[key];
- }
-
- static getLKey(lang, key) {
+ static getLKey(lang, key, model) {
if(!(lang in _i18langs)) {
throw Error(`Language not found ${lang}`);
}
- return _i18langs[lang].data[key];
+ const ldata = _i18langs[lang];
+ if (ldata.data) {
+ const t = _i18langs[lang].data[key];
+ return I18.tkey(t, model);
+ } else {
+ throw Error(`Language not loaded ${lang}`);
+ }
+ }
+
+ static tkey(t, model) {
+ if(typeof t == "string" || typeof t == "number") {
+ return t;
+ } else {
+ let txt = t.txt;
+ t.var.forEach((el) => {
+ txt = txt.replace(`{{${el.key}}}`, model.get(el.key))
+ });
+ return txt;
+ }
}
}
diff --git a/package.json b/package.json
index 55f3728..bfbc4d4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@szczepano/huh",
- "version": "0.0.4",
+ "version": "0.0.5",
"description": "huh",
"main": "huh.js",
"license": "BSD-3-Clause",