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",