{"id":16865483,"url":"https://github.com/easylogic/sapa","last_synced_at":"2025-03-17T05:32:39.553Z","repository":{"id":34986738,"uuid":"173138474","full_name":"easylogic/sapa","owner":"easylogic","description":"sapa is a library that creates a UI with a simple event system.","archived":false,"fork":false,"pushed_at":"2023-04-30T13:38:24.000Z","size":1852,"stargazers_count":76,"open_issues_count":10,"forks_count":9,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-10-29T17:18:58.963Z","etag":null,"topics":["dom","event","javascript","typescript"],"latest_commit_sha":null,"homepage":"https://sapa.easylogic.studio","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/easylogic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-02-28T15:36:38.000Z","updated_at":"2024-06-18T00:21:31.000Z","dependencies_parsed_at":"2023-02-18T11:46:37.897Z","dependency_job_id":null,"html_url":"https://github.com/easylogic/sapa","commit_stats":{"total_commits":111,"total_committers":3,"mean_commits":37.0,"dds":"0.18018018018018023","last_synced_commit":"47e721a8015f8dda5cba79955450cdff23a7a5d8"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/easylogic%2Fsapa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/easylogic%2Fsapa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/easylogic%2Fsapa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/easylogic%2Fsapa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/easylogic","download_url":"https://codeload.github.com/easylogic/sapa/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243846978,"owners_count":20357297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dom","event","javascript","typescript"],"created_at":"2024-10-13T14:47:14.622Z","updated_at":"2025-03-17T05:32:39.516Z","avatar_url":"https://github.com/easylogic.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sapa\n\nsapa is a library that creates a UI with a simple event system.\n# Basic concept\n\nsapa helps you to create applications naturally in html without compiling.\n\n* No compile\n* No virtual dom \n* Simple DOM event system \n* Support Typescript\n\n# Install \n\n`\nnpm install @easylogic/sapa\n`\n\n# How to use in es6\n\n```js\nimport {App, UIElement, SUBSCRIBE, CLICK} from '@easylogic/sapa'\n\n```\n\n# How to use in browser \n\n```html\n\u003cscript type='text/javascript' src='https://cdn.jsdelivr.net/npm/@easylogic/sapa@0.3.2/dist/sapa.umd.js'\u003e\u003c/script\u003e\n\u003cscript type='text/javacript'\u003e\n    const {App, CLICK, SUBSCRIBE, UIElement} = sapa;   // or window.sapa \n\u003c/script\u003e\n\n```\n\n# View examples \n\n```\nnpm run dev \nopen localhost:8080/examples/first.html\n```\n\n\n# Core System Design \n\n\n\n## Start a application \n\n```js\n\nimport {start, UIElement} from '@easylogic/sapa';\n\nclass SampleElement extends UIElement { }\n\nstart(SampleElement, {\n    container: document.getElementById('sample') // default value is document.body\n})\n```\n\nThe `start` method defines the point in time of the first run. Apply the template to the location specified by container.\n\n\n## DOM Based Class System\n\n```js\nclass MyElement extends UIElement {\n    template () {\n        return `\u003cdiv\u003emy element\u003c/div\u003e`\n    }\n}\n\n```\n\nUse the `template ()` method to specify the actual HTML string for MyElement.\n\nA UIElement can be contained in other UIElement.\n\n```js\nclass SecondElement extends UIElement {\n    components () {\n        return { MyElement }\n    }\n    template () {\n        return `\n        \u003cdiv\u003e\n            \u003cobject refClass='MyElement' /\u003e\n        \u003c/div\u003e\n        `\n    }\n}\n\n```\n\nIt creates MyElement internally when SecondElement is created. At this time, the parent property of MyElement becomes the instance of SecondElement.\n\n### register component\nYou can register global components using registElement .\n\n```js\n\nclass GlobalElement extends UIElement { }\n\nregistElement({\n    GlobalElement\n})\n\nclass Test extends UIElement {\n    template() {\n        return `\n            \u003cdiv\u003e\n                \u003cobject refClass='GlobalElement'\u003e\u003c/object\u003e\n            \u003c/div\u003e\n\n        `\n    }\n}\n\n```\n\n### add component alias \n\n```js\n\nclass GlobalElement extends UIElement { }\n\nregistElement({\n    GlobalElement\n})\n\nregistAlias('global-element', GlobalElement)\n\nclass Test extends UIElement {\n    template() {\n        return `\n            \u003cdiv\u003e\n                \u003cobject refClass='global-element'\u003e\u003c/object\u003e\n            \u003c/div\u003e\n\n        `\n    }\n}\n\n```\n\n### `refClass` attribute\n\nTo create an instance of a newly defined Element, use the `refClass` property.\n\n```js\n\u003cobject refClass=\"MyElement\" /\u003e\n```\n\nUsing the tag object has no special meaning and is used only as a name meaning creating an object.\n\nIt is free to define it in the form below.\n\n\n```js\n\u003cspan refClass=\"MyElement\" /\u003e\n```\n\n### createComponent function \n\nYou can create object tags more easily by using the createComponent function.\n\n```js\n\ncreateComponent('GlobalElement', {ref: '$globalElement'}) \n\n==\u003e // output \n\n\u003cobject refClass=\"GlobalElement\" ref=\"$globalElement\" \u003e\u003c/object\u003e\n\n```\n\n\n```js\n\nclass Test extends UIElement {\n    template() {\n        return `\n            \u003cdiv\u003e\n                ${createComponent('GlobalElement', {\n                    ref: '$globalElement'\n                })}\n            \u003c/div\u003e\n\n        `\n    }\n}\n\n```\n\n\n### Pass props \n\nsapa can create props as it is to create html.\n\n```js\n\nclass SecondElement extends UIElement {\n    components () {\n        return { MyElement }\n    }\n    template () {\n        return `\n            \u003cdiv\u003e\n                \u003cobject refClass='MyElement' title=\"my element title\" /\u003e\n            \u003c/div\u003e\n        `\n    }\n}\n\n\n```\n\n\n\n\n### Passing variables as props\n\nsapa uses html strings.\n\nSo, when passing a certain variable as props, it must be converted into a string.\n\nIn this case, it provides a way to keep the reference as it is without converting the variable to a string.\n\n\n\n```js\n\nclass SecondElement extends UIElement {\n    components () {\n        return { MyElement }\n    }\n    template () {\n        return `\n            \u003cdiv\u003e\n                \u003cobject refClass='MyElement' title=${variable({\n                    title: 'my element title'\n                })} /\u003e\n            \u003c/div\u003e\n        `\n    }\n}\n\n\n```\n\nYou can also pass props object. \n\n\n```js\n\nclass SecondElement extends UIElement {\n    components () {\n        return { MyElement }\n    }\n    template () {\n        return `\n            \u003cdiv\u003e\n                \u003cobject refClass='MyElement' ${variable({\n                    title: 'my element title',\n                    description: 'my element description'\n                })}\u003e\u003c/object\u003e\n            \u003c/div\u003e\n        `\n    }\n}\n\n\n```\n\n\n\n### Using props \n\nIt can be used by referencing the value of props through `this.props`.\n\n```js\n\nclass MyElement extends UIElement {\n\n    template () {\n        const titleObject = this.props.title;\n        return `\n            \u003cdiv\u003e\n                ${titleObject.title}\n            \u003c/div\u003e\n        `\n    }\n}\n\n```\n\n### Local State \n\nUIElement provides a state that is simple to use.\n\n```js\n\nclass MyElement extends UIElement {\n\n    // initialize local state \n    initState() {\n        return {\n            title: this.props.title\n        }\n\n    }\n\n    template () {\n        const {title} = this.state; \n        return `\n            \u003cdiv\u003e\n                ${title}\n            \u003c/div\u003e\n        `\n    }\n}\n\n\n```\n\n\n### Access DOM \n\nUse `this.$el`\n\n$el is jQuery-liked DOM wrapper object.\n\n```js\nclass Test extends UIElement {\n    template () { return '\u003cdiv class=\"test-item\"\u003e\u003c/div\u003e' }\n\n    [CLICK()] () {\n        if (this.$el.hasClass('test-item')) {\n            console.log('this element has .test-item')\n        }\n    }\n}\n```\n\n\n### ref  \n\nWhen the DOM is created, the DOM with the ref attribute is managed as a variable that can be used in advance.\n\n```js\ntemplate () {\n    return `\u003cdiv\u003e\u003cspan ref='$text'\u003e\u003c/span\u003e\u003c/div\u003e`\n}\n[CLICK('$text')]  (e) { \n    console.log(this.refs.$text.html())\n}\n```\n\nYou can apply CLICK events to the `$text` DOM object.\n\n### LOAD \n\n`LOAD` can define the part that changed frequently.\n\n```js\ntemplate () {\n    return `\n        \u003cdiv\u003e\n            \u003cdiv ref='$list'\u003e\u003c/div\u003e\n        \u003c/div\u003e\n    `\n}\n\n[LOAD('$list')] () {\n    const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n    return arr.map(value =\u003e `\u003cdiv class='item'\u003e${value}\u003c/div\u003e`)\n}\n\nrefresh( ) {\n    this.load();\n}\n```\n\n#### local load \n\nThe load function can also specify directly within the template.\n\n```js\ntemplate () {\n    return `\n        \u003cdiv\u003e\n            \u003cdiv ref='$list' load=${variable(() =\u003e { \n                const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n                return arr.map(value =\u003e `\u003cdiv class='item'\u003e${value}\u003c/div\u003e`)\n            })}\u003e\u003c/div\u003e\n        \u003c/div\u003e\n    `\n}\n\n```\n\n\n#### support async function \n\n```js\nasync [LOAD('$list')] () {\n    return await api.get('xxxx').data;\n}\n```\n\n### BIND \n\n`BIND` are used to change the attributes and style of a particular element. That is, it does not create the DOM itself.\n\n```js\ntemplate () {\n    return `\n        \u003cdiv\u003e\n            \u003cdiv ref='$list'\u003e\u003c/div\u003e\n        \u003c/div\u003e\n    `\n}\n\n[BIND('$list')] () {\n    return {\n        'data-length': arr.length,\n        style: {\n            overflow: 'hidden'\n        },\n        cssText: `\n            background-color: yellow;\n            color: white;\n            background-image: linear-gradient('xxxx')\n        `,\n        html: \"\u003cdiv\u003e\u003c/div\u003e\",\n        innerHTML: \"\u003cdiv\u003e\u003c/div\u003e\",\n        text: \"blackblack\",\n        textContent: \"redred\",        \n        class: {\n            \"is-selected\": true,\n            \"is-focused\": false,\n        },\n        class : [ 'className', 'className' ],\n        class : 'string-class',\n        htmlDiff: '\u003cdiv\u003e\u003cspan\u003e\u003c/span\u003e\u003c/div\u003e',\n        svgDiff: '\u003cg\u003e\u003crect /\u003e\u003ccircle /\u003e\u003c/g\u003e',\n        value: \"input text\",\n    }\n}\n\nrefresh( ) {\n    this.load();\n}\n```\n\nThe final output after `BIND` is as follows.\n\n```html\n\u003cdiv ref='$list' data-value='0' style='overflow:hidden'\u003e\u003c/div\u003e\n```\n\n#### local bind \n\nThe bind function can also specify directly within the template.\n\n```js\ntemplate () {\n    return `\n        \u003cdiv\u003e\n            \u003cdiv ref='$list' bind=${variable(() =\u003e { \n                color: 'white'\n            })}\u003e\u003c/div\u003e\n        \u003c/div\u003e\n    `\n}\n\n```\n\n## Run separately\n\n`LOAD` and `BIND` can be executed separately.\n\n```js\nthis.load('$list')\nthis.bindData('$list');\n```\n\n## Life Cycle \n\nsapa has a life cycle. \n\n```js\nUIElement -\u003e\n    created()\n    initialize() -\u003e \n        initState()\n    render -\u003e \n        template() \n        parseComponent() -\u003e \n            create child component -\u003e \n    load()            \n    initializeEvent()\n    afterRender()\n```\n\n| Method | Override | Description |\n| --- | --- | --- |\n| created | O | When the UIElement is created  |\n| initialize | O | It is the same as `created` but it is used when creating initial data. |\n| initState | O | Methods to initialize state  |\n| template | O | Generate html at render time |\n| afterRender | O | When the DOM is applied to the actual browser, the element can be accessed from outside |\n\n## Method Based DOM Event Handler \n\nsapa sets the DOM Event in a unique way. sapa take full advantage of the fact that javascript's methods are strings.\n\n```js\nclass Test extends UIElement {\n    template() {\n        return '\u003cdiv\u003eText\u003c/div\u003e'\n    }\n\n    [CLICK()] (e) {\n        console.log(e);\n    }\n}\n```\n\n`[CLICK()]` is basically the same as `CLICK('$el')`. Sets `$el`'s click event automatically.\n\nThe `CLICK()` method internally creates a string. The final result is shown below.\n\n```js\n'click $el' (e) { \n    // console.log(e);\n}\n```\n\n### Support DOM Event List \n\n```\nCLICK = \"click\"\nDOUBLECLICK = \"dblclick\"\nMOUSEDOWN = \"mousedown\"\nMOUSEUP = \"mouseup\"\nMOUSEMOVE = \"mousemove\"\nMOUSEOVER = \"mouseover\"\nMOUSEOUT = \"mouseout\"\nMOUSEENTER = \"mouseenter\"\nMOUSELEAVE = \"mouseleave\"\nTOUCHSTART = \"touchstart\"\nTOUCHMOVE = \"touchmove\"\nTOUCHEND = \"touchend\"\nKEYDOWN = \"keydown\"\nKEYUP = \"keyup\"\nKEYPRESS = \"keypress\"\nDRAG = \"drag\"\nDRAGSTART = \"dragstart\"\nDROP = \"drop\"\nDRAGOVER = \"dragover\"\nDRAGENTER = \"dragenter\"\nDRAGLEAVE = \"dragleave\"\nDRAGEXIT = \"dragexit\"\nDRAGOUT = \"dragout\"\nDRAGEND = \"dragend\"\nCONTEXTMENU = \"contextmenu\"\nCHANGE = \"change\"\nINPUT = \"input\"\nFOCUS = \"focus\"\nFOCUSIN = \"focusin\"\nFOCUSOUT = \"focusout\"\nBLUR = \"blur\"\nPASTE = \"paste\"\nRESIZE = \"resize\"\nSCROLL = \"scroll\"\nSUBMIT = \"submit\"\nPOINTERSTART = \"mousedown\", \"touchstart\"\nPOINTERMOVE = \"mousemove\", \"touchmove\"\nPOINTEREND = \"mouseup\", \"touchend\"\nCHANGEINPUT = \"change\", \"input\"\nWHEEL = \"wheel\", \"mousewheel\", \"DOMMouseScroll\"\n```\n\nYou can define any additional events you need. Common DOM events are defined.\n\nYou can set several DOM events at the same time.\n\n```\nPOINTERSTART is a defined name. Two events are actually specified, namely `mousedown` and `touchstart`.\n```\n\nDOM events can have some special elements other than $ el.\n\n### ref  \n\nWhen the DOM is created, the DOM with the ref attribute is managed as a variable that can be used in advance.\n\n```js\ntemplate () {\n    return `\u003cdiv\u003e\u003cspan ref='$text'\u003e\u003c/span\u003e\u003c/div\u003e`\n}\n[CLICK('$text')]  (e) { }\n```\n\nYou can apply CLICK events to the `$text` DOM object.\n\n\n### window, document \n\nGlobal objects such as window and document can also apply events to their methods.\n\n```js\n[RESIZE('window')] (e) { }\n[POINTERSTART('document')] (e) { }\n```\n\n### delegate \n\nApplying events to individual DOMs may be bad for performance. In that case, use delegate to handle it.\n\n\n```js\ntemplate () {\n    return `\n    \u003cdiv\u003e\n        \u003cdiv class='list' ref='$list'\u003e\n            \u003cdiv class='item'\u003eItem\u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n    `\n}\n\n[CLICK('$list .item')] (e) {\n    // this method will run after .item element is clicked\n}\n```\n\nThis is also possible the css selector.\n\n\n```js\n[CLICK('$list .item:not(.selected)')] (e) {\n    // do event \n    console.log(e.$dt.html())\n}\n```\nYou can run the method only when you click on the `.item` that is not applied to the` .selected` class.\n\n`e.$dt` points to the element where the actual event occurred.\n\n\nDOM events can have several PIPE functions.\n\nPIPE is a concept that combines predefined functions in an event.\n\n### ALT\n\nThe event will only work when Alt key is pressed.\n\n\n```js\n[CLICK() + ALT] (e) {\n    // when alt key is pressed\n}\n```\n\nIn addition to ALT, you can use default key combinations such as CTRL, SHIFT, and META.\n\nPIPE can be connected with `+` character.\n\n```js\n[CLICK() + ALT + CTRL] (e) {\n    // when alt and control key are pressed \n}\n\n```\n\n### IF \n\nwhen checkTarget's result is true, this method is run\n\n```js\ncheckTarget(e) {\n    if (e.target.nodeType != 3) return false;\n    return true; \n}\n[CLICK() + IF('checkTarget')] (e) {}\n```\n\n### check LeftMouseButton or RightMouseButton \n\n```js\n[CLICK() + LEFT_BUTTON] (e) {}\n\n[CLICK() + RIGHT_BUTTON] (e) {}\n```\n\n### DEBOUNCE \n\nSome PIPEs can also use actual methods in other ways. A typical example is DEBOUNCE.\n\n```js\n[RESIZE('window') + DEBOUNCE(100)] (e) {}\n```\n\nTROTTLE is also available.\n\n```js\n[SCROLL('document') + TROTTLE(100)] (e) {}\n```\n\n## Method Based Messaging System \n\nsapa has a simple event system for sending messages between objects.\n\nThis also uses `method` string, just like specifying a DOM event.\n\n### SUBSCRIBE \n\nSUBSCRIBE allows you to receive emit messages from elsewhere. \n\nProvides a callback to send and receive messages even if they are not connected.\n\n\n```js\n\nclass A extends UIElement {\n    [SUBSCRIBE('setLocale')] (locale) {\n        console.log(locale);\n    }\n}\n\nclass B extends UIElement {\n    template () {\n        return `\u003cbutton type=\"button\"\u003eClick\u003c/button\u003e`\n    }\n\n    [CLICK()] () {\n        this.emit('setLocale', 'ko')\n    }\n}\n\nApp.start({\n    components : {\n        A, B\n    },\n    template : `\n        \u003cdiv\u003e\n            \u003cA /\u003e\n            \u003cB /\u003e\n        \u003c/div\u003e\n    `\n})\n\n```\n\n\n### emit\n\n`emit` is a method that delivers a message to an object other than itself.\n\n\n```js\n[CLICK()] () {\n    this.emit('setLocale', 'ko')\n}\n```\nwhy does not it send to its element?\n\nThe reason for not sending to itself is that there is a possibility that the event can run infinitely. Once I send the message, I can not come back to me.\n\n### multiple SUBSCRIBE \n\nSUBSCRIBE can define several at the same time.\n\n```js\n\n[SUBSCRIBE('a', 'b', 'c')] () {\n    // \n}\n\n// this.emit('a')\n// this.emit('b')\n// this.emit('c')\n\n```\n\n### DEBOUNCE \n\nYou can also slow down the execution time of a message.\n\n```js\n\n[SUBSCRIBE('a') + DEBOUNCE(100)] () {\n\n}\n\n```\n\n### THROTTLE \n\nYou can also slow down the execution time of a message.\n\n```js\n\n[SUBSCRIBE('a') + THROTTLE(100)] () {\n\n}\n\n```\n\n### FRAME \n\nYou can run subscribe function by requestAnimationFrame.\n\n```js\nclass A extends UIElement {\n    [SUBSCRIBE('animationStart') + FRAME] () {\n        console.log('Aanimation is started.');\n    }\n}\n```\n\n\n#### IF\n\n```js\nclass A extends UIElement {\n\n    checkShow(locale) {\n        return true;        // 실행 가능 \n    }\n\n    [SUBSCRIBE('setLocale') + IF(\"checkShow\")] (locale) {\n        console.log(locale);\n    }\n}\n```\n\n\n### trigger \n\nThe trigger method allows you to execute an event defined on the object itself. Messages sent by trigger are not propagated elsewhere.\n \n```js\nthis.trigger('setLocale', 'en')  // setLocale message is run only on self instance \n```\n\nIf you want to send a message only to the parent object, you can do the following:\n\n```js\nthis.parent.trigger('setLocale', 'en'); \n```\n\n\n### SUBSCRIBE_SELF \n\ntrigger 함수로만 호출 할 수 있음.  나 자신의 이벤트를 실행하는 방법 \n\n\n```js\nclass A extends UIElement {\n    [SUBSCRIBE_SELF('setLocale')] (locale) {\n        console.log(locale);\n    }\n}\n```\n\n# Simple example \n\nThis sample make a clickable element.\n\n```js\n\nimport {start, UIElement, CLICK} from 'sapa'\n\nclass Test extends UIElement {\n    template() {\n        return '\u003cdiv\u003eText\u003c/div\u003e'\n    }\n\n    [CLICK()] (e) {\n        console.log(e);\n    }\n}\n\nstart(Test, {\n    container: document.getElementById('app')\n});\n\n```\n\n# Development\n\n`\nnpm run dev\n`\n\n# How to build \n\n`\nnpm run build\n`\n\n# Projects \n\n* https://github.com/easylogic/editor - Web Design Editor\n\n\n\n# LICENSE: MIT \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feasylogic%2Fsapa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feasylogic%2Fsapa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feasylogic%2Fsapa/lists"}