Our primary goals are
- view template engine
- Dom control and reorder
- all internal variables are managed by proxy. (DomRenderProxy)
๐ Quick start
npm install dom-render
๐ examples
๐ Code description
initialized
<!doctype html><html lang="en"><body id="app"></body></html>
const target = document.querySelector('#app');
const data = DomRender.run({name: 'my name is dom-render'}, target);
data.name = 'modify name';
print and call
<div>${this.name}</div>
<div>${this.office.addr.first}, ${this.office.addr.last}, ${this.office.addr.street} (${this.office.name})</div>
<div dr="this.office.addr.street">${this.getOfficeFullAddr()}</div>
dr-if
<div dr-if="true">true</div>
<div dr-if="this.gender==='M'">gender: M</div>
dr-for, dr-for-of
<div dr-for="var i = 0; i < this.friends.length; i++"> friend</div>
<div dr-for-of="this.friends"> ${#it#.name}</div>
<div dr-for-of="$range(10, 20)"><div>${#it#}</div><div>
<div dr-for="var i = 1 ; i <= 9 ; i++" dr-it="i">
${#it#} *
<scope dr-for="var y = 1 ; y <= 9 ; y++" dr-it="y" dr-var="superIt=#it#" dr-strip="true">
#it# = ${var.superIt * #it#}
</scope>
</div>
dr-repeat
<div dr-repeat="10"><div>#it#</div></div>
<div dr-repeat="$range(10, 20)"><div>#it#</div></div>
<div dr-repeat="$range(10, 20, 5)"><div>#it#</div></div>
<div dr-repeat="$range('10..5, 2')"><div>#it#</div></div>
dr-inner-text, dr-inner-html
<div dr-inner-text="'<b>aa</b> <button dr-event-click=\'alert(1)\'>aa</button>'"> friend</div>
<div dr-inner-html="'<b>aa</b> <button dr-event-click=\'alert(1)\'>aa</button>'"> friend</div>
event
dr-event-(name)
- click, mousedown, mouseup, dblclick, mouseover, mouseout, mousemove, mouseenter, mouseleave, contextmenu, keyup, keydown, keypress, change, input, submit, resize, focus, blur
- ref: element
- variable: $event, $target
click: <button dr-event-click="this.name = 'name' + new Date()">click</button> <br>
change: <input type="text" dr-event-change="this.name = $target.value"> <br>
input: <input type="text" dr-event-input="this.name = $target.value"> <br>
keyup: <input type="text" dr-event-keyup="this.name = $target.value"> <br>
...
keydown: <input type="text" dr-event-keydown="this.name = $target.value"><br>
submit: <form dr-event-submit="console.log($event); $event.preventDefault();"><input type="text"> <button type="submit">submit</button></form><br>
dr-window-event-popstate
- ref: window
- variable: $target
window-event-popstate: <input type="text" dr-window-event-popstate="alert(this.name)"><br>
dr-event
- other event
- ref: element
- variable: $params, $event
<input dr-event:bind='eventName1, eventName2' dr-event="console.log('event', $params, $event)" type="text">
dr-value, value-link
dr-value
- The value is assigned the first time.
dr-value-link
- Value and variable values are referencing each other. It affects each other when changing. (Immediate reflection event: input)
dr-value: <input type="text" dr-value="this.office.name"> <br>
dr-value-link: <input type="text" dr-value-link="this.office.addr.street"> <br>
dr-attr
<textarea dr-attr="{rows: this.age/2, cols: this.age}"></textarea>
<div dr-attr="{wow: '123', good: 123444}"></div>
<div dr-attr="['wow=123', 'good=123444']"></div>
<div dr-attr="'wow=123, good=123444'"></div>
dr-class
<div dr-class="{big: this.age > 50, red: this.age > 50}">
<div dr-class="'big yellow ' + (this.age > 50 ? 'old' : 'young')">
<div dr-class="['small', 'yellow']">
dr-style
<div dr-style="{fontSize: this.age + 'px'}"> style </div>
<div dr-style="{'font-size': '20px'}"> style</div>
<div dr-style="'font-size: ' + this.age +'px; margin: ' + this.age + 'px'"> style </div>
<div dr-style="['font-size: ' + this.age +'px', 'margin: ' + this.age + 'px']"> style </div>
dr-strip
as-is
<div dr-strip="true"><span>hello</span></div>
to-be
<span>hello</span>
dr-on-init
<input dr-on-init="this.onInitElement">
dr-before, dr-after
<div dr-before="console.log('process before')" dr-after="console.log('process after')"></div>
dr-complete
<select dr-value-link="this.currentContry" dr-event-change="this.contryChange($event)">
<option dr-for-of="this.languages" dr-value="#it#.key" dr-complete="this.currentContry='defaultValue'">${#it#.title}</option>
</select>
LifeCycle
* OnInitRender
- onInitRender(): init render call
Script
new DomRender.run(obj, target, {
scripts: {
concat: function (head: string, tail: string) {
return head + tail;
}
}
});
using script
const data = config.scripts.concat('head', 'tail')
<div>${$scripts.concat('head', 'tail')}</div>
<div dr-if="$scripts.concat('wow', 'good') === 'wowgood'"> is wowgood</div>
Component
export namespace Profile {
export const templat = '<div>aaaaa${this.name}aaaaa </div>';
export const styles = ['p {color: red}', 'div {margin: ${this.margin} + \'px\' }'];
export class Component {
public name = 'default name';
public margin = 5;
public say() {
console.log('say!~')
}
}
}
new DomRender.run(obj, target, {
targetElements: [
RawSet.createComponentTargetElement('my-element', (e, o, r) => new Profile.Component(), Profile.templat, Profile.styles, scripts)
],
});
using component
<my-element dr-on-init="$component.say();"></my-element>
- attribute
- dr-on-init: component created init call script
- $component: component instance
- $element: element instance
- $attribute: element attribute object
- $innerHTML: element innerHTML string
dr-form
- event: change
- modify change: dr-form:event="input"
class User {
form = {};
submit() {
const form = (this.form as any)
console.log('submit->', form, form.name, form.age, form.addr);
}
}
<form dr-form="this.form" dr-event-submit="this.submit(); $event.preventDefault();">
name: <input name="name">
age: <input name="age">
addr: <input dr-form:event="input" name="addr">
<button type="submit"> aa</button>
</form>
validation
<div class="row mb-3">
<div class="col">
<span>์์ด๋</span>
<input class="w-100" type="text" name="id" placeholder="์์ด๋" dr-value-link="this.form.id.value" dr-event-blur="this.form.id.valid()" required/>
<span dr-on-init="this.form.id.msgElement" class="d-none"></span>
</div>
</div>
<div class="row mb-3">
<div class="col">
<span>๋น๋ฐ๋ฒํธ</span>
<input class="w-100" type="password" name="id" placeholder="์ซ์, ์๋ฌธ, ํน์๋ฌธ์ ์กฐํฉ ์ต์ 8์~20์" dr-value-link="this.form.password.value" dr-event-blur="this.form.password.valid()" required/>
<span dr-on-init="this.form.password.msgElement" >password valid message section</span>
<input class="w-100" type="password" name="id" placeholder="๋น๋ฐ๋ฒํธ ์ฌ์
๋ ฅ" dr-value-link="this.form.confirmPassword" required/>
<span dr-class="{'d-block': this.form.password != this.form.confirmPassword, 'text-danger': this.form.password != this.form.confirmPassword}">2nd input password valid message section</span>
</div>
</div>
<div class="row mb-3">
<div class="col">
<span>์ด๋ฉ์ผ</span>
<input class="w-100" type="email" name="id" placeholder="์ด๋ฉ์ผ" dr-value-link="this.form.email" required/>
<span class="">2nd input password valid message section</span>
</div>
</div>
</div>
class SignUp {
public form = new class extends Validation {
id = new class extends Validation {
public msgElement!: HTMLElement;
valid(): boolean {
let valid = false;
if (this.length == 0) {
this.msgElement.className = 'd-block text-danger';
this.msgElement.textContent = '์์ด๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.';
} else if (this.length < 3 || this.length > 20) {
this.msgElement.className = 'd-block text-danger';
this.msgElement.textContent = '์์ด๋๋ 4๊ธ์ ์ด์ 20์ ๋ฏธ๋ง ์ด์ด์ผ ํฉ๋๋ค.';
} else {
let isExist = false;
if (isExist) {
this.msgElement.className = 'd-block text-danger';
this.msgElement.textContent = '์ด๋ฏธ ์ฌ์ฉ์ค์ธ ์์ด๋์
๋๋ค. ๋ค๋ฅธ ์์ด๋๋ฅผ ์ด์ฉํด์ฃผ์ธ์.';
} else {
this.msgElement.className = 'd-block text-success';
this.msgElement.textContent = '์ฌ์ฉ ๊ฐ๋ฅํ ์์ด๋์
๋๋ค.';
valid = true;
}
}
return valid;
}
}();
password = new class extends Validation {
public msgElement!: HTMLElement;
valid(): boolean {
let valid = false;
let regExp = /^.*(?=.)(?=.*[0-9])(?=.*[a-zA-Z]).*$/;
const ERROR_REQUIRE_PW = '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.';
const ERROR_PW_LENGTH = '๋น๋ฐ๋ฒํธ๋ 8๊ธ์~20๊ธ์๋ก ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค.';
const ERROR_PW_REGEXP = '๋น๋ฐ๋ฒํธ๋ ์ซ์, ์๋ฌธ, ํน์๋ฌธ์ ์กฐํฉ์ผ๋ก ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค.';
const SUCCESS_AVAILABLE_USER_PW = '์ฌ์ฉ ๊ฐ๋ฅํ ๋น๋ฐ๋ฒํธ์
๋๋ค.';
if (this.length == 0) {
this.msgElement.className = 'd-block text-danger';
this.msgElement.textContent = ERROR_REQUIRE_PW;
} else if (this.length < 8 || this.length > 20) {
this.msgElement.className = 'd-block text-danger';
this.msgElement.textContent = ERROR_PW_LENGTH;
} else {
if (regExp.test(this.value)) {
this.msgElement.className = 'd-block text-success';
this.msgElement.textContent = SUCCESS_AVAILABLE_USER_PW;
valid = true;
} else {
this.msgElement.className = 'd-block text-danger';
this.msgElement.textContent = ERROR_PW_REGEXP;
}
}
return valid;
}
}();
confirmPassword = new class extends Validation {
value = '';
valid(): boolean {
return true;
}
}();
email = new class extends Validation {
value = '';
valid(): boolean {
return true;
}
}();
valid() {
return this.childValid()
}
}();
}
dr-form (normal)
form = new class extends Validation {
id = new class extends Validation {
valid(): boolean {
return false;
}
}()
valid(): boolean {
return false;
}
}();
console.log(form.id.value)
<form dr-form="this.form" dr-event-submit="this.submit(); $event.preventDefault();">
<input name="id">
<input name="">
<button type="submit"> submit</button>
</form>
dr-form (multiple validation)
form = new class extends Validation {
id = new class extends Validation {
valid(): boolean {
return this.length > 0;
}
}();
all = new class extends Validations<string, HTMLInputElement> {
valid(): boolean {
const inChecked = this.values?.filter(it => it.target && !it.target.checked) ?? [];
return !(inChecked.length > 0)
}
}()
gender = new class extends Validations<string, HTMLInputElement> {
valid(): boolean {
const inChecked = this.values?.filter(it => it.target && !it.target.checked) ?? [];
return inChecked.length > 0
}
}()
valid(): boolean {
return this.valids();
}
}();
submit() {
const form = (this.form as any)
console.log('submit222->', form.valid());
}
<form dr-form="this.form" dr-event-submit="this.submit(); $event.preventDefault();">
<h2>name</h2>
name: <input name="id">
<h2>all check required</h2>
<input name="all" type="checkbox" value="a">a
<input name="all" type="checkbox" value="b">b
<h2>gender chose one</h2>
<input name="gender" type="radio" value="male"> male
<input name="gender" type="radio" value="female"> Female
<br>
<button type="submit">check valid</button>
</form>
Detect Get, Set
OnBeforeReturnSet
export interface OnBeforeReturnSet {
onBeforeReturnSet(name: string, value: any, fullPath?: string[]): void;
}
OnBeforeReturnGet
export interface OnBeforeReturnGet {
onBeforeReturnGet(name: string, value: any, fullPath?: string[]): void;
}
using detect
{
name: 'dom-render'
onBeforeReturnSet: (name: string, value: any, fullpath: string[]) => {
console.log('set name-->', name, value, fullpath);
}
onBeforeReturnGet: (name: string, value: any, fullpath: string[]) => {
console.log('get name-->', name, value, fullpath);
}
}
exclude detect property: Config
- proxyExcludeOnBeforeReturnGets: ['propertyName']
- proxyExcludeOnBeforeReturnSets: ['propertyName']
Proxy
all internal variables are managed by proxy. (DomRenderProxy)
exclude proxy (situation: Maximum call stack error)
exclude detect property: Config
- proxyExcludeTyps: [Class...]
Code base
{name : Object.freeze({...})}
{name : new Shield()}
{name : DomRenderProxy.final({...})}
Config
export type TargetElement = {
_name: string,
template?: string,
styles?: string[],
callBack: (target: Element, obj: any, rawSet: RawSet) => DocumentFragment,
complete?: (target: Element, obj: any, rawSet: RawSet) => void
};
export type TargetAttr = {
name: string,
callBack: (target: Element, attrValue: string, obj: any, rawSet: RawSet) => DocumentFragment,
complete?: (target: Element, attrValue: string, obj: any, rawSet: RawSet) => void
};
export interface Config {
targetElements?: TargetElement[];
targetAttrs?: TargetAttr[];
onElementInit?: (name: string, obj: any, rawSet: RawSet) => void;
onAttrInit?: (name: string, attrValue: string, obj: any, rawSet: RawSet) => void;
proxyExcludeTyps?: ConstructorType<any>[];
proxyExcludeOnBeforeReturnSets?: string[];
proxyExcludeOnBeforeReturnGets?: string[];
scripts?: { [n: string]: any };
applyEvents?: { attrName: string, callBack: (elements: Element, attrValue: string, obj: any) => void }[];
}
License