├── .editorconfig ├── .gitattributes ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── configs ├── tsconfig-build-amd.json ├── tsconfig-build-commonjs.json ├── tsconfig-build-es2015.json ├── tsconfig-build-es2017.json ├── tsconfig-build-native-modules.json ├── tsconfig-build-system.json ├── tsconfig-build.json ├── tsconfig-demo.json ├── tsconfig-projectroot.json ├── tsconfig-test.json ├── tsconfig-webpack.json └── tsconfig.json ├── demo ├── app.css ├── app.html ├── app.ts ├── index.ejs ├── main.ts ├── resources │ ├── elements │ │ ├── dynamic-html.ts │ │ ├── monaco-editor.css │ │ ├── monaco-editor.html │ │ └── monaco-editor.ts │ └── index.ts └── tsconfig.json ├── dist ├── amd │ ├── aurelia-dynamic-html.d.ts │ ├── aurelia-dynamic-html.js │ ├── dynamic-html.d.ts │ ├── dynamic-html.js │ ├── interfaces.d.ts │ └── interfaces.js ├── commonjs │ ├── aurelia-dynamic-html.d.ts │ ├── aurelia-dynamic-html.js │ ├── dynamic-html.d.ts │ ├── dynamic-html.js │ ├── interfaces.d.ts │ └── interfaces.js ├── es2015 │ ├── aurelia-dynamic-html.d.ts │ ├── aurelia-dynamic-html.js │ ├── dynamic-html.d.ts │ ├── dynamic-html.js │ ├── interfaces.d.ts │ └── interfaces.js ├── es2017 │ ├── aurelia-dynamic-html.d.ts │ ├── aurelia-dynamic-html.js │ ├── dynamic-html.d.ts │ ├── dynamic-html.js │ ├── interfaces.d.ts │ └── interfaces.js ├── native-modules │ ├── aurelia-dynamic-html.d.ts │ ├── aurelia-dynamic-html.js │ ├── dynamic-html.d.ts │ ├── dynamic-html.js │ ├── interfaces.d.ts │ └── interfaces.js └── system │ ├── aurelia-dynamic-html.d.ts │ ├── aurelia-dynamic-html.js │ ├── dynamic-html.d.ts │ ├── dynamic-html.js │ ├── interfaces.d.ts │ └── interfaces.js ├── karma.conf.ts ├── package-scripts.js ├── package.json ├── rollup.config.js ├── src ├── aurelia-dynamic-html.ts ├── dynamic-html.ts └── interfaces.ts ├── test ├── setup.ts └── unit │ ├── aurelia-dynamic-html.spec.ts │ ├── dynamic-html.spec.ts │ └── util.ts ├── tsconfig.json ├── tslint.json └── webpack.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | ## AUTO-DETECT 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text=auto 17 | 18 | ## SOURCE CODE 19 | *.bat text eol=crlf 20 | *.coffee text 21 | *.css text 22 | *.htm text 23 | *.html text 24 | *.inc text 25 | *.ini text 26 | *.js text 27 | *.json text 28 | *.jsx text 29 | *.less text 30 | *.od text 31 | *.onlydata text 32 | *.php text 33 | *.pl text 34 | *.py text 35 | *.rb text 36 | *.sass text 37 | *.scm text 38 | *.scss text 39 | *.sh text eol=lf 40 | *.sql text 41 | *.styl text 42 | *.tag text 43 | *.ts text 44 | *.tsx text 45 | *.xml text 46 | *.xhtml text 47 | 48 | ## DOCKER 49 | *.dockerignore text 50 | Dockerfile text 51 | 52 | ## DOCUMENTATION 53 | *.markdown text 54 | *.md text 55 | *.mdwn text 56 | *.mdown text 57 | *.mkd text 58 | *.mkdn text 59 | *.mdtxt text 60 | *.mdtext text 61 | *.txt text 62 | AUTHORS text 63 | CHANGELOG text 64 | CHANGES text 65 | CONTRIBUTING text 66 | COPYING text 67 | copyright text 68 | *COPYRIGHT* text 69 | INSTALL text 70 | license text 71 | LICENSE text 72 | NEWS text 73 | readme text 74 | *README* text 75 | TODO text 76 | 77 | ## TEMPLATES 78 | *.dot text 79 | *.ejs text 80 | *.haml text 81 | *.handlebars text 82 | *.hbs text 83 | *.hbt text 84 | *.jade text 85 | *.latte text 86 | *.mustache text 87 | *.njk text 88 | *.phtml text 89 | *.tmpl text 90 | *.tpl text 91 | *.twig text 92 | 93 | ## LINTERS 94 | .csslintrc text 95 | .eslintrc text 96 | .htmlhintrc text 97 | .jscsrc text 98 | .jshintrc text 99 | .jshintignore text 100 | .stylelintrc text 101 | 102 | ## CONFIGS 103 | *.bowerrc text 104 | *.cnf text 105 | *.conf text 106 | *.config text 107 | .browserslistrc text 108 | .editorconfig text 109 | .gitattributes text 110 | .gitconfig text 111 | .htaccess text 112 | *.npmignore text 113 | *.yaml text 114 | *.yml text 115 | browserslist text 116 | Makefile text 117 | makefile text 118 | 119 | ## HEROKU 120 | Procfile text 121 | .slugignore text 122 | 123 | ## GRAPHICS 124 | *.ai binary 125 | *.bmp binary 126 | *.eps binary 127 | *.gif binary 128 | *.ico binary 129 | *.jng binary 130 | *.jp2 binary 131 | *.jpg binary 132 | *.jpeg binary 133 | *.jpx binary 134 | *.jxr binary 135 | *.pdf binary 136 | *.png binary 137 | *.psb binary 138 | *.psd binary 139 | *.svg text 140 | *.svgz binary 141 | *.tif binary 142 | *.tiff binary 143 | *.wbmp binary 144 | *.webp binary 145 | 146 | ## AUDIO 147 | *.kar binary 148 | *.m4a binary 149 | *.mid binary 150 | *.midi binary 151 | *.mp3 binary 152 | *.ogg binary 153 | *.ra binary 154 | 155 | ## VIDEO 156 | *.3gpp binary 157 | *.3gp binary 158 | *.as binary 159 | *.asf binary 160 | *.asx binary 161 | *.fla binary 162 | *.flv binary 163 | *.m4v binary 164 | *.mng binary 165 | *.mov binary 166 | *.mp4 binary 167 | *.mpeg binary 168 | *.mpg binary 169 | *.ogv binary 170 | *.swc binary 171 | *.swf binary 172 | *.webm binary 173 | 174 | ## ARCHIVES 175 | *.7z binary 176 | *.gz binary 177 | *.jar binary 178 | *.rar binary 179 | *.tar binary 180 | *.zip binary 181 | 182 | ## FONTS 183 | *.ttf binary 184 | *.eot binary 185 | *.otf binary 186 | *.woff binary 187 | *.woff2 binary 188 | 189 | ## EXECUTABLES 190 | *.exe binary 191 | *.pyc binary 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /scripts 3 | coverage 4 | yarn-error.log 5 | .rollupcache 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "AureliaEffect.aurelia", 4 | "behzad88.Aurelia", 5 | "christian-kohler.path-intellisense", 6 | "EditorConfig.EditorConfig", 7 | "esbenp.prettier-vscode", 8 | "joelday.docthis", 9 | "steoates.autoimport" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "attach", 10 | "name": "Attach Karma Chrome", 11 | "address": "localhost", 12 | "port": 9333, 13 | "sourceMaps": true, 14 | "webRoot": "${workspaceRoot}", 15 | "sourceMapPathOverrides": { 16 | "webpack:///*": "${webRoot}/*" 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "typescript.tsdk": "./node_modules/typescript/lib", 4 | "tslint.nodePath": "./node_modules/tslint/bin/tslint", 5 | "html.suggest.angular1": false, 6 | "html.suggest.ionic": false, 7 | "html.format.endWithNewline": true, 8 | "tslint.exclude": [ 9 | "**/node_modules/**", 10 | "**/dist/**" 11 | ], 12 | "emmet.triggerExpansionOnTab": true, 13 | "search.exclude": { 14 | "**/node_modules": true, 15 | "**/dist": true, 16 | "**/doc": true, 17 | "**/.vscode": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [0.3.1](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.3.0...v0.3.1) (2018-05-07) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **build:** back to tsc for multi-file output ([26c5638](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/26c5638)) 12 | 13 | 14 | 15 | 16 | # [0.3.0](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.2.5...v0.3.0) (2018-05-07) 17 | 18 | 19 | ### Features 20 | 21 | * **dynamic-html:** dispatch compiled event when compilation is done ([2518c8c](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/2518c8c)) 22 | 23 | 24 | 25 | 26 | ## [0.2.5](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.2.4...v0.2.5) (2018-05-07) 27 | 28 | 29 | 30 | 31 | ## [0.2.4](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.2.3...v0.2.4) (2018-05-07) 32 | 33 | 34 | 35 | 36 | ## [0.2.3](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.2.2...v0.2.3) (2018-05-06) 37 | 38 | 39 | 40 | 41 | ## [0.2.2](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.2.1...v0.2.2) (2018-05-06) 42 | 43 | 44 | 45 | 46 | ## [0.2.1](https://github.com/aurelia-contrib/aurelia-dynamic-html/compare/v0.2.0...v0.2.1) (2018-05-06) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * split out interfaces in separate file and export everything via index ([dd80902](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/dd80902)) 52 | 53 | 54 | 55 | 56 | # 0.2.0 (2018-05-06) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * **package-scripts:** checkout master after setting up gh-pages ([63f596a](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/63f596a)) 62 | * **package-scripts:** fix ghpages-setup script ([6b32d97](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/6b32d97)) 63 | 64 | 65 | ### Features 66 | 67 | * **demo:** add something to render for demo app ([f4df84c](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/f4df84c)) 68 | * **dynamic-html:** initial implementation ([833d8b2](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/833d8b2)) 69 | * **rollup:** switch to rollup (but retain tsc as an alternative build setup) ([b7923f9](https://github.com/aurelia-contrib/aurelia-dynamic-html/commit/b7923f9)) 70 | 71 | 72 | 73 | # Change Log 74 | 75 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Fred Kleuver. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aurelia-dynamic-html 2 | 3 | Aurelia custom element that takes (server- or client side) generated html and compiles it into a fully functional Aurelia View. 4 | 5 | View [LIVE DEMO](https://aurelia-contrib.github.io/aurelia-dynamic-html/) 6 | 7 | ## Installation 8 | Install the npm dependency via 9 | 10 | ```bash 11 | npm i aurelia-dynamic-html 12 | ``` 13 | 14 | or via the Aurelia CLI 15 | 16 | ```bash 17 | au install aurelia-dynamic-html 18 | ``` 19 | 20 | ## Aurelia-CLI 21 | 22 | For Aurelia-CLI projects based on RequireJS or SystemJS, the following will install and declare the dependency in your aurelia.json: 23 | 24 | ```bash 25 | au install aurelia-dynamic-html 26 | ``` 27 | 28 | or if you have already installed and only need to add the dependency to aurelia.json: 29 | 30 | ```bash 31 | au import aurelia-dynamic-html 32 | ``` 33 | 34 | alternatively you can manually add the dependency to your vendor.bundles: 35 | 36 | ```json 37 | "dependencies": [ 38 | { 39 | "name": "aurelia-dynamic-html", 40 | "path": "../node_modules/aurelia-dynamic-html/dist/amd", 41 | "main": "aurelia-dynamic-html" 42 | } 43 | ] 44 | ``` 45 | 46 | ## Configuration 47 | 48 | ```typescript 49 | import { Aurelia } from "aurelia-framework"; 50 | 51 | export function configure(au: Aurelia) { 52 | au.use.standardConfiguration(); 53 | 54 | au.use.plugin("aurelia-dynamic-html"); // don't forget PLATFORM.moduleName if you're using webpack 55 | 56 | au.start().then(() => au.setRoot()); 57 | } 58 | ``` 59 | 60 | ## Usage 61 | 62 | ### Inline html, implicit $this context 63 | 64 | * Input 65 | 66 | ```ts 67 | export class App { 68 | message = "Hello world!"; 69 | } 70 | ``` 71 | 72 | ```html 73 | 76 | ``` 77 | 78 | * Output 79 | 80 | ```html 81 |
Hello world!
82 | ``` 83 | 84 | Note: the variants below also apply to inline html, but are omitted for brevity 85 | 86 | ### Html variable, implicit $this context 87 | 88 | * Input 89 | 90 | ```ts 91 | export class App { 92 | message = "Hello world!"; 93 | html = "${message}"; 94 | } 95 | ``` 96 | 97 | ```html 98 | 101 | ``` 102 | 103 | * Output 104 | 105 | ```html 106 |
Hello world!
107 | ``` 108 | 109 | ### Html variable, explicit $this context 110 | 111 | * Input 112 | 113 | ```ts 114 | export class App { 115 | message = "Hello world!"; 116 | html = "${message}"; 117 | } 118 | ``` 119 | 120 | ```html 121 | 124 | ``` 125 | 126 | * Output 127 | 128 | ```html 129 |
Hello world!
130 | ``` 131 | 132 | 133 | ### Html variable, context variable 134 | 135 | * Input 136 | 137 | ```ts 138 | export class App { 139 | context = { message: "Hello world!" }; 140 | html = "${message}"; 141 | } 142 | ``` 143 | 144 | ```html 145 | 148 | ``` 149 | 150 | * Output 151 | 152 | ```html 153 |
Hello world!
154 | ``` 155 | 156 | 157 | ### Html variable, context variable (complex / nested) 158 | 159 | The html and context can come from any source, be of arbitrary complexity, and work for any Aurelia feature. 160 | 161 | * Input 162 | 163 | ```ts 164 | export class App { 165 | context = { message: "Hello world!", html: "${message}" }; 166 | html = ""; 167 | } 168 | ``` 169 | 170 | ```html 171 | 174 | ``` 175 | 176 | * Output 177 | 178 | ```html 179 |
Hello world!
180 | ``` 181 | 182 | ### Erroneous html, do not render errors 183 | 184 | * Input 185 | 186 | ```ts 187 | export class App { 188 | context = { message: "Hello world!" }; 189 | html = "${message"; // missing closing brace 190 | } 191 | ``` 192 | 193 | ```html 194 | 197 | ``` 198 | 199 | * Output 200 | 201 | ```html 202 |
203 | ``` 204 | 205 | ### Erroneous html, render errors 206 | 207 | * Input 208 | 209 | ```ts 210 | export class App { 211 | context = { message: "Hello world!" }; 212 | html = "${message"; // missing closing brace 213 | } 214 | ``` 215 | 216 | ```html 217 | 220 | ``` 221 | 222 | * Output 223 | 224 | ```html 225 |
Parser Error: Missing expected token } (...)
226 | ``` 227 | 228 | ## Building The Code 229 | 230 | 231 | 1. From the project folder, execute the following command: 232 | 233 | ``` 234 | yarn/npm install 235 | ``` 236 | 2. To build the code: 237 | 238 | ``` 239 | npm run build 240 | ``` 241 | 242 | ## Running The Tests 243 | 244 | 1. To run the tests 245 | 246 | ``` 247 | npm run test 248 | ``` 249 | 250 | 2. To continuously run the tests 251 | 252 | ``` 253 | npm run develop 254 | ``` 255 | 256 | 257 | -------------------------------------------------------------------------------- /configs/tsconfig-build-amd.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-build.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/amd", 5 | "module": "amd" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /configs/tsconfig-build-commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-build.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/commonjs", 5 | "module": "commonjs" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /configs/tsconfig-build-es2015.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-build.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/es2015" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /configs/tsconfig-build-es2017.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-build.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/es2017", 5 | "target": "es2017" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /configs/tsconfig-build-native-modules.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-build.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/native-modules" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /configs/tsconfig-build-system.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-build.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/system", 5 | "module": "system" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /configs/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "noUnusedLocals": true, 6 | "noUnusedParameters": true, 7 | "strict": true, 8 | "stripInternal": true 9 | }, 10 | "include": ["../src"] 11 | } 12 | -------------------------------------------------------------------------------- /configs/tsconfig-demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["../src", "../demo"] 4 | } 5 | -------------------------------------------------------------------------------- /configs/tsconfig-projectroot.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true, 6 | "strict": true, 7 | "module": "commonjs", 8 | "target": "es5" 9 | }, 10 | "include": ["../src", "../test"] 11 | } 12 | -------------------------------------------------------------------------------- /configs/tsconfig-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["../src", "../test"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "target": "es5", 7 | "sourceMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /configs/tsconfig-webpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es5" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /configs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | //"allowJs": false, 5 | "allowSyntheticDefaultImports": true, //default: module === "system" or --esModuleInterop 6 | //"allowUnreachableCode": false, 7 | //"allowUnusedLabels": false, 8 | //"alwaysStrict": false, 9 | //"baseUrl": "", 10 | //"charset": "utf8", 11 | //"checkJs": false, 12 | //"declaration": false, 13 | //"declarationDir": "", 14 | //"diagnostics": false, 15 | //"disableSizeLimit": false, 16 | "downlevelIteration": true, //default: false 17 | //"emitBOM": false, 18 | "emitDecoratorMetadata": true, //default: false 19 | "esModuleInterop": true, //default: false 20 | "experimentalDecorators": true, //default: false 21 | "forceConsistentCasingInFileNames": true, //default: false 22 | //"importHelpers": false, 23 | //"inlineSourceMap": false, 24 | //"inlineSources": false, 25 | //"jsxFactory": "React.createElement", 26 | "lib": ["dom", "es2017"], //default: --target ES5: DOM,ES5,ScriptHost; --target ES6: DOM,ES6,DOM.Iterable,ScriptHost 27 | //"listEmittedFiles": false, 28 | //"listFiles": false, 29 | //"mapRoot": "", 30 | //"maxNodeModuleJsDepth": 0, 31 | "module": "es2015", //default: target === "ES3" or "ES5" ? "CommonJS" : "ES6" 32 | "moduleResolution": "node", //default: module === "AMD" or "System" or "ES6" ? "Classic" : "Node", 33 | "newLine": "lf", //default: (platform specific) 34 | //"noEmit": false, 35 | //"noEmitHelpers": false, 36 | //"noEmitOnError": false, 37 | //"noFallthroughCasesInSwitch": false, 38 | //"noImplicitAny": false, 39 | "noImplicitReturns": true, //default: false 40 | //"noImplicitThis": false, 41 | //"noImplicitUseStrict": false, 42 | //"noLib": false, 43 | //"noResolve": false, 44 | //"noStrictGenericChecks": false, 45 | //"noUnusedLocals": false, 46 | //"noUnusedParameters": false, 47 | //"outDir": "", 48 | //"outFile": "", 49 | //"paths": {}, 50 | "preserveConstEnums": true, //default: false 51 | //"preserveSymlinks": false, 52 | //"pretty": false, 53 | //"removeComments": false, 54 | //"rootDir": "", 55 | //"rootDirs": [], 56 | "skipLibCheck": true, //default: false 57 | //"sourceMap": false, 58 | //"sourceRoot": "", 59 | //"strict": false, 60 | //"strictFunctionTypes": false, 61 | //"strictPropertyInitialization": false, 62 | //"strictNullChecks": false, 63 | //"stripInternal": false, 64 | //"suppressExcessPropertyErrors": false, 65 | "target": "es2015", //default: es3 66 | //"traceResolution": false, 67 | //"typeRoots": [], 68 | //"watch": false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /demo/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | [aurelia-app] { 4 | width: 100vw; 5 | height: 100vh; 6 | margin: 0; 7 | border: 0; 8 | padding: 0; 9 | overflow-y: hidden; 10 | overflow-x: hidden; 11 | } 12 | 13 | [aurelia-app] { 14 | display: grid; 15 | grid-template-columns: 1fr 1fr; 16 | grid-template-rows: 1fr 1fr; 17 | grid-template-areas: 18 | 'tleft right' 19 | 'bleft right'; 20 | } 21 | 22 | .area-tleft { 23 | grid-area: tleft; 24 | } 25 | 26 | .area-bleft { 27 | grid-area: bleft; 28 | } 29 | 30 | .area-right { 31 | grid-area: right; 32 | padding: 12px; 33 | overflow-y: auto; 34 | } 35 | 36 | .content-area { 37 | border: 1px solid grey; 38 | } 39 | -------------------------------------------------------------------------------- /demo/app.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /demo/app.ts: -------------------------------------------------------------------------------- 1 | import { observable } from "aurelia-binding"; 2 | 3 | export class App { 4 | @observable() public jsRaw: string; 5 | 6 | public html: string; 7 | public context: any; 8 | 9 | constructor() { 10 | this.jsRaw = initialJs; 11 | this.html = initialHTML; 12 | } 13 | 14 | public jsRawChanged(newValue: string, oldValue: string): void { 15 | const functionBody = `return ${newValue}`; 16 | // tslint:disable-next-line:no-function-constructor-with-string-args 17 | const ctorFactory = new Function(functionBody); 18 | const ctor = ctorFactory(); 19 | this.context = new ctor(); 20 | } 21 | } 22 | 23 | // tslint:disable:no-multiline-string 24 | const initialJs = `class Foo { 25 | constructor() { 26 | this.firstName = 'John'; 27 | this.lastName = 'Doe'; 28 | } 29 | 30 | submit() { 31 | alert('You submitted "' + this.firstName + ' ' + this.lastName + '"'); 32 | } 33 | }`; 34 | 35 | const initialHTML = `
36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 |
45 |
Your name is:
46 |

\${firstName} \${lastName}

47 |
48 | 49 |
`; 50 | -------------------------------------------------------------------------------- /demo/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- htmlWebpackPlugin.options.metadata.title %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | <% if (htmlWebpackPlugin.options.metadata.server) { %> 26 | 27 | 28 | <% } %> 29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/main.ts: -------------------------------------------------------------------------------- 1 | import { Aurelia } from "aurelia-framework"; 2 | import { PLATFORM } from "aurelia-pal"; 3 | 4 | export async function configure(au: Aurelia): Promise { 5 | au.use.standardConfiguration(); 6 | 7 | au.use.feature(PLATFORM.moduleName("resources/index")); 8 | 9 | await au.start(); 10 | 11 | const host = document.querySelector("[aurelia-app]"); 12 | 13 | await au.setRoot(PLATFORM.moduleName("app"), host); 14 | } 15 | -------------------------------------------------------------------------------- /demo/resources/elements/dynamic-html.ts: -------------------------------------------------------------------------------- 1 | import { bindingMode, createOverrideContext, OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { getLogger } from "aurelia-logging"; 4 | import { TaskQueue } from "aurelia-task-queue"; 5 | import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating"; 6 | import { IBindingContext, IOverrideContext } from "../../../src/interfaces"; 7 | 8 | 9 | const logger = getLogger("dynamic-html"); 10 | 11 | @customElement("dynamic-html") 12 | @inlineView("") 13 | export class DynamicHtml { 14 | @bindable({ defaultBindingMode: bindingMode.toView }) 15 | public html: string | null; 16 | 17 | @bindable({ defaultBindingMode: bindingMode.toView }) 18 | public context: IBindingContext | null; 19 | 20 | @bindable({ defaultBindingMode: bindingMode.toView }) 21 | public renderErrors: boolean; 22 | 23 | public slot: ViewSlot | null; 24 | public bindingContext: IBindingContext | null; 25 | public overrideContext: IOverrideContext | OverrideContext | null; 26 | public isAttached: boolean; 27 | public isBound: boolean; 28 | public isCompiled: boolean; 29 | public isCleanedUp: boolean; 30 | public isInitialized: boolean; 31 | 32 | public el: HTMLElement; 33 | protected tq: TaskQueue; 34 | protected container: Container; 35 | protected viewCompiler: ViewCompiler; 36 | 37 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler) { 38 | this.el = el as HTMLElement; 39 | this.tq = tq; 40 | this.container = container; 41 | this.viewCompiler = viewCompiler; 42 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 43 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 44 | this.isCleanedUp = true; 45 | } 46 | 47 | public bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void { 48 | this.isBound = true; 49 | 50 | this.bindingContext = this.context || bindingContext.context || bindingContext; 51 | this.overrideContext = createOverrideContext(bindingContext, overrideContext); 52 | 53 | this.htmlChanged(this.html); 54 | } 55 | 56 | public unbind(): void { 57 | this.isBound = false; 58 | 59 | this.bindingContext = null; 60 | this.overrideContext = null; 61 | } 62 | 63 | public attached(): void { 64 | this.isAttached = true; 65 | this.isInitialized = true; 66 | 67 | this.slot = new ViewSlot(this.el.firstElementChild || this.el, true); 68 | 69 | this.tq.queueMicroTask(() => { 70 | this.tryCompile(); 71 | }); 72 | } 73 | 74 | public detached(): void { 75 | this.isAttached = false; 76 | 77 | if (this.isCompiled) { 78 | this.cleanUp(); 79 | } 80 | this.slot = null; 81 | } 82 | 83 | protected htmlChanged(newValue: string | null, __?: string): void { 84 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 85 | this.cleanUp(); 86 | } else if (this.isBound || this.isInitialized) { 87 | this.tq.queueMicroTask(() => { 88 | this.tryCompile(); 89 | }); 90 | } 91 | } 92 | 93 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void { 94 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 95 | this.bindingContext = this.overrideContext.bindingContext; 96 | } else { 97 | this.bindingContext = newValue; 98 | } 99 | if (this.isBound || this.isInitialized) { 100 | this.tq.queueMicroTask(() => { 101 | this.tryCompile(); 102 | }); 103 | } 104 | } 105 | 106 | protected tryCompile(): void { 107 | if (this.isAttached) { 108 | if (!this.isCleanedUp) { 109 | this.cleanUp(); 110 | } 111 | try { 112 | this.tq.queueMicroTask(() => { 113 | this.compile(); 114 | }); 115 | } catch (e) { 116 | logger.warn(e.message); 117 | if (this.renderErrors) { 118 | this.tq.queueMicroTask(() => { 119 | this.compile(``); 120 | }); 121 | } 122 | } 123 | } 124 | } 125 | 126 | protected cleanUp(): void { 127 | this.isCompiled = false; 128 | 129 | logger.debug("Cleaning up"); 130 | 131 | const slot = this.slot as ViewSlot; 132 | try { 133 | slot.detached(); 134 | } catch (e) { 135 | logger.error(e.message); 136 | } 137 | try { 138 | slot.unbind(); 139 | } catch (e) { 140 | logger.error(e.message); 141 | } 142 | try { 143 | slot.removeAll(); 144 | } catch (e) { 145 | logger.error(e.message); 146 | } 147 | 148 | this.isCleanedUp = true; 149 | } 150 | 151 | protected compile(message?: string): void { 152 | if (!this.isCleanedUp) { 153 | this.cleanUp(); 154 | } 155 | if ((this.html === null || this.html === undefined) && message === undefined) { 156 | logger.debug("Skipping compilation because no html value is set"); 157 | 158 | return; 159 | } 160 | this.isCleanedUp = false; 161 | 162 | const template = ``; 163 | 164 | logger.debug("Compiling", template, this.bindingContext); 165 | 166 | const viewResources = this.container.get(ViewResources) as ViewResources; 167 | const childContainer = this.container.createChild(); 168 | const factory = this.viewCompiler.compile(template, viewResources); 169 | const view = factory.create(childContainer); 170 | const slot = this.slot as ViewSlot; 171 | 172 | slot.add(view); 173 | slot.bind(this.bindingContext as IBindingContext, this.overrideContext as IOverrideContext); 174 | slot.attached(); 175 | 176 | this.dispatchCompiledEvent(); 177 | 178 | this.isCompiled = true; 179 | } 180 | 181 | protected dispatchCompiledEvent(): void { 182 | const event = new CustomEvent("compiled", { 183 | cancelable: true, 184 | bubbles: true, 185 | detail: this 186 | }); 187 | this.el.dispatchEvent(event); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /demo/resources/elements/monaco-editor.css: -------------------------------------------------------------------------------- 1 | .monaco-editor-host { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .ui.dimmer { 7 | position: relative !important; 8 | } 9 | -------------------------------------------------------------------------------- /demo/resources/elements/monaco-editor.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /demo/resources/elements/monaco-editor.ts: -------------------------------------------------------------------------------- 1 | import { bindingMode } from "aurelia-binding"; 2 | import { bindable, customElement } from "aurelia-framework"; 3 | import { PLATFORM } from "aurelia-pal"; 4 | import { TaskQueue } from "aurelia-task-queue"; 5 | import * as monaco from "monaco-editor"; 6 | 7 | @customElement("monaco-editor") 8 | export class MonacoEditor { 9 | @bindable({ defaultBindingMode: bindingMode.toView }) 10 | public options: monaco.editor.IEditorConstructionOptions; 11 | 12 | @bindable({ defaultBindingMode: bindingMode.fromView }) 13 | public editor: monaco.editor.IStandaloneCodeEditor; 14 | 15 | @bindable({ defaultBindingMode: bindingMode.twoWay }) 16 | public code: string; 17 | 18 | public editorHost: HTMLElement; 19 | private tq: TaskQueue; 20 | 21 | private isLoaded: boolean; 22 | private needsUpdateEditorValue: boolean; 23 | private isUpdatingCode: boolean; 24 | private isUpdatingEditorValue: boolean; 25 | 26 | constructor(tq: TaskQueue) { 27 | this.tq = tq; 28 | this.isLoaded = false; 29 | this.isUpdatingCode = false; 30 | this.isUpdatingEditorValue = false; 31 | } 32 | 33 | public attached(): void { 34 | PLATFORM.addEventListener("resize", this.handleResize); 35 | 36 | this.editor = monaco.editor.create(this.editorHost, this.options); 37 | this.isLoaded = true; 38 | if (this.needsUpdateEditorValue) { 39 | this.needsUpdateEditorValue = false; 40 | this.updateEditorValue(); 41 | } 42 | this.editor.onDidChangeModelContent((): void => { 43 | this.updateCode(); 44 | }); 45 | } 46 | 47 | public detached(): void { 48 | PLATFORM.removeEventListener("resize", this.handleResize); 49 | 50 | this.editor.dispose(); 51 | this.isLoaded = false; 52 | } 53 | 54 | protected codeChanged(): void { 55 | this.updateEditorValue(); 56 | } 57 | 58 | private updateCode(): void { 59 | if (!this.isUpdatingEditorValue) { 60 | this.isUpdatingCode = true; 61 | this.code = this.editor.getValue(); 62 | this.tq.queueMicroTask(() => { 63 | this.isUpdatingCode = false; 64 | }); 65 | } 66 | } 67 | 68 | private updateEditorValue(): void { 69 | if (!this.isUpdatingCode && /String/.test(Object.prototype.toString.call(this.code))) { 70 | if (this.isLoaded) { 71 | this.isUpdatingEditorValue = true; 72 | this.editor.setValue(this.code); 73 | this.tq.queueTask(() => { 74 | this.isUpdatingEditorValue = false; 75 | }); 76 | } else { 77 | this.needsUpdateEditorValue = true; 78 | } 79 | } 80 | } 81 | 82 | private handleResize = (): void => { 83 | if (!(this.editor === null || this.editor === undefined)) { 84 | this.editor.layout(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /demo/resources/index.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | import { FrameworkConfiguration } from "aurelia-framework"; 3 | import { PLATFORM } from "aurelia-pal"; 4 | 5 | export function configure(config: FrameworkConfiguration): void { 6 | config.globalResources([ 7 | PLATFORM.moduleName("resources/elements/monaco-editor"), 8 | PLATFORM.moduleName("resources/elements/dynamic-html") 9 | ]); 10 | } 11 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../configs/tsconfig-demo.json" 3 | } 4 | -------------------------------------------------------------------------------- /dist/amd/aurelia-dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | export declare function configure(config: any): void; 2 | export * from "./dynamic-html"; 3 | export * from "./interfaces"; 4 | -------------------------------------------------------------------------------- /dist/amd/aurelia-dynamic-html.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports", "aurelia-pal", "./dynamic-html"], function (require, exports, aurelia_pal_1, dynamic_html_1) { 2 | "use strict"; 3 | function __export(m) { 4 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 5 | } 6 | Object.defineProperty(exports, "__esModule", { value: true }); 7 | function configure(config) { 8 | config.globalResources(aurelia_pal_1.PLATFORM.moduleName("./dynamic-html")); 9 | } 10 | exports.configure = configure; 11 | __export(dynamic_html_1); 12 | }); 13 | -------------------------------------------------------------------------------- /dist/amd/dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { TaskQueue } from "aurelia-task-queue"; 4 | import { ViewCompiler, ViewSlot } from "aurelia-templating"; 5 | import { IBindingContext, IOverrideContext } from "./interfaces"; 6 | export declare class DynamicHtml { 7 | html: string | null; 8 | context: IBindingContext | null; 9 | renderErrors: boolean; 10 | slot: ViewSlot | null; 11 | bindingContext: IBindingContext | null; 12 | overrideContext: IOverrideContext | OverrideContext | null; 13 | isAttached: boolean; 14 | isBound: boolean; 15 | isCompiled: boolean; 16 | isCleanedUp: boolean; 17 | isInitialized: boolean; 18 | el: HTMLElement; 19 | protected tq: TaskQueue; 20 | protected container: Container; 21 | protected viewCompiler: ViewCompiler; 22 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler); 23 | bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void; 24 | unbind(): void; 25 | attached(): void; 26 | detached(): void; 27 | protected htmlChanged(newValue: string | null, __?: string): void; 28 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void; 29 | protected tryCompile(): void; 30 | protected cleanUp(): void; 31 | protected compile(message?: string): void; 32 | protected dispatchCompiledEvent(): void; 33 | } 34 | -------------------------------------------------------------------------------- /dist/amd/dynamic-html.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | 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; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | var __metadata = (this && this.__metadata) || function (k, v) { 8 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 9 | }; 10 | define(["require", "exports", "aurelia-binding", "aurelia-dependency-injection", "aurelia-logging", "aurelia-task-queue", "aurelia-templating"], function (require, exports, aurelia_binding_1, aurelia_dependency_injection_1, aurelia_logging_1, aurelia_task_queue_1, aurelia_templating_1) { 11 | "use strict"; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | const logger = aurelia_logging_1.getLogger("dynamic-html"); 14 | let DynamicHtml = class DynamicHtml { 15 | constructor(el, tq, container, viewCompiler) { 16 | this.el = el; 17 | this.tq = tq; 18 | this.container = container; 19 | this.viewCompiler = viewCompiler; 20 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 21 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 22 | this.isCleanedUp = true; 23 | } 24 | bind(bindingContext, overrideContext) { 25 | this.isBound = true; 26 | this.bindingContext = this.context || bindingContext.context || bindingContext; 27 | this.overrideContext = aurelia_binding_1.createOverrideContext(bindingContext, overrideContext); 28 | this.htmlChanged(this.html); 29 | } 30 | unbind() { 31 | this.isBound = false; 32 | this.bindingContext = null; 33 | this.overrideContext = null; 34 | } 35 | attached() { 36 | this.isAttached = true; 37 | this.isInitialized = true; 38 | this.slot = new aurelia_templating_1.ViewSlot(this.el.firstElementChild || this.el, true); 39 | this.tq.queueMicroTask(() => { 40 | this.tryCompile(); 41 | }); 42 | } 43 | detached() { 44 | this.isAttached = false; 45 | if (this.isCompiled) { 46 | this.cleanUp(); 47 | } 48 | this.slot = null; 49 | } 50 | htmlChanged(newValue, __) { 51 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 52 | this.cleanUp(); 53 | } 54 | else if (this.isBound || this.isInitialized) { 55 | this.tq.queueMicroTask(() => { 56 | this.tryCompile(); 57 | }); 58 | } 59 | } 60 | contextChanged(newValue, _) { 61 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 62 | this.bindingContext = this.overrideContext.bindingContext; 63 | } 64 | else { 65 | this.bindingContext = newValue; 66 | } 67 | if (this.isBound || this.isInitialized) { 68 | this.tq.queueMicroTask(() => { 69 | this.tryCompile(); 70 | }); 71 | } 72 | } 73 | tryCompile() { 74 | if (this.isAttached) { 75 | if (!this.isCleanedUp) { 76 | this.cleanUp(); 77 | } 78 | try { 79 | this.tq.queueMicroTask(() => { 80 | this.compile(); 81 | }); 82 | } 83 | catch (e) { 84 | logger.warn(e.message); 85 | if (this.renderErrors) { 86 | this.tq.queueMicroTask(() => { 87 | this.compile(``); 88 | }); 89 | } 90 | } 91 | } 92 | } 93 | cleanUp() { 94 | this.isCompiled = false; 95 | logger.debug("Cleaning up"); 96 | const slot = this.slot; 97 | try { 98 | slot.detached(); 99 | } 100 | catch (e) { 101 | logger.error(e.message); 102 | } 103 | try { 104 | slot.unbind(); 105 | } 106 | catch (e) { 107 | logger.error(e.message); 108 | } 109 | try { 110 | slot.removeAll(); 111 | } 112 | catch (e) { 113 | logger.error(e.message); 114 | } 115 | this.isCleanedUp = true; 116 | } 117 | compile(message) { 118 | if (!this.isCleanedUp) { 119 | this.cleanUp(); 120 | } 121 | if ((this.html === null || this.html === undefined) && message === undefined) { 122 | logger.debug("Skipping compilation because no html value is set"); 123 | return; 124 | } 125 | this.isCleanedUp = false; 126 | const template = ``; 127 | logger.debug("Compiling", template, this.bindingContext); 128 | const viewResources = this.container.get(aurelia_templating_1.ViewResources); 129 | const childContainer = this.container.createChild(); 130 | const factory = this.viewCompiler.compile(template, viewResources); 131 | const view = factory.create(childContainer); 132 | const slot = this.slot; 133 | slot.add(view); 134 | slot.bind(this.bindingContext, this.overrideContext); 135 | slot.attached(); 136 | this.dispatchCompiledEvent(); 137 | this.isCompiled = true; 138 | } 139 | dispatchCompiledEvent() { 140 | const event = new CustomEvent("compiled", { 141 | cancelable: true, 142 | bubbles: true, 143 | detail: this 144 | }); 145 | this.el.dispatchEvent(event); 146 | } 147 | }; 148 | __decorate([ 149 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 150 | __metadata("design:type", Object) 151 | ], DynamicHtml.prototype, "html", void 0); 152 | __decorate([ 153 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 154 | __metadata("design:type", Object) 155 | ], DynamicHtml.prototype, "context", void 0); 156 | __decorate([ 157 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 158 | __metadata("design:type", Boolean) 159 | ], DynamicHtml.prototype, "renderErrors", void 0); 160 | DynamicHtml = __decorate([ 161 | aurelia_templating_1.customElement("dynamic-html"), 162 | aurelia_templating_1.inlineView(""), 163 | __metadata("design:paramtypes", [Element, aurelia_task_queue_1.TaskQueue, aurelia_dependency_injection_1.Container, aurelia_templating_1.ViewCompiler]) 164 | ], DynamicHtml); 165 | exports.DynamicHtml = DynamicHtml; 166 | }); 167 | -------------------------------------------------------------------------------- /dist/amd/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | export interface IOverrideContext { 5 | parentOverrideContext: IOverrideContext; 6 | bindingContext: IBindingContext; 7 | } 8 | -------------------------------------------------------------------------------- /dist/amd/interfaces.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports"], function (require, exports) { 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | }); 5 | -------------------------------------------------------------------------------- /dist/commonjs/aurelia-dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | export declare function configure(config: any): void; 2 | export * from "./dynamic-html"; 3 | export * from "./interfaces"; 4 | -------------------------------------------------------------------------------- /dist/commonjs/aurelia-dynamic-html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const aurelia_pal_1 = require("aurelia-pal"); 7 | function configure(config) { 8 | config.globalResources(aurelia_pal_1.PLATFORM.moduleName("./dynamic-html")); 9 | } 10 | exports.configure = configure; 11 | __export(require("./dynamic-html")); 12 | -------------------------------------------------------------------------------- /dist/commonjs/dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { TaskQueue } from "aurelia-task-queue"; 4 | import { ViewCompiler, ViewSlot } from "aurelia-templating"; 5 | import { IBindingContext, IOverrideContext } from "./interfaces"; 6 | export declare class DynamicHtml { 7 | html: string | null; 8 | context: IBindingContext | null; 9 | renderErrors: boolean; 10 | slot: ViewSlot | null; 11 | bindingContext: IBindingContext | null; 12 | overrideContext: IOverrideContext | OverrideContext | null; 13 | isAttached: boolean; 14 | isBound: boolean; 15 | isCompiled: boolean; 16 | isCleanedUp: boolean; 17 | isInitialized: boolean; 18 | el: HTMLElement; 19 | protected tq: TaskQueue; 20 | protected container: Container; 21 | protected viewCompiler: ViewCompiler; 22 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler); 23 | bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void; 24 | unbind(): void; 25 | attached(): void; 26 | detached(): void; 27 | protected htmlChanged(newValue: string | null, __?: string): void; 28 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void; 29 | protected tryCompile(): void; 30 | protected cleanUp(): void; 31 | protected compile(message?: string): void; 32 | protected dispatchCompiledEvent(): void; 33 | } 34 | -------------------------------------------------------------------------------- /dist/commonjs/dynamic-html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | 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; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __metadata = (this && this.__metadata) || function (k, v) { 9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const aurelia_binding_1 = require("aurelia-binding"); 13 | const aurelia_dependency_injection_1 = require("aurelia-dependency-injection"); 14 | const aurelia_logging_1 = require("aurelia-logging"); 15 | const aurelia_task_queue_1 = require("aurelia-task-queue"); 16 | const aurelia_templating_1 = require("aurelia-templating"); 17 | const logger = aurelia_logging_1.getLogger("dynamic-html"); 18 | let DynamicHtml = class DynamicHtml { 19 | constructor(el, tq, container, viewCompiler) { 20 | this.el = el; 21 | this.tq = tq; 22 | this.container = container; 23 | this.viewCompiler = viewCompiler; 24 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 25 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 26 | this.isCleanedUp = true; 27 | } 28 | bind(bindingContext, overrideContext) { 29 | this.isBound = true; 30 | this.bindingContext = this.context || bindingContext.context || bindingContext; 31 | this.overrideContext = aurelia_binding_1.createOverrideContext(bindingContext, overrideContext); 32 | this.htmlChanged(this.html); 33 | } 34 | unbind() { 35 | this.isBound = false; 36 | this.bindingContext = null; 37 | this.overrideContext = null; 38 | } 39 | attached() { 40 | this.isAttached = true; 41 | this.isInitialized = true; 42 | this.slot = new aurelia_templating_1.ViewSlot(this.el.firstElementChild || this.el, true); 43 | this.tq.queueMicroTask(() => { 44 | this.tryCompile(); 45 | }); 46 | } 47 | detached() { 48 | this.isAttached = false; 49 | if (this.isCompiled) { 50 | this.cleanUp(); 51 | } 52 | this.slot = null; 53 | } 54 | htmlChanged(newValue, __) { 55 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 56 | this.cleanUp(); 57 | } 58 | else if (this.isBound || this.isInitialized) { 59 | this.tq.queueMicroTask(() => { 60 | this.tryCompile(); 61 | }); 62 | } 63 | } 64 | contextChanged(newValue, _) { 65 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 66 | this.bindingContext = this.overrideContext.bindingContext; 67 | } 68 | else { 69 | this.bindingContext = newValue; 70 | } 71 | if (this.isBound || this.isInitialized) { 72 | this.tq.queueMicroTask(() => { 73 | this.tryCompile(); 74 | }); 75 | } 76 | } 77 | tryCompile() { 78 | if (this.isAttached) { 79 | if (!this.isCleanedUp) { 80 | this.cleanUp(); 81 | } 82 | try { 83 | this.tq.queueMicroTask(() => { 84 | this.compile(); 85 | }); 86 | } 87 | catch (e) { 88 | logger.warn(e.message); 89 | if (this.renderErrors) { 90 | this.tq.queueMicroTask(() => { 91 | this.compile(``); 92 | }); 93 | } 94 | } 95 | } 96 | } 97 | cleanUp() { 98 | this.isCompiled = false; 99 | logger.debug("Cleaning up"); 100 | const slot = this.slot; 101 | try { 102 | slot.detached(); 103 | } 104 | catch (e) { 105 | logger.error(e.message); 106 | } 107 | try { 108 | slot.unbind(); 109 | } 110 | catch (e) { 111 | logger.error(e.message); 112 | } 113 | try { 114 | slot.removeAll(); 115 | } 116 | catch (e) { 117 | logger.error(e.message); 118 | } 119 | this.isCleanedUp = true; 120 | } 121 | compile(message) { 122 | if (!this.isCleanedUp) { 123 | this.cleanUp(); 124 | } 125 | if ((this.html === null || this.html === undefined) && message === undefined) { 126 | logger.debug("Skipping compilation because no html value is set"); 127 | return; 128 | } 129 | this.isCleanedUp = false; 130 | const template = ``; 131 | logger.debug("Compiling", template, this.bindingContext); 132 | const viewResources = this.container.get(aurelia_templating_1.ViewResources); 133 | const childContainer = this.container.createChild(); 134 | const factory = this.viewCompiler.compile(template, viewResources); 135 | const view = factory.create(childContainer); 136 | const slot = this.slot; 137 | slot.add(view); 138 | slot.bind(this.bindingContext, this.overrideContext); 139 | slot.attached(); 140 | this.dispatchCompiledEvent(); 141 | this.isCompiled = true; 142 | } 143 | dispatchCompiledEvent() { 144 | const event = new CustomEvent("compiled", { 145 | cancelable: true, 146 | bubbles: true, 147 | detail: this 148 | }); 149 | this.el.dispatchEvent(event); 150 | } 151 | }; 152 | __decorate([ 153 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 154 | __metadata("design:type", Object) 155 | ], DynamicHtml.prototype, "html", void 0); 156 | __decorate([ 157 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 158 | __metadata("design:type", Object) 159 | ], DynamicHtml.prototype, "context", void 0); 160 | __decorate([ 161 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 162 | __metadata("design:type", Boolean) 163 | ], DynamicHtml.prototype, "renderErrors", void 0); 164 | DynamicHtml = __decorate([ 165 | aurelia_templating_1.customElement("dynamic-html"), 166 | aurelia_templating_1.inlineView(""), 167 | __metadata("design:paramtypes", [Element, aurelia_task_queue_1.TaskQueue, aurelia_dependency_injection_1.Container, aurelia_templating_1.ViewCompiler]) 168 | ], DynamicHtml); 169 | exports.DynamicHtml = DynamicHtml; 170 | -------------------------------------------------------------------------------- /dist/commonjs/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | export interface IOverrideContext { 5 | parentOverrideContext: IOverrideContext; 6 | bindingContext: IBindingContext; 7 | } 8 | -------------------------------------------------------------------------------- /dist/commonjs/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/es2015/aurelia-dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | export declare function configure(config: any): void; 2 | export * from "./dynamic-html"; 3 | export * from "./interfaces"; 4 | -------------------------------------------------------------------------------- /dist/es2015/aurelia-dynamic-html.js: -------------------------------------------------------------------------------- 1 | import { PLATFORM } from "aurelia-pal"; 2 | export function configure(config) { 3 | config.globalResources(PLATFORM.moduleName("./dynamic-html")); 4 | } 5 | export * from "./dynamic-html"; 6 | -------------------------------------------------------------------------------- /dist/es2015/dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { TaskQueue } from "aurelia-task-queue"; 4 | import { ViewCompiler, ViewSlot } from "aurelia-templating"; 5 | import { IBindingContext, IOverrideContext } from "./interfaces"; 6 | export declare class DynamicHtml { 7 | html: string | null; 8 | context: IBindingContext | null; 9 | renderErrors: boolean; 10 | slot: ViewSlot | null; 11 | bindingContext: IBindingContext | null; 12 | overrideContext: IOverrideContext | OverrideContext | null; 13 | isAttached: boolean; 14 | isBound: boolean; 15 | isCompiled: boolean; 16 | isCleanedUp: boolean; 17 | isInitialized: boolean; 18 | el: HTMLElement; 19 | protected tq: TaskQueue; 20 | protected container: Container; 21 | protected viewCompiler: ViewCompiler; 22 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler); 23 | bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void; 24 | unbind(): void; 25 | attached(): void; 26 | detached(): void; 27 | protected htmlChanged(newValue: string | null, __?: string): void; 28 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void; 29 | protected tryCompile(): void; 30 | protected cleanUp(): void; 31 | protected compile(message?: string): void; 32 | protected dispatchCompiledEvent(): void; 33 | } 34 | -------------------------------------------------------------------------------- /dist/es2015/dynamic-html.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | 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; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | var __metadata = (this && this.__metadata) || function (k, v) { 8 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 9 | }; 10 | import { bindingMode, createOverrideContext } from "aurelia-binding"; 11 | import { Container } from "aurelia-dependency-injection"; 12 | import { getLogger } from "aurelia-logging"; 13 | import { TaskQueue } from "aurelia-task-queue"; 14 | import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating"; 15 | const logger = getLogger("dynamic-html"); 16 | let DynamicHtml = class DynamicHtml { 17 | constructor(el, tq, container, viewCompiler) { 18 | this.el = el; 19 | this.tq = tq; 20 | this.container = container; 21 | this.viewCompiler = viewCompiler; 22 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 23 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 24 | this.isCleanedUp = true; 25 | } 26 | bind(bindingContext, overrideContext) { 27 | this.isBound = true; 28 | this.bindingContext = this.context || bindingContext.context || bindingContext; 29 | this.overrideContext = createOverrideContext(bindingContext, overrideContext); 30 | this.htmlChanged(this.html); 31 | } 32 | unbind() { 33 | this.isBound = false; 34 | this.bindingContext = null; 35 | this.overrideContext = null; 36 | } 37 | attached() { 38 | this.isAttached = true; 39 | this.isInitialized = true; 40 | this.slot = new ViewSlot(this.el.firstElementChild || this.el, true); 41 | this.tq.queueMicroTask(() => { 42 | this.tryCompile(); 43 | }); 44 | } 45 | detached() { 46 | this.isAttached = false; 47 | if (this.isCompiled) { 48 | this.cleanUp(); 49 | } 50 | this.slot = null; 51 | } 52 | htmlChanged(newValue, __) { 53 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 54 | this.cleanUp(); 55 | } 56 | else if (this.isBound || this.isInitialized) { 57 | this.tq.queueMicroTask(() => { 58 | this.tryCompile(); 59 | }); 60 | } 61 | } 62 | contextChanged(newValue, _) { 63 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 64 | this.bindingContext = this.overrideContext.bindingContext; 65 | } 66 | else { 67 | this.bindingContext = newValue; 68 | } 69 | if (this.isBound || this.isInitialized) { 70 | this.tq.queueMicroTask(() => { 71 | this.tryCompile(); 72 | }); 73 | } 74 | } 75 | tryCompile() { 76 | if (this.isAttached) { 77 | if (!this.isCleanedUp) { 78 | this.cleanUp(); 79 | } 80 | try { 81 | this.tq.queueMicroTask(() => { 82 | this.compile(); 83 | }); 84 | } 85 | catch (e) { 86 | logger.warn(e.message); 87 | if (this.renderErrors) { 88 | this.tq.queueMicroTask(() => { 89 | this.compile(``); 90 | }); 91 | } 92 | } 93 | } 94 | } 95 | cleanUp() { 96 | this.isCompiled = false; 97 | logger.debug("Cleaning up"); 98 | const slot = this.slot; 99 | try { 100 | slot.detached(); 101 | } 102 | catch (e) { 103 | logger.error(e.message); 104 | } 105 | try { 106 | slot.unbind(); 107 | } 108 | catch (e) { 109 | logger.error(e.message); 110 | } 111 | try { 112 | slot.removeAll(); 113 | } 114 | catch (e) { 115 | logger.error(e.message); 116 | } 117 | this.isCleanedUp = true; 118 | } 119 | compile(message) { 120 | if (!this.isCleanedUp) { 121 | this.cleanUp(); 122 | } 123 | if ((this.html === null || this.html === undefined) && message === undefined) { 124 | logger.debug("Skipping compilation because no html value is set"); 125 | return; 126 | } 127 | this.isCleanedUp = false; 128 | const template = ``; 129 | logger.debug("Compiling", template, this.bindingContext); 130 | const viewResources = this.container.get(ViewResources); 131 | const childContainer = this.container.createChild(); 132 | const factory = this.viewCompiler.compile(template, viewResources); 133 | const view = factory.create(childContainer); 134 | const slot = this.slot; 135 | slot.add(view); 136 | slot.bind(this.bindingContext, this.overrideContext); 137 | slot.attached(); 138 | this.dispatchCompiledEvent(); 139 | this.isCompiled = true; 140 | } 141 | dispatchCompiledEvent() { 142 | const event = new CustomEvent("compiled", { 143 | cancelable: true, 144 | bubbles: true, 145 | detail: this 146 | }); 147 | this.el.dispatchEvent(event); 148 | } 149 | }; 150 | __decorate([ 151 | bindable({ defaultBindingMode: bindingMode.toView }), 152 | __metadata("design:type", Object) 153 | ], DynamicHtml.prototype, "html", void 0); 154 | __decorate([ 155 | bindable({ defaultBindingMode: bindingMode.toView }), 156 | __metadata("design:type", Object) 157 | ], DynamicHtml.prototype, "context", void 0); 158 | __decorate([ 159 | bindable({ defaultBindingMode: bindingMode.toView }), 160 | __metadata("design:type", Boolean) 161 | ], DynamicHtml.prototype, "renderErrors", void 0); 162 | DynamicHtml = __decorate([ 163 | customElement("dynamic-html"), 164 | inlineView(""), 165 | __metadata("design:paramtypes", [Element, TaskQueue, Container, ViewCompiler]) 166 | ], DynamicHtml); 167 | export { DynamicHtml }; 168 | -------------------------------------------------------------------------------- /dist/es2015/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | export interface IOverrideContext { 5 | parentOverrideContext: IOverrideContext; 6 | bindingContext: IBindingContext; 7 | } 8 | -------------------------------------------------------------------------------- /dist/es2015/interfaces.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia-contrib/aurelia-dynamic-html/e05b31c3d3c25c92b3afb03dae77b9a9983bd07f/dist/es2015/interfaces.js -------------------------------------------------------------------------------- /dist/es2017/aurelia-dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | export declare function configure(config: any): void; 2 | export * from "./dynamic-html"; 3 | export * from "./interfaces"; 4 | -------------------------------------------------------------------------------- /dist/es2017/aurelia-dynamic-html.js: -------------------------------------------------------------------------------- 1 | import { PLATFORM } from "aurelia-pal"; 2 | export function configure(config) { 3 | config.globalResources(PLATFORM.moduleName("./dynamic-html")); 4 | } 5 | export * from "./dynamic-html"; 6 | -------------------------------------------------------------------------------- /dist/es2017/dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { TaskQueue } from "aurelia-task-queue"; 4 | import { ViewCompiler, ViewSlot } from "aurelia-templating"; 5 | import { IBindingContext, IOverrideContext } from "./interfaces"; 6 | export declare class DynamicHtml { 7 | html: string | null; 8 | context: IBindingContext | null; 9 | renderErrors: boolean; 10 | slot: ViewSlot | null; 11 | bindingContext: IBindingContext | null; 12 | overrideContext: IOverrideContext | OverrideContext | null; 13 | isAttached: boolean; 14 | isBound: boolean; 15 | isCompiled: boolean; 16 | isCleanedUp: boolean; 17 | isInitialized: boolean; 18 | el: HTMLElement; 19 | protected tq: TaskQueue; 20 | protected container: Container; 21 | protected viewCompiler: ViewCompiler; 22 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler); 23 | bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void; 24 | unbind(): void; 25 | attached(): void; 26 | detached(): void; 27 | protected htmlChanged(newValue: string | null, __?: string): void; 28 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void; 29 | protected tryCompile(): void; 30 | protected cleanUp(): void; 31 | protected compile(message?: string): void; 32 | protected dispatchCompiledEvent(): void; 33 | } 34 | -------------------------------------------------------------------------------- /dist/es2017/dynamic-html.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | 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; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | var __metadata = (this && this.__metadata) || function (k, v) { 8 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 9 | }; 10 | import { bindingMode, createOverrideContext } from "aurelia-binding"; 11 | import { Container } from "aurelia-dependency-injection"; 12 | import { getLogger } from "aurelia-logging"; 13 | import { TaskQueue } from "aurelia-task-queue"; 14 | import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating"; 15 | const logger = getLogger("dynamic-html"); 16 | let DynamicHtml = class DynamicHtml { 17 | constructor(el, tq, container, viewCompiler) { 18 | this.el = el; 19 | this.tq = tq; 20 | this.container = container; 21 | this.viewCompiler = viewCompiler; 22 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 23 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 24 | this.isCleanedUp = true; 25 | } 26 | bind(bindingContext, overrideContext) { 27 | this.isBound = true; 28 | this.bindingContext = this.context || bindingContext.context || bindingContext; 29 | this.overrideContext = createOverrideContext(bindingContext, overrideContext); 30 | this.htmlChanged(this.html); 31 | } 32 | unbind() { 33 | this.isBound = false; 34 | this.bindingContext = null; 35 | this.overrideContext = null; 36 | } 37 | attached() { 38 | this.isAttached = true; 39 | this.isInitialized = true; 40 | this.slot = new ViewSlot(this.el.firstElementChild || this.el, true); 41 | this.tq.queueMicroTask(() => { 42 | this.tryCompile(); 43 | }); 44 | } 45 | detached() { 46 | this.isAttached = false; 47 | if (this.isCompiled) { 48 | this.cleanUp(); 49 | } 50 | this.slot = null; 51 | } 52 | htmlChanged(newValue, __) { 53 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 54 | this.cleanUp(); 55 | } 56 | else if (this.isBound || this.isInitialized) { 57 | this.tq.queueMicroTask(() => { 58 | this.tryCompile(); 59 | }); 60 | } 61 | } 62 | contextChanged(newValue, _) { 63 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 64 | this.bindingContext = this.overrideContext.bindingContext; 65 | } 66 | else { 67 | this.bindingContext = newValue; 68 | } 69 | if (this.isBound || this.isInitialized) { 70 | this.tq.queueMicroTask(() => { 71 | this.tryCompile(); 72 | }); 73 | } 74 | } 75 | tryCompile() { 76 | if (this.isAttached) { 77 | if (!this.isCleanedUp) { 78 | this.cleanUp(); 79 | } 80 | try { 81 | this.tq.queueMicroTask(() => { 82 | this.compile(); 83 | }); 84 | } 85 | catch (e) { 86 | logger.warn(e.message); 87 | if (this.renderErrors) { 88 | this.tq.queueMicroTask(() => { 89 | this.compile(``); 90 | }); 91 | } 92 | } 93 | } 94 | } 95 | cleanUp() { 96 | this.isCompiled = false; 97 | logger.debug("Cleaning up"); 98 | const slot = this.slot; 99 | try { 100 | slot.detached(); 101 | } 102 | catch (e) { 103 | logger.error(e.message); 104 | } 105 | try { 106 | slot.unbind(); 107 | } 108 | catch (e) { 109 | logger.error(e.message); 110 | } 111 | try { 112 | slot.removeAll(); 113 | } 114 | catch (e) { 115 | logger.error(e.message); 116 | } 117 | this.isCleanedUp = true; 118 | } 119 | compile(message) { 120 | if (!this.isCleanedUp) { 121 | this.cleanUp(); 122 | } 123 | if ((this.html === null || this.html === undefined) && message === undefined) { 124 | logger.debug("Skipping compilation because no html value is set"); 125 | return; 126 | } 127 | this.isCleanedUp = false; 128 | const template = ``; 129 | logger.debug("Compiling", template, this.bindingContext); 130 | const viewResources = this.container.get(ViewResources); 131 | const childContainer = this.container.createChild(); 132 | const factory = this.viewCompiler.compile(template, viewResources); 133 | const view = factory.create(childContainer); 134 | const slot = this.slot; 135 | slot.add(view); 136 | slot.bind(this.bindingContext, this.overrideContext); 137 | slot.attached(); 138 | this.dispatchCompiledEvent(); 139 | this.isCompiled = true; 140 | } 141 | dispatchCompiledEvent() { 142 | const event = new CustomEvent("compiled", { 143 | cancelable: true, 144 | bubbles: true, 145 | detail: this 146 | }); 147 | this.el.dispatchEvent(event); 148 | } 149 | }; 150 | __decorate([ 151 | bindable({ defaultBindingMode: bindingMode.toView }), 152 | __metadata("design:type", Object) 153 | ], DynamicHtml.prototype, "html", void 0); 154 | __decorate([ 155 | bindable({ defaultBindingMode: bindingMode.toView }), 156 | __metadata("design:type", Object) 157 | ], DynamicHtml.prototype, "context", void 0); 158 | __decorate([ 159 | bindable({ defaultBindingMode: bindingMode.toView }), 160 | __metadata("design:type", Boolean) 161 | ], DynamicHtml.prototype, "renderErrors", void 0); 162 | DynamicHtml = __decorate([ 163 | customElement("dynamic-html"), 164 | inlineView(""), 165 | __metadata("design:paramtypes", [Element, TaskQueue, Container, ViewCompiler]) 166 | ], DynamicHtml); 167 | export { DynamicHtml }; 168 | -------------------------------------------------------------------------------- /dist/es2017/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | export interface IOverrideContext { 5 | parentOverrideContext: IOverrideContext; 6 | bindingContext: IBindingContext; 7 | } 8 | -------------------------------------------------------------------------------- /dist/es2017/interfaces.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia-contrib/aurelia-dynamic-html/e05b31c3d3c25c92b3afb03dae77b9a9983bd07f/dist/es2017/interfaces.js -------------------------------------------------------------------------------- /dist/native-modules/aurelia-dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | export declare function configure(config: any): void; 2 | export * from "./dynamic-html"; 3 | export * from "./interfaces"; 4 | -------------------------------------------------------------------------------- /dist/native-modules/aurelia-dynamic-html.js: -------------------------------------------------------------------------------- 1 | import { PLATFORM } from "aurelia-pal"; 2 | export function configure(config) { 3 | config.globalResources(PLATFORM.moduleName("./dynamic-html")); 4 | } 5 | export * from "./dynamic-html"; 6 | -------------------------------------------------------------------------------- /dist/native-modules/dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { TaskQueue } from "aurelia-task-queue"; 4 | import { ViewCompiler, ViewSlot } from "aurelia-templating"; 5 | import { IBindingContext, IOverrideContext } from "./interfaces"; 6 | export declare class DynamicHtml { 7 | html: string | null; 8 | context: IBindingContext | null; 9 | renderErrors: boolean; 10 | slot: ViewSlot | null; 11 | bindingContext: IBindingContext | null; 12 | overrideContext: IOverrideContext | OverrideContext | null; 13 | isAttached: boolean; 14 | isBound: boolean; 15 | isCompiled: boolean; 16 | isCleanedUp: boolean; 17 | isInitialized: boolean; 18 | el: HTMLElement; 19 | protected tq: TaskQueue; 20 | protected container: Container; 21 | protected viewCompiler: ViewCompiler; 22 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler); 23 | bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void; 24 | unbind(): void; 25 | attached(): void; 26 | detached(): void; 27 | protected htmlChanged(newValue: string | null, __?: string): void; 28 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void; 29 | protected tryCompile(): void; 30 | protected cleanUp(): void; 31 | protected compile(message?: string): void; 32 | protected dispatchCompiledEvent(): void; 33 | } 34 | -------------------------------------------------------------------------------- /dist/native-modules/dynamic-html.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | 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; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | var __metadata = (this && this.__metadata) || function (k, v) { 8 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 9 | }; 10 | import { bindingMode, createOverrideContext } from "aurelia-binding"; 11 | import { Container } from "aurelia-dependency-injection"; 12 | import { getLogger } from "aurelia-logging"; 13 | import { TaskQueue } from "aurelia-task-queue"; 14 | import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating"; 15 | const logger = getLogger("dynamic-html"); 16 | let DynamicHtml = class DynamicHtml { 17 | constructor(el, tq, container, viewCompiler) { 18 | this.el = el; 19 | this.tq = tq; 20 | this.container = container; 21 | this.viewCompiler = viewCompiler; 22 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 23 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 24 | this.isCleanedUp = true; 25 | } 26 | bind(bindingContext, overrideContext) { 27 | this.isBound = true; 28 | this.bindingContext = this.context || bindingContext.context || bindingContext; 29 | this.overrideContext = createOverrideContext(bindingContext, overrideContext); 30 | this.htmlChanged(this.html); 31 | } 32 | unbind() { 33 | this.isBound = false; 34 | this.bindingContext = null; 35 | this.overrideContext = null; 36 | } 37 | attached() { 38 | this.isAttached = true; 39 | this.isInitialized = true; 40 | this.slot = new ViewSlot(this.el.firstElementChild || this.el, true); 41 | this.tq.queueMicroTask(() => { 42 | this.tryCompile(); 43 | }); 44 | } 45 | detached() { 46 | this.isAttached = false; 47 | if (this.isCompiled) { 48 | this.cleanUp(); 49 | } 50 | this.slot = null; 51 | } 52 | htmlChanged(newValue, __) { 53 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 54 | this.cleanUp(); 55 | } 56 | else if (this.isBound || this.isInitialized) { 57 | this.tq.queueMicroTask(() => { 58 | this.tryCompile(); 59 | }); 60 | } 61 | } 62 | contextChanged(newValue, _) { 63 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 64 | this.bindingContext = this.overrideContext.bindingContext; 65 | } 66 | else { 67 | this.bindingContext = newValue; 68 | } 69 | if (this.isBound || this.isInitialized) { 70 | this.tq.queueMicroTask(() => { 71 | this.tryCompile(); 72 | }); 73 | } 74 | } 75 | tryCompile() { 76 | if (this.isAttached) { 77 | if (!this.isCleanedUp) { 78 | this.cleanUp(); 79 | } 80 | try { 81 | this.tq.queueMicroTask(() => { 82 | this.compile(); 83 | }); 84 | } 85 | catch (e) { 86 | logger.warn(e.message); 87 | if (this.renderErrors) { 88 | this.tq.queueMicroTask(() => { 89 | this.compile(``); 90 | }); 91 | } 92 | } 93 | } 94 | } 95 | cleanUp() { 96 | this.isCompiled = false; 97 | logger.debug("Cleaning up"); 98 | const slot = this.slot; 99 | try { 100 | slot.detached(); 101 | } 102 | catch (e) { 103 | logger.error(e.message); 104 | } 105 | try { 106 | slot.unbind(); 107 | } 108 | catch (e) { 109 | logger.error(e.message); 110 | } 111 | try { 112 | slot.removeAll(); 113 | } 114 | catch (e) { 115 | logger.error(e.message); 116 | } 117 | this.isCleanedUp = true; 118 | } 119 | compile(message) { 120 | if (!this.isCleanedUp) { 121 | this.cleanUp(); 122 | } 123 | if ((this.html === null || this.html === undefined) && message === undefined) { 124 | logger.debug("Skipping compilation because no html value is set"); 125 | return; 126 | } 127 | this.isCleanedUp = false; 128 | const template = ``; 129 | logger.debug("Compiling", template, this.bindingContext); 130 | const viewResources = this.container.get(ViewResources); 131 | const childContainer = this.container.createChild(); 132 | const factory = this.viewCompiler.compile(template, viewResources); 133 | const view = factory.create(childContainer); 134 | const slot = this.slot; 135 | slot.add(view); 136 | slot.bind(this.bindingContext, this.overrideContext); 137 | slot.attached(); 138 | this.dispatchCompiledEvent(); 139 | this.isCompiled = true; 140 | } 141 | dispatchCompiledEvent() { 142 | const event = new CustomEvent("compiled", { 143 | cancelable: true, 144 | bubbles: true, 145 | detail: this 146 | }); 147 | this.el.dispatchEvent(event); 148 | } 149 | }; 150 | __decorate([ 151 | bindable({ defaultBindingMode: bindingMode.toView }), 152 | __metadata("design:type", Object) 153 | ], DynamicHtml.prototype, "html", void 0); 154 | __decorate([ 155 | bindable({ defaultBindingMode: bindingMode.toView }), 156 | __metadata("design:type", Object) 157 | ], DynamicHtml.prototype, "context", void 0); 158 | __decorate([ 159 | bindable({ defaultBindingMode: bindingMode.toView }), 160 | __metadata("design:type", Boolean) 161 | ], DynamicHtml.prototype, "renderErrors", void 0); 162 | DynamicHtml = __decorate([ 163 | customElement("dynamic-html"), 164 | inlineView(""), 165 | __metadata("design:paramtypes", [Element, TaskQueue, Container, ViewCompiler]) 166 | ], DynamicHtml); 167 | export { DynamicHtml }; 168 | -------------------------------------------------------------------------------- /dist/native-modules/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | export interface IOverrideContext { 5 | parentOverrideContext: IOverrideContext; 6 | bindingContext: IBindingContext; 7 | } 8 | -------------------------------------------------------------------------------- /dist/native-modules/interfaces.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelia-contrib/aurelia-dynamic-html/e05b31c3d3c25c92b3afb03dae77b9a9983bd07f/dist/native-modules/interfaces.js -------------------------------------------------------------------------------- /dist/system/aurelia-dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | export declare function configure(config: any): void; 2 | export * from "./dynamic-html"; 3 | export * from "./interfaces"; 4 | -------------------------------------------------------------------------------- /dist/system/aurelia-dynamic-html.js: -------------------------------------------------------------------------------- 1 | System.register(["aurelia-pal", "./dynamic-html"], function (exports_1, context_1) { 2 | "use strict"; 3 | var __moduleName = context_1 && context_1.id; 4 | function configure(config) { 5 | config.globalResources(aurelia_pal_1.PLATFORM.moduleName("./dynamic-html")); 6 | } 7 | exports_1("configure", configure); 8 | var aurelia_pal_1; 9 | var exportedNames_1 = { 10 | "configure": true 11 | }; 12 | function exportStar_1(m) { 13 | var exports = {}; 14 | for (var n in m) { 15 | if (n !== "default" && !exportedNames_1.hasOwnProperty(n)) exports[n] = m[n]; 16 | } 17 | exports_1(exports); 18 | } 19 | return { 20 | setters: [ 21 | function (aurelia_pal_1_1) { 22 | aurelia_pal_1 = aurelia_pal_1_1; 23 | }, 24 | function (dynamic_html_1_1) { 25 | exportStar_1(dynamic_html_1_1); 26 | } 27 | ], 28 | execute: function () { 29 | } 30 | }; 31 | }); 32 | -------------------------------------------------------------------------------- /dist/system/dynamic-html.d.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { TaskQueue } from "aurelia-task-queue"; 4 | import { ViewCompiler, ViewSlot } from "aurelia-templating"; 5 | import { IBindingContext, IOverrideContext } from "./interfaces"; 6 | export declare class DynamicHtml { 7 | html: string | null; 8 | context: IBindingContext | null; 9 | renderErrors: boolean; 10 | slot: ViewSlot | null; 11 | bindingContext: IBindingContext | null; 12 | overrideContext: IOverrideContext | OverrideContext | null; 13 | isAttached: boolean; 14 | isBound: boolean; 15 | isCompiled: boolean; 16 | isCleanedUp: boolean; 17 | isInitialized: boolean; 18 | el: HTMLElement; 19 | protected tq: TaskQueue; 20 | protected container: Container; 21 | protected viewCompiler: ViewCompiler; 22 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler); 23 | bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void; 24 | unbind(): void; 25 | attached(): void; 26 | detached(): void; 27 | protected htmlChanged(newValue: string | null, __?: string): void; 28 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void; 29 | protected tryCompile(): void; 30 | protected cleanUp(): void; 31 | protected compile(message?: string): void; 32 | protected dispatchCompiledEvent(): void; 33 | } 34 | -------------------------------------------------------------------------------- /dist/system/dynamic-html.js: -------------------------------------------------------------------------------- 1 | System.register(["aurelia-binding", "aurelia-dependency-injection", "aurelia-logging", "aurelia-task-queue", "aurelia-templating"], function (exports_1, context_1) { 2 | "use strict"; 3 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 4 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 5 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 6 | 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; 7 | return c > 3 && r && Object.defineProperty(target, key, r), r; 8 | }; 9 | var __metadata = (this && this.__metadata) || function (k, v) { 10 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); 11 | }; 12 | var __moduleName = context_1 && context_1.id; 13 | var aurelia_binding_1, aurelia_dependency_injection_1, aurelia_logging_1, aurelia_task_queue_1, aurelia_templating_1, logger, DynamicHtml; 14 | return { 15 | setters: [ 16 | function (aurelia_binding_1_1) { 17 | aurelia_binding_1 = aurelia_binding_1_1; 18 | }, 19 | function (aurelia_dependency_injection_1_1) { 20 | aurelia_dependency_injection_1 = aurelia_dependency_injection_1_1; 21 | }, 22 | function (aurelia_logging_1_1) { 23 | aurelia_logging_1 = aurelia_logging_1_1; 24 | }, 25 | function (aurelia_task_queue_1_1) { 26 | aurelia_task_queue_1 = aurelia_task_queue_1_1; 27 | }, 28 | function (aurelia_templating_1_1) { 29 | aurelia_templating_1 = aurelia_templating_1_1; 30 | } 31 | ], 32 | execute: function () { 33 | logger = aurelia_logging_1.getLogger("dynamic-html"); 34 | DynamicHtml = class DynamicHtml { 35 | constructor(el, tq, container, viewCompiler) { 36 | this.el = el; 37 | this.tq = tq; 38 | this.container = container; 39 | this.viewCompiler = viewCompiler; 40 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 41 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 42 | this.isCleanedUp = true; 43 | } 44 | bind(bindingContext, overrideContext) { 45 | this.isBound = true; 46 | this.bindingContext = this.context || bindingContext.context || bindingContext; 47 | this.overrideContext = aurelia_binding_1.createOverrideContext(bindingContext, overrideContext); 48 | this.htmlChanged(this.html); 49 | } 50 | unbind() { 51 | this.isBound = false; 52 | this.bindingContext = null; 53 | this.overrideContext = null; 54 | } 55 | attached() { 56 | this.isAttached = true; 57 | this.isInitialized = true; 58 | this.slot = new aurelia_templating_1.ViewSlot(this.el.firstElementChild || this.el, true); 59 | this.tq.queueMicroTask(() => { 60 | this.tryCompile(); 61 | }); 62 | } 63 | detached() { 64 | this.isAttached = false; 65 | if (this.isCompiled) { 66 | this.cleanUp(); 67 | } 68 | this.slot = null; 69 | } 70 | htmlChanged(newValue, __) { 71 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 72 | this.cleanUp(); 73 | } 74 | else if (this.isBound || this.isInitialized) { 75 | this.tq.queueMicroTask(() => { 76 | this.tryCompile(); 77 | }); 78 | } 79 | } 80 | contextChanged(newValue, _) { 81 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 82 | this.bindingContext = this.overrideContext.bindingContext; 83 | } 84 | else { 85 | this.bindingContext = newValue; 86 | } 87 | if (this.isBound || this.isInitialized) { 88 | this.tq.queueMicroTask(() => { 89 | this.tryCompile(); 90 | }); 91 | } 92 | } 93 | tryCompile() { 94 | if (this.isAttached) { 95 | if (!this.isCleanedUp) { 96 | this.cleanUp(); 97 | } 98 | try { 99 | this.tq.queueMicroTask(() => { 100 | this.compile(); 101 | }); 102 | } 103 | catch (e) { 104 | logger.warn(e.message); 105 | if (this.renderErrors) { 106 | this.tq.queueMicroTask(() => { 107 | this.compile(``); 108 | }); 109 | } 110 | } 111 | } 112 | } 113 | cleanUp() { 114 | this.isCompiled = false; 115 | logger.debug("Cleaning up"); 116 | const slot = this.slot; 117 | try { 118 | slot.detached(); 119 | } 120 | catch (e) { 121 | logger.error(e.message); 122 | } 123 | try { 124 | slot.unbind(); 125 | } 126 | catch (e) { 127 | logger.error(e.message); 128 | } 129 | try { 130 | slot.removeAll(); 131 | } 132 | catch (e) { 133 | logger.error(e.message); 134 | } 135 | this.isCleanedUp = true; 136 | } 137 | compile(message) { 138 | if (!this.isCleanedUp) { 139 | this.cleanUp(); 140 | } 141 | if ((this.html === null || this.html === undefined) && message === undefined) { 142 | logger.debug("Skipping compilation because no html value is set"); 143 | return; 144 | } 145 | this.isCleanedUp = false; 146 | const template = ``; 147 | logger.debug("Compiling", template, this.bindingContext); 148 | const viewResources = this.container.get(aurelia_templating_1.ViewResources); 149 | const childContainer = this.container.createChild(); 150 | const factory = this.viewCompiler.compile(template, viewResources); 151 | const view = factory.create(childContainer); 152 | const slot = this.slot; 153 | slot.add(view); 154 | slot.bind(this.bindingContext, this.overrideContext); 155 | slot.attached(); 156 | this.dispatchCompiledEvent(); 157 | this.isCompiled = true; 158 | } 159 | dispatchCompiledEvent() { 160 | const event = new CustomEvent("compiled", { 161 | cancelable: true, 162 | bubbles: true, 163 | detail: this 164 | }); 165 | this.el.dispatchEvent(event); 166 | } 167 | }; 168 | __decorate([ 169 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 170 | __metadata("design:type", Object) 171 | ], DynamicHtml.prototype, "html", void 0); 172 | __decorate([ 173 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 174 | __metadata("design:type", Object) 175 | ], DynamicHtml.prototype, "context", void 0); 176 | __decorate([ 177 | aurelia_templating_1.bindable({ defaultBindingMode: aurelia_binding_1.bindingMode.toView }), 178 | __metadata("design:type", Boolean) 179 | ], DynamicHtml.prototype, "renderErrors", void 0); 180 | DynamicHtml = __decorate([ 181 | aurelia_templating_1.customElement("dynamic-html"), 182 | aurelia_templating_1.inlineView(""), 183 | __metadata("design:paramtypes", [Element, aurelia_task_queue_1.TaskQueue, aurelia_dependency_injection_1.Container, aurelia_templating_1.ViewCompiler]) 184 | ], DynamicHtml); 185 | exports_1("DynamicHtml", DynamicHtml); 186 | } 187 | }; 188 | }); 189 | -------------------------------------------------------------------------------- /dist/system/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | export interface IOverrideContext { 5 | parentOverrideContext: IOverrideContext; 6 | bindingContext: IBindingContext; 7 | } 8 | -------------------------------------------------------------------------------- /dist/system/interfaces.js: -------------------------------------------------------------------------------- 1 | System.register([], function (exports_1, context_1) { 2 | "use strict"; 3 | var __moduleName = context_1 && context_1.id; 4 | return { 5 | setters: [], 6 | execute: function () { 7 | } 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /karma.conf.ts: -------------------------------------------------------------------------------- 1 | import * as karma from "karma"; 2 | import * as path from "path"; 3 | import * as webpack from "webpack"; 4 | 5 | export interface IKarmaConfig extends karma.Config, IKarmaConfigOptions { 6 | transpileOnly?: boolean; 7 | noInfo?: boolean; 8 | coverage?: boolean; 9 | tsconfig?: string; 10 | set(config: IKarmaConfigOptions): void; 11 | } 12 | 13 | export interface IKarmaConfigOptions extends karma.ConfigOptions { 14 | webpack: webpack.Configuration; 15 | coverageIstanbulReporter?: any; 16 | webpackServer: any; 17 | customLaunchers: any; 18 | } 19 | 20 | export default (config: IKarmaConfig): void => { 21 | const rules: webpack.Rule[] = []; 22 | 23 | const options: IKarmaConfigOptions = { 24 | basePath: config.basePath || "./", 25 | frameworks: ["jasmine"], 26 | files: ["test/setup.ts"], 27 | preprocessors: { 28 | "test/setup.ts": ["webpack", "sourcemap"] 29 | }, 30 | webpack: { 31 | mode: "development", 32 | resolve: { 33 | extensions: [".ts", ".js"], 34 | modules: ["src", "node_modules"], 35 | alias: { 36 | src: path.resolve(__dirname, "src") 37 | } 38 | }, 39 | devtool: "cheap-module-eval-source-map", 40 | module: { 41 | rules: [ 42 | { 43 | test: /\.ts$/, 44 | loader: "ts-loader", 45 | exclude: /node_modules/, 46 | options: { 47 | configFile: config.tsconfig, 48 | transpileOnly: config.transpileOnly 49 | } 50 | } 51 | ] 52 | } 53 | }, 54 | mime: { 55 | "text/x-typescript": ["ts"] 56 | }, 57 | reporters: ["mocha", "progress"], 58 | webpackServer: { noInfo: config.noInfo }, 59 | browsers: config.browsers || ["Chrome"], 60 | customLaunchers: { 61 | ChromeDebugging: { 62 | base: "Chrome", 63 | flags: ["--remote-debugging-port=9333"], 64 | debug: true 65 | } 66 | } 67 | }; 68 | 69 | if (config.coverage) { 70 | options.webpack.module.rules.push({ 71 | enforce: "post", 72 | exclude: /(node_modules|\.spec\.ts$)/, 73 | loader: "istanbul-instrumenter-loader", 74 | options: { esModules: true }, 75 | test: /src[\/\\].+\.ts$/ 76 | }); 77 | options.reporters.push("coverage-istanbul"); 78 | options.coverageIstanbulReporter = { 79 | reports: ["html", "lcovonly", "text-summary"], 80 | fixWebpackSourcePaths: true 81 | }; 82 | } 83 | 84 | config.set(options); 85 | }; 86 | -------------------------------------------------------------------------------- /package-scripts.js: -------------------------------------------------------------------------------- 1 | const { concurrent, copy, crossEnv, rimraf, series } = require("nps-utils"); 2 | 3 | function config(name) { 4 | return `configs/tsconfig-${name}.json`; 5 | } 6 | 7 | function tsc(tsconfig) { 8 | return package(`tsc --project ${config(tsconfig)}`); 9 | } 10 | 11 | function webpack(tool, arg) { 12 | return crossEnv(`TS_NODE_PROJECT=\"${config("webpack")}\" ${tool} --config webpack.config.ts ${arg}`); 13 | } 14 | 15 | function package(script) { 16 | return crossEnv(`./node_modules/.bin/${script}`); 17 | } 18 | 19 | function karma(single, watch, browsers, transpileOnly, noInfo, coverage, tsconfig, logLevel, devtool) { 20 | return package( 21 | "karma start" 22 | .concat(single !== null ? ` --single-run=${single}` : "") 23 | .concat(watch !== null ? ` --auto-watch=${watch}` : "") 24 | .concat(browsers !== null ? ` --browsers=${browsers}` : "") 25 | .concat(transpileOnly !== null ? ` --transpile-only=${transpileOnly}` : "") 26 | .concat(noInfo !== null ? ` --no-info=${noInfo}` : "") 27 | .concat(coverage !== null ? ` --coverage=${coverage}` : "") 28 | .concat(tsconfig !== null ? ` --tsconfig=${tsconfig}` : "") 29 | .concat(logLevel !== null ? ` --log-level=${logLevel}` : "") 30 | .concat(devtool !== null ? ` --devtool=${devtool}` : "") 31 | ); 32 | } 33 | 34 | function release(version, dry) { 35 | /** 36 | * Dry prevents anything irreversible from happening. 37 | * It will do pretty much do the same thing except it won't push to git or publish to npm. 38 | * Just remember to "unbump" the version in package.json after a dry run (see explanation below) 39 | */ 40 | const variant = dry ? "dry" : "default"; 41 | return { 42 | default: series.nps( 43 | `release.${version}.${variant}.before`, 44 | `release.${version}.${variant}.version`, 45 | `release.${version}.${variant}.after` 46 | ), 47 | before: series.nps( 48 | `release.${version}.${variant}.build`, 49 | `release.${version}.${variant}.bump`, 50 | `release.${version}.${variant}.git.stage` 51 | ), 52 | after: series.nps(`release.${version}.${variant}.git.push`, `release.${version}.${variant}.npm.publish`), 53 | bump: crossEnv(`npm --no-git-tag-version version ${version}`), 54 | /** 55 | * Normally, standard-version looks for certain keywords in the commit log and automatically assigns 56 | * major/minor/patch based on the contents of those logs. 57 | * 58 | * --first-release disables that behavior and does not change the version, which allows us to manually 59 | * decide the version and bump it with npm version (see right above) instead. 60 | * 61 | * The downside is that we have to bump the version in package.json even in a dry run, because 62 | * standard-version wouldn't report what it would do otherwise. 63 | * 64 | * Therefore, always remember to manually "unbump" the version number in package.json after doing a dry run! 65 | * If you forget this, you'll end up bumping the version twice which gives you one release without changes. 66 | */ 67 | version: `standard-version --first-release --commit-all${dry ? " --dry-run" : ""}`, 68 | build: series.nps("test", "build.dist"), 69 | git: { 70 | stage: "git add package.json dist", 71 | push: `git push --follow-tags origin master${dry ? " -n" : ""}` 72 | }, 73 | npm: { 74 | publish: `npm ${dry ? "pack" : "publish"}` 75 | } 76 | }; 77 | } 78 | 79 | module.exports = { 80 | scripts: { 81 | lint: package(`tslint --project ${config("build")}`), 82 | test: { 83 | default: package("nps test.single"), 84 | single: karma(true, false, "ChromeHeadless", true, true, true, config("test"), null, null), 85 | watch: { 86 | default: package("nps test.watch.dev"), 87 | dev: karma(false, true, "ChromeHeadless", true, true, true, config("test"), null, null), 88 | debug: karma(false, true, "ChromeDebugging", true, false, null, config("test"), "debug", null) 89 | } 90 | }, 91 | build: { 92 | demo: { 93 | default: "nps build.demo.development", 94 | development: { 95 | default: webpack("webpack-dev-server", "--hot --env.server") 96 | }, 97 | production: { 98 | default: webpack("webpack", "--env.production") 99 | } 100 | }, 101 | dist: { 102 | default: "nps build.dist.tsc", 103 | before: series.nps("lint", "build.dist.clean"), 104 | clean: rimraf("dist"), 105 | rollup: { 106 | default: series.nps("build.dist.before", "build.dist.rollup.all"), 107 | all: `${package("rollup")} -c` 108 | }, 109 | tsc: { 110 | default: series.nps("build.dist.before", "build.dist.tsc.all"), 111 | all: concurrent.nps( 112 | "build.dist.tsc.amd", 113 | "build.dist.tsc.commonjs", 114 | "build.dist.tsc.es2017", 115 | "build.dist.tsc.es2015", 116 | "build.dist.tsc.nativeModules", 117 | "build.dist.tsc.system" 118 | ), 119 | amd: tsc("build-amd"), 120 | commonjs: tsc("build-commonjs"), 121 | es2017: tsc("build-es2017"), 122 | es2015: tsc("build-es2015"), 123 | nativeModules: tsc("build-native-modules"), 124 | system: tsc("build-system") 125 | } 126 | } 127 | }, 128 | release: { 129 | patch: { 130 | default: release("patch"), 131 | dry: release("patch", true) 132 | }, 133 | minor: { 134 | default: release("minor"), 135 | dry: release("minor", true) 136 | }, 137 | major: { 138 | default: release("major"), 139 | dry: release("major", true) 140 | } 141 | }, 142 | /** 143 | * Make sure to run "npm run ghpages-setup" before "npm run ghpages" the very first time, 144 | * or manually create the gh-pages branch and set the remote. 145 | * 146 | * There is no dry run variant for this because it's not really that harmful if the demo page goes bad 147 | * and it doesn't affect the master branch. 148 | */ 149 | ghpages: { 150 | default: series( 151 | "git checkout gh-pages", 152 | "git merge master --no-edit", 153 | rimraf("*.bundle.js *.worker.js"), 154 | package("nps build.demo.production"), 155 | "git add index.html *.bundle.js *.worker.js", 156 | 'git commit -m "doc(demo): build demo"', 157 | "git push", 158 | "git checkout master" 159 | ), 160 | setup: series( 161 | "git checkout -b gh-pages", 162 | "git push -u origin gh-pages", 163 | "git checkout master" 164 | ) 165 | } 166 | } 167 | }; 168 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-dynamic-html", 3 | "version": "0.3.1", 4 | "description": "Aurelia custom element that takes (server- or client side) generated html and compiles it into a fully functional Aurelia View", 5 | "keywords": [ 6 | "aurelia", 7 | "dynamic", 8 | "generate", 9 | "html", 10 | "runtime", 11 | "composition", 12 | "enhance", 13 | "compile" 14 | ], 15 | "homepage": "https://github.com/aurelia-contrib/aurelia-dynamic-html", 16 | "bugs": { 17 | "url": "https://github.com/aurelia-contrib/aurelia-dynamic-html/issues" 18 | }, 19 | "license": "MIT", 20 | "author": "Fred Kleuver ", 21 | "main": "dist/commonjs/aurelia-dynamic-html.js", 22 | "typings": "dist/commonjs/aurelia-dynamic-html.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/aurelia-contrib/aurelia-dynamic-html" 26 | }, 27 | "scripts": { 28 | "lint": "cross-env ./node_modules/.bin/nps lint", 29 | "test": "cross-env ./node_modules/.bin/nps test", 30 | "test-watch": "cross-env ./node_modules/.bin/nps test.watch", 31 | "test-debug": "cross-env ./node_modules/.bin/nps test.watch.debug", 32 | "build": "cross-env ./node_modules/.bin/nps build.dist", 33 | "demo-dev": "cross-env ./node_modules/.bin/nps build.demo", 34 | "demo-prod": "cross-env ./node_modules/.bin/nps build.demo.production", 35 | "release-patch": "cross-env ./node_modules/.bin/nps release.patch", 36 | "release-minor": "cross-env ./node_modules/.bin/nps release.minor", 37 | "release-major": "cross-env ./node_modules/.bin/nps release.major", 38 | "release-patch-dry": "cross-env ./node_modules/.bin/nps release.patch.dry", 39 | "release-minor-dry": "cross-env ./node_modules/.bin/nps release.minor.dry", 40 | "release-major-dry": "cross-env ./node_modules/.bin/nps release.major.dry", 41 | "ghpages": "cross-env ./node_modules/.bin/nps ghpages", 42 | "ghpages-setup": "cross-env ./node_modules/.bin/nps ghpages.setup" 43 | }, 44 | "dependencies": { 45 | "aurelia-binding": "^1.0.0", 46 | "aurelia-dependency-injection": "^1.0.0", 47 | "aurelia-logging": "^1.0.0", 48 | "aurelia-pal": "^1.0.0", 49 | "aurelia-task-queue": "^1.0.0", 50 | "aurelia-templating": "^1.0.0" 51 | }, 52 | "devDependencies": { 53 | "@types/bluebird-global": "^3.5.5", 54 | "@types/extract-text-webpack-plugin": "^3.0.2", 55 | "@types/html-webpack-plugin": "^2.30.3", 56 | "@types/jasmine": "^2.8.6", 57 | "@types/karma": "^1.7.3", 58 | "@types/node": "^9.6.4", 59 | "@types/webpack": "^4.1.3", 60 | "@types/webpack-env": "^1.13.6", 61 | "aurelia-bootstrapper": "^2.2.0", 62 | "aurelia-framework": "^1.2.0", 63 | "aurelia-pal-browser": "^1.7.0", 64 | "aurelia-polyfills": "^1.3.0", 65 | "aurelia-testing": "^1.0.0-beta.4.0.0", 66 | "aurelia-webpack-plugin": "^3.0.0-rc.1", 67 | "bluebird": "^3.5.1", 68 | "cross-env": "^5.1.4", 69 | "css-loader": "^0.28.11", 70 | "expose-loader": "^0.7.5", 71 | "extract-loader": "^2.0.1", 72 | "extract-text-webpack-plugin": "^3.0.2", 73 | "fork-ts-checker-webpack-plugin": "^0.4.1", 74 | "html-loader": "^0.5.5", 75 | "html-webpack-plugin": "^3.2.0", 76 | "istanbul-instrumenter-loader": "^3.0.1", 77 | "jasmine-core": "^3.1.0", 78 | "karma": "^2.0.0", 79 | "karma-chrome-launcher": "^2.2.0", 80 | "karma-coverage": "^1.1.1", 81 | "karma-coverage-istanbul-reporter": "^1.4.2", 82 | "karma-firefox-launcher": "^1.1.0", 83 | "karma-ie-launcher": "^1.0.0", 84 | "karma-jasmine": "^1.1.1", 85 | "karma-mocha-reporter": "^2.2.5", 86 | "karma-sourcemap-loader": "^0.3.7", 87 | "karma-webpack": "^3.0.0", 88 | "monaco-editor": "^0.12.0", 89 | "monaco-editor-webpack-plugin": "^1.1.0", 90 | "nps": "^5.9.0", 91 | "nps-utils": "^1.5.0", 92 | "rollup": "^0.57.1", 93 | "rollup-plugin-commonjs": "^9.1.0", 94 | "rollup-plugin-node-resolve": "^3.3.0", 95 | "rollup-plugin-typescript2": "^0.13.0", 96 | "standard-version": "^4.3.0", 97 | "style-loader": "^0.20.3", 98 | "ts-loader": "^4.2.0", 99 | "ts-node": "^5.0.1", 100 | "tsconfig-paths": "^3.2.0", 101 | "tslint": "^5.9.1", 102 | "tslint-microsoft-contrib": "^5.0.3", 103 | "typescript": "^2.8.1", 104 | "webpack": "^4.5.0", 105 | "webpack-cli": "^2.0.14", 106 | "webpack-dev-server": "^3.1.3" 107 | }, 108 | "jspm": { 109 | "registry": "npm", 110 | "jspmPackage": true, 111 | "main": "index", 112 | "format": "amd", 113 | "directories": { 114 | "dist": "dist/amd" 115 | }, 116 | "dependencies": { 117 | "aurelia-binding": "^1.0.0", 118 | "aurelia-dependency-injection": "^1.0.0", 119 | "aurelia-logging": "^1.0.0", 120 | "aurelia-pal": "^1.0.0", 121 | "aurelia-task-queue": "^1.0.0", 122 | "aurelia-templating": "^1.0.0" 123 | }, 124 | "peerDependencies": { 125 | "aurelia-binding": "^1.0.0", 126 | "aurelia-dependency-injection": "^1.0.0", 127 | "aurelia-logging": "^1.0.0", 128 | "aurelia-pal": "^1.0.0", 129 | "aurelia-task-queue": "^1.0.0", 130 | "aurelia-templating": "^1.0.0" 131 | } 132 | }, 133 | "aurelia": { 134 | "import": { 135 | "dependencies": [ 136 | { 137 | "name": "aurelia-dynamic-html", 138 | "path": "../node_modules/aurelia-dynamic-html/dist/amd", 139 | "main": "aurelia-dynamic-html" 140 | } 141 | ] 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import ts from "rollup-plugin-typescript2"; 3 | import resolve from "rollup-plugin-node-resolve"; 4 | import commonJS from "rollup-plugin-commonjs"; 5 | 6 | const pkg = JSON.parse(readFileSync("package.json", "utf-8")); 7 | 8 | function output(config, format, opts = {}) { 9 | return { 10 | input: `src/${pkg.name}.ts`, 11 | output: { ...{ file: `dist/${config}/${pkg.name}.js`, format }, ...opts }, 12 | plugins: [ 13 | resolve(), 14 | commonJS({ 15 | include: "node_modules/**" 16 | }), 17 | ts({ 18 | tsconfig: `configs/tsconfig-build-${config}.json`, 19 | tsconfigOverride: { 20 | compilerOptions: { 21 | module: "ES2015" 22 | } 23 | }, 24 | cacheRoot: ".rollupcache" 25 | }) 26 | ], 27 | external: [ 28 | "aurelia-binding", 29 | "aurelia-dependency-injection", 30 | "aurelia-logging", 31 | "aurelia-pal", 32 | "aurelia-task-queue", 33 | "aurelia-templating" 34 | ] 35 | }; 36 | } 37 | 38 | const outputs = [ 39 | output("amd", "amd", { amd: { id: pkg.name } }), 40 | output("commonjs", "cjs"), 41 | output("es2017", "es"), 42 | output("es2015", "es"), 43 | output("native-modules", "es"), 44 | output("system", "system") 45 | ]; 46 | 47 | export default outputs; 48 | -------------------------------------------------------------------------------- /src/aurelia-dynamic-html.ts: -------------------------------------------------------------------------------- 1 | import { PLATFORM } from "aurelia-pal"; 2 | 3 | export function configure(config: any): void { 4 | config.globalResources(PLATFORM.moduleName("./dynamic-html")); 5 | } 6 | 7 | export * from "./dynamic-html"; 8 | export * from "./interfaces"; 9 | -------------------------------------------------------------------------------- /src/dynamic-html.ts: -------------------------------------------------------------------------------- 1 | import { bindingMode, createOverrideContext, OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { getLogger } from "aurelia-logging"; 4 | import { TaskQueue } from "aurelia-task-queue"; 5 | import { bindable, customElement, inlineView, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating"; 6 | import { IBindingContext, IOverrideContext } from "./interfaces"; 7 | 8 | const logger = getLogger("dynamic-html"); 9 | 10 | @customElement("dynamic-html") 11 | @inlineView("") 12 | export class DynamicHtml { 13 | @bindable({ defaultBindingMode: bindingMode.toView }) 14 | public html: string | null; 15 | 16 | @bindable({ defaultBindingMode: bindingMode.toView }) 17 | public context: IBindingContext | null; 18 | 19 | @bindable({ defaultBindingMode: bindingMode.toView }) 20 | public renderErrors: boolean; 21 | 22 | public slot: ViewSlot | null; 23 | public bindingContext: IBindingContext | null; 24 | public overrideContext: IOverrideContext | OverrideContext | null; 25 | public isAttached: boolean; 26 | public isBound: boolean; 27 | public isCompiled: boolean; 28 | public isCleanedUp: boolean; 29 | public isInitialized: boolean; 30 | 31 | public el: HTMLElement; 32 | protected tq: TaskQueue; 33 | protected container: Container; 34 | protected viewCompiler: ViewCompiler; 35 | 36 | constructor(el: Element, tq: TaskQueue, container: Container, viewCompiler: ViewCompiler) { 37 | this.el = el as HTMLElement; 38 | this.tq = tq; 39 | this.container = container; 40 | this.viewCompiler = viewCompiler; 41 | this.html = this.context = this.slot = this.bindingContext = this.overrideContext = null; 42 | this.renderErrors = this.isAttached = this.isBound = this.isCompiled = this.isInitialized = false; 43 | this.isCleanedUp = true; 44 | } 45 | 46 | public bind(bindingContext: IBindingContext, overrideContext: IOverrideContext): void { 47 | this.isBound = true; 48 | 49 | this.bindingContext = this.context || bindingContext.context || bindingContext; 50 | this.overrideContext = createOverrideContext(bindingContext, overrideContext); 51 | 52 | this.htmlChanged(this.html); 53 | } 54 | 55 | public unbind(): void { 56 | this.isBound = false; 57 | 58 | this.bindingContext = null; 59 | this.overrideContext = null; 60 | } 61 | 62 | public attached(): void { 63 | this.isAttached = true; 64 | this.isInitialized = true; 65 | 66 | this.slot = new ViewSlot(this.el.firstElementChild || this.el, true); 67 | 68 | this.tq.queueMicroTask(() => { 69 | this.tryCompile(); 70 | }); 71 | } 72 | 73 | public detached(): void { 74 | this.isAttached = false; 75 | 76 | if (this.isCompiled) { 77 | this.cleanUp(); 78 | } 79 | this.slot = null; 80 | } 81 | 82 | protected htmlChanged(newValue: string | null, __?: string): void { 83 | if ((newValue === null || newValue === undefined) && !this.isCleanedUp) { 84 | this.cleanUp(); 85 | } else if (this.isBound || this.isInitialized) { 86 | this.tq.queueMicroTask(() => { 87 | this.tryCompile(); 88 | }); 89 | } 90 | } 91 | 92 | protected contextChanged(newValue: IBindingContext | null, _?: IBindingContext): void { 93 | if ((newValue === null || newValue === undefined) && !!this.overrideContext) { 94 | this.bindingContext = this.overrideContext.bindingContext; 95 | } else { 96 | this.bindingContext = newValue; 97 | } 98 | if (this.isBound || this.isInitialized) { 99 | this.tq.queueMicroTask(() => { 100 | this.tryCompile(); 101 | }); 102 | } 103 | } 104 | 105 | protected tryCompile(): void { 106 | if (this.isAttached) { 107 | if (!this.isCleanedUp) { 108 | this.cleanUp(); 109 | } 110 | try { 111 | this.tq.queueMicroTask(() => { 112 | this.compile(); 113 | }); 114 | } catch (e) { 115 | logger.warn(e.message); 116 | if (this.renderErrors) { 117 | this.tq.queueMicroTask(() => { 118 | this.compile(``); 119 | }); 120 | } 121 | } 122 | } 123 | } 124 | 125 | protected cleanUp(): void { 126 | this.isCompiled = false; 127 | 128 | logger.debug("Cleaning up"); 129 | 130 | const slot = this.slot as ViewSlot; 131 | try { 132 | slot.detached(); 133 | } catch (e) { 134 | logger.error(e.message); 135 | } 136 | try { 137 | slot.unbind(); 138 | } catch (e) { 139 | logger.error(e.message); 140 | } 141 | try { 142 | slot.removeAll(); 143 | } catch (e) { 144 | logger.error(e.message); 145 | } 146 | 147 | this.isCleanedUp = true; 148 | } 149 | 150 | protected compile(message?: string): void { 151 | if (!this.isCleanedUp) { 152 | this.cleanUp(); 153 | } 154 | if ((this.html === null || this.html === undefined) && message === undefined) { 155 | logger.debug("Skipping compilation because no html value is set"); 156 | 157 | return; 158 | } 159 | this.isCleanedUp = false; 160 | 161 | const template = ``; 162 | 163 | logger.debug("Compiling", template, this.bindingContext); 164 | 165 | const viewResources = this.container.get(ViewResources) as ViewResources; 166 | const childContainer = this.container.createChild(); 167 | const factory = this.viewCompiler.compile(template, viewResources); 168 | const view = factory.create(childContainer); 169 | const slot = this.slot as ViewSlot; 170 | 171 | slot.add(view); 172 | slot.bind(this.bindingContext as IBindingContext, this.overrideContext as IOverrideContext); 173 | slot.attached(); 174 | 175 | this.dispatchCompiledEvent(); 176 | 177 | this.isCompiled = true; 178 | } 179 | 180 | protected dispatchCompiledEvent(): void { 181 | const event = new CustomEvent("compiled", { 182 | cancelable: true, 183 | bubbles: true, 184 | detail: this 185 | }); 186 | this.el.dispatchEvent(event); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IBindingContext { 2 | [key: string]: any; 3 | } 4 | 5 | export interface IOverrideContext { 6 | parentOverrideContext: IOverrideContext; 7 | bindingContext: IBindingContext; 8 | } 9 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | import "aurelia-loader-webpack"; 3 | import "aurelia-polyfills"; 4 | import { initialize } from "aurelia-pal-browser"; 5 | // tslint:enable 6 | 7 | initialize(); 8 | Error.stackTraceLimit = Infinity; 9 | 10 | const testContext: any = (require as any).context("./unit", true, /\.spec/); 11 | testContext.keys().forEach(testContext); 12 | -------------------------------------------------------------------------------- /test/unit/aurelia-dynamic-html.spec.ts: -------------------------------------------------------------------------------- 1 | import { configure } from "../../src/aurelia-dynamic-html"; 2 | 3 | describe("configure()", () => { 4 | it("should call globalResources", () => { 5 | let moduleName: string = null as any; 6 | const config: any = { 7 | globalResources: (name: string) => { 8 | moduleName = name; 9 | } 10 | }; 11 | 12 | configure(config); 13 | 14 | expect(moduleName).toEqual("./dynamic-html"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/unit/dynamic-html.spec.ts: -------------------------------------------------------------------------------- 1 | import { OverrideContext } from "aurelia-binding"; 2 | import { Container } from "aurelia-dependency-injection"; 3 | import { DOM } from "aurelia-pal"; 4 | import { TaskQueue } from "aurelia-task-queue"; 5 | import { BindingLanguage, ViewCompiler, ViewResources, ViewSlot } from "aurelia-templating"; 6 | import { DynamicHtml } from "../../src/dynamic-html"; 7 | import { IBindingContext, IOverrideContext } from "../../src/interfaces"; 8 | import { getAllProperties, getAllPropertyNames } from "./util"; 9 | 10 | // tslint:disable:mocha-no-side-effect-code 11 | // tslint:disable:variable-name 12 | // tslint:disable:no-empty 13 | // tslint:disable:max-line-length 14 | 15 | class SutProps { 16 | public __metadata__: any = null; 17 | public html: string | null = null; 18 | public context: IBindingContext | null = null; 19 | public renderErrors: boolean = false; 20 | 21 | public slot: ViewSlot | null = null; 22 | public bindingContext: IBindingContext | null = null; 23 | public overrideContext: IOverrideContext | OverrideContext | null = null; 24 | public isAttached: boolean = false; 25 | public isBound: boolean = false; 26 | public isCompiled: boolean = false; 27 | public isCleanedUp: boolean = true; 28 | public isInitialized: boolean = false; 29 | 30 | public el: HTMLElement = DOM.createElement("div"); 31 | public tq: TaskQueue = new TaskQueue(); 32 | public container: Container = new Container(); 33 | public viewCompiler: ViewCompiler = new ViewCompiler(new BindingLanguage(), new ViewResources()); 34 | 35 | public bind(_: any, __: any): void {} 36 | public unbind(): void {} 37 | public attached(): void {} 38 | public detached(): void {} 39 | public htmlChanged(_: any, __?: any): void {} 40 | public contextChanged(_: any, __?: any): void {} 41 | public tryCompile(): void {} 42 | public cleanUp(): void {} 43 | public compile(_: any): void {} 44 | public dispatchCompiledEvent(_: any): void {} 45 | } 46 | 47 | describe("SutProps", () => { 48 | const sutActual = new (DynamicHtml as any)(); 49 | const sutProps = new SutProps(); 50 | 51 | it("should have property", () => { 52 | const actualKeys = getAllPropertyNames(sutActual).sort(); 53 | const expectedKeys = getAllPropertyNames(sutProps).sort(); 54 | 55 | expect(actualKeys).toEqual(expectedKeys); 56 | }); 57 | }); 58 | 59 | describe("constructor()", () => { 60 | let actualSut: SutProps; 61 | const expectedSut = new SutProps(); 62 | const expectedProps = getAllProperties(expectedSut).filter(p => !(p.value instanceof Function)); 63 | const expectedPropsCount = expectedProps.length; 64 | 65 | beforeEach(() => { 66 | actualSut = new DynamicHtml(expectedSut.el, expectedSut.tq, expectedSut.container, expectedSut.viewCompiler) as any; 67 | }); 68 | 69 | describe("should set property", () => { 70 | for (let i = 0; i < expectedPropsCount; i++) { 71 | it(expectedProps[i].key, () => { 72 | expect(actualSut[expectedProps[i].key]).toEqual(expectedProps[i].value); 73 | }); 74 | } 75 | }); 76 | 77 | describe("should not leave property undefined", () => { 78 | for (let i = 0; i < expectedPropsCount; i++) { 79 | it(expectedProps[i].key, () => { 80 | expect(actualSut[expectedProps[i].key]).not.toBeUndefined(); 81 | }); 82 | } 83 | }); 84 | }); 85 | 86 | describe("bind()", () => { 87 | let sut: SutProps; 88 | let bc: any; 89 | let oc: any; 90 | const props = new SutProps(); 91 | 92 | beforeEach(() => { 93 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 94 | bc = {}; 95 | oc = {}; 96 | }); 97 | 98 | describe("should set property", () => { 99 | it("isBound", () => { 100 | sut.bind(bc, oc); 101 | expect(sut.isBound).toBe(true); 102 | }); 103 | 104 | it("bindingContext when this.context is null and bindingContext.context is undefined", () => { 105 | sut.bind(bc, oc); 106 | expect(sut.bindingContext).toBe(bc); 107 | }); 108 | 109 | it("bindingContext when this.context is null and bindingContext.context is an object", () => { 110 | bc.context = {}; 111 | sut.bind(bc, oc); 112 | expect(sut.bindingContext).toBe(bc.context); 113 | }); 114 | 115 | it("bindingContext when this.context is an object", () => { 116 | sut.context = {}; 117 | delete sut.contextChanged; 118 | sut.bind(bc, oc); 119 | expect(sut.bindingContext).toBe(sut.context); 120 | }); 121 | }); 122 | 123 | describe("should not set property", () => { 124 | const excludedProps = ["context", "isBound", "bindingContext", "overrideContext", "tq", "viewCompiler"]; 125 | const expectedSut = new SutProps(); 126 | const expectedProps = getAllProperties(expectedSut).filter( 127 | p => !(p.value instanceof Function) && excludedProps.indexOf(p.key) === -1 128 | ); 129 | const expectedPropsCount = expectedProps.length; 130 | 131 | for (let i = 0; i < expectedPropsCount; i++) { 132 | it(expectedProps[i].key, () => { 133 | sut.bind(bc, oc); 134 | expect((sut as any)[expectedProps[i].key]).toEqual((expectedSut as any)[expectedProps[i].key]); 135 | }); 136 | } 137 | }); 138 | }); 139 | 140 | describe("unbind()", () => { 141 | let sut: SutProps; 142 | const props = new SutProps(); 143 | 144 | beforeEach(() => { 145 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 146 | }); 147 | 148 | describe("should set property", () => { 149 | beforeEach(() => { 150 | sut.bind({}, {}); 151 | }); 152 | 153 | it("bindingContext", () => { 154 | sut.unbind(); 155 | expect(sut.bindingContext).toBeNull(); 156 | }); 157 | 158 | it("overrideContext", () => { 159 | sut.unbind(); 160 | expect(sut.overrideContext).toBeNull(); 161 | }); 162 | 163 | it("isBound", () => { 164 | sut.unbind(); 165 | expect(sut.isBound).toBe(false); 166 | }); 167 | }); 168 | 169 | describe("should not set property", () => { 170 | const excludedProps = ["isBound", "bindingContext", "overrideContext"]; 171 | const expectedSut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 172 | const expectedProps = getAllProperties(expectedSut).filter( 173 | p => !(p.value instanceof Function) && excludedProps.indexOf(p.key) === -1 174 | ); 175 | const expectedPropsCount = expectedProps.length; 176 | 177 | beforeEach(() => { 178 | const bc = {}; 179 | const oc = {}; 180 | sut.bind(bc, oc); 181 | expectedSut.bind(bc, oc); 182 | }); 183 | 184 | for (let i = 0; i < expectedPropsCount; i++) { 185 | it(expectedProps[i].key, () => { 186 | sut.unbind(); 187 | expect((sut as any)[expectedProps[i].key]).toEqual((expectedSut as any)[expectedProps[i].key]); 188 | }); 189 | } 190 | }); 191 | }); 192 | 193 | describe("attached()", () => { 194 | let sut: SutProps; 195 | const props = new SutProps(); 196 | 197 | beforeEach(() => { 198 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 199 | }); 200 | 201 | describe("should set property", () => { 202 | it("isAttached", () => { 203 | sut.attached(); 204 | expect(sut.isAttached).toBe(true); 205 | }); 206 | 207 | it("isInitialized", () => { 208 | sut.attached(); 209 | expect(sut.isInitialized).toBe(true); 210 | }); 211 | 212 | it("slot", () => { 213 | sut.attached(); 214 | expect(sut.slot).toEqual(jasmine.any(ViewSlot)); 215 | }); 216 | }); 217 | 218 | describe("should not set property", () => { 219 | const excludedProps = ["isAttached", "isInitialized", "slot", "tq", "viewCompiler"]; 220 | const expectedSut = new SutProps(); 221 | const expectedProps = getAllProperties(expectedSut).filter( 222 | p => !(p.value instanceof Function) && excludedProps.indexOf(p.key) === -1 223 | ); 224 | const expectedPropsCount = expectedProps.length; 225 | 226 | for (let i = 0; i < expectedPropsCount; i++) { 227 | it(expectedProps[i].key, () => { 228 | sut.attached(); 229 | expect((sut as any)[expectedProps[i].key]).toEqual((expectedSut as any)[expectedProps[i].key]); 230 | }); 231 | } 232 | }); 233 | }); 234 | 235 | describe("detached()", () => { 236 | let sut: SutProps; 237 | const props = new SutProps(); 238 | 239 | beforeEach(() => { 240 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 241 | sut.attached(); 242 | }); 243 | 244 | describe("should set property", () => { 245 | it("isAttached", () => { 246 | sut.detached(); 247 | expect(sut.isAttached).toBe(false); 248 | }); 249 | 250 | it("slot", () => { 251 | sut.detached(); 252 | expect(sut.slot).toBeNull(); 253 | }); 254 | }); 255 | 256 | describe("should not set property", () => { 257 | const excludedProps = ["isAttached", "slot"]; 258 | const expectedSut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 259 | const expectedProps = getAllProperties(expectedSut).filter( 260 | p => !(p.value instanceof Function) && excludedProps.indexOf(p.key) === -1 261 | ); 262 | const expectedPropsCount = expectedProps.length; 263 | 264 | beforeEach(() => { 265 | sut.attached(); 266 | expectedSut.attached(); 267 | }); 268 | 269 | for (let i = 0; i < expectedPropsCount; i++) { 270 | it(expectedProps[i].key, () => { 271 | sut.detached(); 272 | expect((sut as any)[expectedProps[i].key]).toEqual((expectedSut as any)[expectedProps[i].key]); 273 | }); 274 | } 275 | }); 276 | }); 277 | 278 | describe("htmlChanged()", () => { 279 | let sut: SutProps; 280 | const props = new SutProps(); 281 | 282 | beforeEach(() => { 283 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 284 | }); 285 | 286 | const newValueValues = [undefined, null, "", "asdf"]; 287 | const isCleanedUpValues = [true, false]; 288 | const isBoundValues = [true, false]; 289 | const isInitializedValues = [true, false]; 290 | 291 | const newValueValuesLength = newValueValues.length; 292 | const isCleanedUpValuesLength = isCleanedUpValues.length; 293 | const isBoundValuesLength = isBoundValues.length; 294 | const isInitializedValuesLength = isInitializedValues.length; 295 | 296 | for (let i = 0; i < newValueValuesLength; i++) { 297 | const newValue = newValueValues[i]; 298 | 299 | for (let j = 0; j < isCleanedUpValuesLength; j++) { 300 | const isCleanedUp = isCleanedUpValues[j]; 301 | 302 | for (let k = 0; k < isBoundValuesLength; k++) { 303 | const isBound = isBoundValues[k]; 304 | 305 | for (let l = 0; l < isInitializedValuesLength; l++) { 306 | const isInitialized = isInitializedValues[l]; 307 | 308 | // tslint:disable-next-line:max-line-length 309 | it(`newValue=${!newValue ? typeof newValue : newValue}, isCleanedUp=${isCleanedUp}, isBound=${isBound}, isInitialized=${isInitialized}`, done => { 310 | sut.isCleanedUp = isCleanedUp; 311 | sut.isBound = isBound; 312 | sut.isInitialized = isInitialized; 313 | 314 | const cleanUpSpy = spyOn(sut, "cleanUp").and.callThrough(); 315 | const tryCompileSpy = spyOn(sut, "tryCompile").and.callThrough(); 316 | 317 | sut.htmlChanged(newValue); 318 | 319 | setTimeout(() => { 320 | if ((newValue === null || newValue === undefined) && isCleanedUp === false) { 321 | expect(cleanUpSpy.calls.count()).toBe(1, "cleanUp() should have been called"); 322 | expect(tryCompileSpy.calls.count()).toBe(0, "tryCompile() should NOT have been called"); 323 | } else if (isBound === true || isInitialized === true) { 324 | expect(cleanUpSpy.calls.count()).toBe(0, "cleanUp() should NOT have been called"); 325 | expect(tryCompileSpy.calls.count()).toBe(1, "tryCompile() should have been called"); 326 | } else { 327 | expect(cleanUpSpy.calls.count()).toBe(0, "tryCompile() should NOT have been called"); 328 | expect(tryCompileSpy.calls.count()).toBe(0, "tryCompile() should NOT have been called"); 329 | } 330 | done(); 331 | }, 0); 332 | }); 333 | } 334 | } 335 | } 336 | } 337 | }); 338 | 339 | describe("contextChanged()", () => { 340 | let sut: SutProps; 341 | const props = new SutProps(); 342 | 343 | beforeEach(() => { 344 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 345 | }); 346 | 347 | const newValueValues = [undefined, null, {}]; 348 | const overrideContextValues = [undefined, null, {bindingContext: {}}]; 349 | const isBoundValues = [true, false]; 350 | const isInitializedValues = [true, false]; 351 | 352 | const newValueValuesLength = newValueValues.length; 353 | const overrideContextValuesLength = overrideContextValues.length; 354 | const isBoundValuesLength = isBoundValues.length; 355 | const isInitializedValuesLength = isInitializedValues.length; 356 | 357 | for (let i = 0; i < newValueValuesLength; i++) { 358 | const newValue = newValueValues[i]; 359 | 360 | for (let j = 0; j < overrideContextValuesLength; j++) { 361 | const overrideContext = overrideContextValues[j]; 362 | 363 | for (let k = 0; k < isBoundValuesLength; k++) { 364 | const isBound = isBoundValues[k]; 365 | 366 | for (let l = 0; l < isInitializedValuesLength; l++) { 367 | const isInitialized = isInitializedValues[l]; 368 | 369 | // tslint:disable-next-line:max-line-length 370 | it(`newValue=${typeof newValue}, overrideContext=${typeof overrideContext}, isBound=${isBound}, isInitialized=${isInitialized}`, done => { 371 | sut.overrideContext = overrideContext as any; 372 | sut.isBound = isBound; 373 | sut.isInitialized = isInitialized; 374 | 375 | const tryCompileSpy = spyOn(sut, "tryCompile").and.callThrough(); 376 | const initialBindingContext = sut.bindingContext = {}; 377 | 378 | sut.contextChanged(newValue); 379 | 380 | setTimeout(() => { 381 | if (newValue === null || newValue === undefined) { 382 | if (!!overrideContext) { 383 | expect(sut.bindingContext).toBe(overrideContext.bindingContext, "bindingContext should have been set to overrideContext.bindingContext"); 384 | } else { 385 | expect(sut.bindingContext).toBe(newValue as any, "bindingContext should have been set to newValue"); 386 | } 387 | } else { 388 | expect(sut.bindingContext).toBe(newValue as any, "bindingContext should have been set to newValue"); 389 | } 390 | expect(sut.bindingContext).not.toBe(initialBindingContext, "bindingContext should never be the same as its original value"); 391 | if (isBound === true || isInitialized === true) { 392 | expect(tryCompileSpy.calls.count()).toBe(1, "tryCompile() should have been called"); 393 | } else { 394 | expect(tryCompileSpy.calls.count()).toBe(0, "tryCompile() should NOT have been called"); 395 | } 396 | done(); 397 | }, 0); 398 | }); 399 | } 400 | } 401 | } 402 | } 403 | }); 404 | 405 | describe("tryCompile()", () => { 406 | let sut: SutProps; 407 | const props = new SutProps(); 408 | 409 | beforeEach(() => { 410 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 411 | }); 412 | 413 | const isAttachedValues = [true, false]; 414 | const isCleanedUpValues = [true, false]; 415 | 416 | const isAttachedValuesLength = isAttachedValues.length; 417 | const isCleanedUpValuesLength = isCleanedUpValues.length; 418 | 419 | for (let i = 0; i < isAttachedValuesLength; i++) { 420 | const isAttached = isAttachedValues[i]; 421 | 422 | for (let j = 0; j < isCleanedUpValuesLength; j++) { 423 | const isCleanedUp = isCleanedUpValues[j]; 424 | 425 | // tslint:disable-next-line:max-line-length 426 | it(`isAttached=${isAttached}, isCleanedUp=${isCleanedUp}`, done => { 427 | sut.isAttached = isAttached; 428 | sut.isCleanedUp = isCleanedUp; 429 | 430 | const cleanUpSpy = spyOn(sut, "cleanUp").and.callThrough(); 431 | 432 | sut.tryCompile(); 433 | 434 | setTimeout(() => { 435 | if (isAttached === true && isCleanedUp === false) { 436 | expect(cleanUpSpy.calls.count()).toBe(1, "cleanUp() should have been called"); 437 | } else { 438 | expect(cleanUpSpy.calls.count()).toBe(0, "cleanUp() should NOT have been called"); 439 | } 440 | done(); 441 | }, 0); 442 | }); 443 | } 444 | } 445 | }); 446 | 447 | describe("cleanUp()", () => { 448 | let sut: any; 449 | const props = new SutProps(); 450 | 451 | beforeEach(() => { 452 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 453 | }); 454 | 455 | it("should always call slot.detached()", () => { 456 | sut.slot = jasmine.createSpyObj(["detached"]); 457 | sut.cleanUp(); 458 | expect(sut.slot.detached.calls.count()).toBe(1); 459 | }); 460 | 461 | it("should always call slot.unbind()", () => { 462 | sut.slot = jasmine.createSpyObj(["unbind"]); 463 | sut.cleanUp(); 464 | expect(sut.slot.unbind.calls.count()).toBe(1); 465 | }); 466 | 467 | it("should always call slot.removeAll()", () => { 468 | sut.slot = jasmine.createSpyObj(["removeAll"]); 469 | sut.cleanUp(); 470 | expect(sut.slot.removeAll.calls.count()).toBe(1); 471 | }); 472 | 473 | it("should always set isCompiled to false", () => { 474 | sut.isCompiled = true; 475 | sut.cleanUp(); 476 | expect(sut.isCompiled).toBe(false); 477 | }); 478 | 479 | it("should always set isCleanedUp to true", () => { 480 | sut.isCleanedUp = false; 481 | sut.cleanUp(); 482 | expect(sut.isCleanedUp).toBe(true); 483 | }); 484 | }); 485 | 486 | describe("compile()", () => { 487 | let sut: any; 488 | const props = new SutProps(); 489 | 490 | beforeEach(() => { 491 | sut = new DynamicHtml(props.el, props.tq, props.container, props.viewCompiler) as any; 492 | }); 493 | 494 | it("should call cleanUp() when isCleanedUp is false", done => { 495 | sut.slot = jasmine.createSpyObj(["add", "bind", "attached"]); 496 | sut.isCleanedUp = false; 497 | 498 | const cleanUpSpy = spyOn(sut, "cleanUp").and.callThrough(); 499 | 500 | sut.compile(); 501 | 502 | setTimeout(() => { 503 | expect(cleanUpSpy.calls.count()).toBe(1); 504 | done(); 505 | }, 0); 506 | }); 507 | 508 | it("should NOT call cleanUp() when isCleanedUp is true", done => { 509 | sut.slot = jasmine.createSpyObj(["add", "bind", "attached"]); 510 | sut.isCleanedUp = true; 511 | 512 | const cleanUpSpy = spyOn(sut, "cleanUp").and.callThrough(); 513 | 514 | sut.compile(); 515 | 516 | setTimeout(() => { 517 | expect(cleanUpSpy.calls.count()).toBe(0); 518 | done(); 519 | }, 0); 520 | }); 521 | 522 | it("should always set isCleanedUp to false if html has a value", () => { 523 | sut.isCleanedUp = true; 524 | delete sut.htmlChanged; 525 | sut.html = ""; 526 | 527 | try { 528 | sut.compile(); 529 | } catch (e) { } 530 | 531 | expect(sut.isCleanedUp).toBe(false); 532 | }); 533 | 534 | it("should not proceed with compilation if html is null", () => { 535 | sut.isCleanedUp = true; 536 | delete sut.htmlChanged; 537 | sut.html = null; 538 | 539 | sut.compile(); 540 | 541 | expect(sut.isCleanedUp).toBe(true); 542 | }); 543 | 544 | it("should not proceed with compilation if html is undefined", () => { 545 | sut.isCleanedUp = true; 546 | delete sut.htmlChanged; 547 | sut.html = undefined; 548 | 549 | sut.compile(); 550 | 551 | expect(sut.isCleanedUp).toBe(true); 552 | }); 553 | 554 | it("should proceed with compilation if html has a value", () => { 555 | sut.isCleanedUp = true; 556 | delete sut.htmlChanged; 557 | sut.html = ""; 558 | spyOn(sut.container, "get").and.callFake(new Function()); 559 | spyOn(sut.container, "createChild").and.callFake(new Function()); 560 | const mockFactory = jasmine.createSpyObj(["create"]); 561 | spyOn(sut.viewCompiler, "compile").and.returnValue(mockFactory); 562 | sut.slot = jasmine.createSpyObj(["add", "bind", "attached"]); 563 | 564 | sut.compile(); 565 | 566 | expect(sut.container.get.calls.count()).toBe(1); 567 | expect(sut.container.createChild.calls.count()).toBe(1); 568 | expect(mockFactory.create.calls.count()).toBe(1); 569 | expect(sut.viewCompiler.compile.calls.count()).toBe(1); 570 | expect(sut.slot.add.calls.count()).toBe(1); 571 | expect(sut.slot.bind.calls.count()).toBe(1); 572 | expect(sut.slot.attached.calls.count()).toBe(1); 573 | 574 | expect(sut.isCompiled).toBe(true); 575 | }); 576 | }); 577 | -------------------------------------------------------------------------------- /test/unit/util.ts: -------------------------------------------------------------------------------- 1 | export function getAllProperties(obj: T): { key: keyof T; value: T[keyof T] }[] { 2 | return (getAllPropertyNames(obj) as (keyof T)[]).map(key => ({ key, value: obj[key] })); 3 | } 4 | 5 | export function getAllPropertyNames(obj: any): string[] { 6 | const allNames = ["__metadata__"]; 7 | let proto = obj; 8 | for (; proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) { 9 | const ownNames = Object.getOwnPropertyNames(proto); 10 | const length = ownNames.length; 11 | for (let i = 0; i < length; i++) { 12 | const propertyName = ownNames[i]; 13 | if (allNames.indexOf(propertyName) === -1) { 14 | allNames.push(propertyName); 15 | } 16 | } 17 | } 18 | 19 | return allNames.slice(1); 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./configs/tsconfig-projectroot.json" 3 | } 4 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | /** 4 | * Security Rules. The following rules should be turned on because they find security issues 5 | * or are recommended in the Microsoft Secure Development Lifecycle (SDL) 6 | */ 7 | "insecure-random": true, 8 | "no-banned-terms": true, 9 | "no-cookies": true, 10 | "no-delete-expression": true, 11 | "no-disable-auto-sanitization": true, 12 | "no-document-domain": true, 13 | "no-document-write": true, 14 | "no-eval": true, 15 | "no-exec-script": true, 16 | "no-function-constructor-with-string-args": true, 17 | "no-http-string": [true, "http://www.example.com/?.*", "http://www.examples.com/?.*"], 18 | "no-inner-html": true, 19 | "no-octal-literal": true, 20 | "no-reserved-keywords": true, 21 | "no-string-based-set-immediate": true, 22 | "no-string-based-set-interval": true, 23 | "no-string-based-set-timeout": true, 24 | "non-literal-require": true, 25 | "possible-timing-attack": true, 26 | "react-anchor-blank-noopener": true, 27 | "react-iframe-missing-sandbox": true, 28 | "react-no-dangerous-html": true, 29 | 30 | /** 31 | * Common Bugs and Correctness. The following rules should be turned on because they find 32 | * common bug patterns in the code or enforce type safety. 33 | */ 34 | "await-promise": true, 35 | "forin": true, 36 | "jquery-deferred-must-complete": true, 37 | "label-position": true, 38 | "match-default-export-name": true, 39 | "mocha-avoid-only": true, 40 | "mocha-no-side-effect-code": true, 41 | "no-any": false, // fkleuver: we often need to use external libraries with incomplete/outdated type definitions, and casting "as any" is the only reasonable way to work around type-checking errors 42 | "no-arg": true, 43 | "no-backbone-get-set-outside-model": true, 44 | "no-bitwise": false, // fkleuver: we need bitwise and we know what we're doing.. 45 | "no-conditional-assignment": true, 46 | "no-console": [true, "debug", "info", "log", "time", "timeEnd", "trace"], 47 | "no-constant-condition": true, 48 | "no-control-regex": true, 49 | "no-debugger": true, 50 | "no-duplicate-super": true, 51 | "no-duplicate-switch-case": true, 52 | "no-duplicate-variable": true, 53 | "no-empty": true, 54 | "no-floating-promises": true, 55 | "no-for-in-array": true, 56 | "no-implicit-dependencies": false, // fkleuver: we can't make internal dependencies explicit as far as I know 57 | "no-import-side-effect": true, 58 | "no-increment-decrement": false, // fkleuver: this is a well-known, well-documented shorthand which is present in (and works consistently across) multiple strongly-typed languages (C#, Java..), and in many cases improves readability of code, so let's not disallow this 59 | "no-invalid-regexp": true, 60 | "no-invalid-template-strings": true, 61 | "no-invalid-this": true, 62 | "no-jquery-raw-elements": true, 63 | "no-misused-new": true, 64 | "no-non-null-assertion": true, 65 | "no-object-literal-type-assertion": true, 66 | "no-parameter-reassignment": true, 67 | "no-reference-import": true, 68 | "no-regex-spaces": true, 69 | "no-sparse-arrays": true, 70 | "no-string-literal": true, 71 | "no-string-throw": true, 72 | "no-submodule-imports": false, // fkleuver: this will also warn on importing internal non-relative modules and that's not worth the hassle 73 | "no-unnecessary-bind": true, 74 | "no-unnecessary-callback-wrapper": true, 75 | "no-unnecessary-initializer": true, 76 | "no-unnecessary-override": true, 77 | "no-unsafe-any": false, // fkleuver: checking "any" arguments for null or undefined should be up to the author, as in many cases this leads to redundant checks 78 | "no-unsafe-finally": true, 79 | "no-unused-expression": true, 80 | "no-use-before-declare": false, // fkleuver: only useful when using the "var" keyword which we really never use anymore in TypeScript, and slow to compute, so let's turn it off 81 | "no-with-statement": true, 82 | "promise-function-async": true, 83 | "promise-must-complete": true, 84 | "radix": true, 85 | "react-this-binding-issue": true, 86 | "react-unused-props-and-state": true, 87 | "restrict-plus-operands": true, // the plus operand should really only be used for strings and numbers 88 | "strict-boolean-expressions": [ 89 | true, 90 | "allow-null-union", 91 | "allow-undefined-union", 92 | "allow-string", 93 | "allow-number", 94 | "allow-mix" 95 | ], // fkleuver: allowing these is a productivity tradeoff between time spent looking for bugs caused by unintentional uses, and time saved by writing shorter code 96 | "switch-default": true, 97 | "switch-final-break": true, 98 | "triple-equals": [true, "allow-null-check"], 99 | "use-isnan": true, 100 | "use-named-parameter": true, 101 | 102 | /** 103 | * Code Clarity. The following rules should be turned on because they make the code 104 | * generally more clear to the reader. 105 | */ 106 | "adjacent-overload-signatures": true, 107 | "array-type": [true, "array"], 108 | "arrow-parens": false, // for simple functions the parens on arrow functions are not needed 109 | "ban-comma-operator": true, // possibly controversial 110 | "binary-expression-operand-order": true, 111 | "callable-types": true, 112 | "chai-prefer-contains-to-index-of": true, 113 | "chai-vague-errors": true, 114 | "class-name": true, 115 | "comment-format": true, 116 | "completed-docs": [true, "classes"], 117 | "export-name": false, // fkleuver: impractical for aurelia projects due to naming conventions for exported html resources, features, etc 118 | "function-name": true, 119 | "import-name": true, 120 | "interface-name": true, 121 | "jsdoc-format": true, 122 | "max-classes-per-file": [true, 3], // we generally recommend making one public class per file 123 | "max-file-line-count": true, 124 | "max-func-body-length": [true, 100, { "ignore-parameters-to-function-regex": "describe" }], 125 | "max-line-length": [true, 140], 126 | "member-access": true, 127 | "member-ordering": [true, { "order": "fields-first" }], 128 | "missing-jsdoc": false, // fkleuver: we don't want to enforce documentation on every single file (due to aurelia conventions being fairly self-explanatory), furthermore, for internal projects code ought to be written in such a way that it documents itself, whilst applying common sense to document critical and/or complex parts of the application 129 | "mocha-unneeded-done": true, 130 | "new-parens": true, 131 | "no-construct": true, 132 | "no-default-export": false, // fkleuver: common practise in many aurelia projects including the generated output from aurelia-cli (environment.ts), so we're leaving this off to prevent nagging just about every aurelia-cli project 133 | "no-duplicate-imports": true, 134 | "no-empty-interface": true, 135 | "no-for-in": true, 136 | "no-function-expression": true, 137 | "no-inferrable-types": false, // turn no-inferrable-types off in order to make the code consistent in its use of type decorations 138 | "no-multiline-string": true, // multiline-strings often introduce unnecessary whitespace into the string literals 139 | "no-null-keyword": false, // turn no-null-keyword off and use undefined to mean not initialized and null to mean without a value 140 | "no-parameter-properties": true, 141 | "no-redundant-jsdoc": true, 142 | "no-relative-imports": false, // fkleuver: relative imports are a standard practise in the vast majority of aurelia projects and examples; I believe that the benefits of consistency between aurelia projects outweigh the benefits of consistency between typescript projects 143 | "no-require-imports": true, 144 | "no-return-await": true, 145 | "no-shadowed-variable": true, 146 | "no-suspicious-comment": true, 147 | "no-this-assignment": true, 148 | "no-typeof-undefined": true, 149 | "no-unnecessary-class": false, // fkleuver: tslint is kind of bad at deciding when a class is "unnecessary" 150 | "no-unnecessary-field-initialization": true, 151 | "no-unnecessary-local-variable": true, 152 | "no-unnecessary-qualifier": true, 153 | "no-unnecessary-type-assertion": true, 154 | "no-unsupported-browser-code": true, 155 | "no-useless-files": true, 156 | "no-var-keyword": true, 157 | "no-var-requires": true, 158 | "no-void-expression": true, 159 | "number-literal-format": true, 160 | "object-literal-sort-keys": false, // turn object-literal-sort-keys off and sort keys in a meaningful manner 161 | "one-variable-per-declaration": true, 162 | "only-arrow-functions": false, // there are many valid reasons to declare a function 163 | "ordered-imports": true, 164 | "prefer-array-literal": true, 165 | "prefer-const": true, 166 | "prefer-for-of": true, 167 | "prefer-method-signature": true, 168 | "prefer-object-spread": true, 169 | "prefer-template": true, 170 | "type-literal-delimiter": true, 171 | "typedef": [ 172 | true, 173 | "call-signature", 174 | //"arrow-call-signature", // fkleuver: this can lead to incredibly verbose code that could otherwise be very succinct; leave this up to the developer where it makes sense 175 | "parameter", 176 | //"arrow-parameter", // fkleuver: this can lead to incredibly verbose code that could otherwise be very succinct; leave this up to the developer where it makes sense 177 | "property-declaration", 178 | //"variable-declaration", // fkleuver: just like with "var" in C#, a variable without a typedef must be directly initialized (the no-implicit-any rule will ensure this); requiring a typedef in those cases leads to redundancy in type keywords and, ultimately, less readable code 179 | "member-variable-declaration" 180 | ], 181 | "underscore-consistent-invocation": true, 182 | "unified-signatures": true, 183 | "use-default-type-parameter": true, 184 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], // fkleuver: allowing leading underscore allows for pragmatic enforcement of compiler rule "no-unused-locals" 185 | 186 | /** 187 | * Accessibility. The following rules should be turned on to guarantee the best user 188 | * experience for keyboard and screen reader users. 189 | */ 190 | "react-a11y-anchors": true, 191 | "react-a11y-aria-unsupported-elements": true, 192 | "react-a11y-event-has-role": true, 193 | "react-a11y-image-button-has-alt": true, 194 | "react-a11y-img-has-alt": true, 195 | "react-a11y-lang": true, 196 | "react-a11y-meta": true, 197 | "react-a11y-props": true, 198 | "react-a11y-proptypes": true, 199 | "react-a11y-role": true, 200 | "react-a11y-role-has-required-aria-props": true, 201 | "react-a11y-role-supports-aria-props": true, 202 | "react-a11y-tabindex-no-positive": true, 203 | "react-a11y-titles": true, 204 | 205 | /** 206 | * Whitespace related rules. The only recommended whitespace strategy is to pick a single format and 207 | * be consistent. 208 | */ 209 | "align": [true, "parameters", "arguments", "statements"], 210 | "curly": true, 211 | "encoding": true, 212 | "eofline": true, 213 | "import-spacing": true, 214 | "indent": [true, "spaces"], 215 | "linebreak-style": true, 216 | "newline-before-return": true, 217 | "no-consecutive-blank-lines": true, 218 | "no-empty-line-after-opening-brace": false, 219 | "no-irregular-whitespace": true, 220 | "no-single-line-block-comment": true, 221 | "no-trailing-whitespace": true, 222 | "no-unnecessary-semicolons": true, 223 | "object-literal-key-quotes": [true, "consistent"], // fkleuver: either all or none, makes for more readable (and pleasant to look at) code 224 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"], 225 | "quotemark": [true, "double"], 226 | "semicolon": [true, "always"], 227 | "space-within-parens": true, 228 | "trailing-comma": [true, { "singleline": "never", "multiline": "never" }], // forcing trailing commas for multi-line 229 | // lists results in lists that are easier to reorder and version control diffs that are more clear. 230 | // Many teams like to have multiline be 'always'. There is no clear consensus on this rule but the 231 | // internal MS JavaScript coding standard does discourage it. 232 | "typedef-whitespace": false, 233 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], 234 | 235 | /** 236 | * Controversial/Configurable rules. 237 | */ 238 | "ban": false, // only enable this if you have some code pattern that you want to ban 239 | "ban-types": true, 240 | "cyclomatic-complexity": true, 241 | "deprecation": false, // deprecated APIs are sometimes unavoidable 242 | "file-header": false, // enable this rule only if you are legally required to add a file header 243 | "import-blacklist": false, // enable and configure this as you desire 244 | "interface-over-type-literal": false, // there are plenty of reasons to prefer interfaces 245 | "no-angle-bracket-type-assertion": false, // pick either type-cast format and use it consistently 246 | "no-inferred-empty-object-type": false, // if the compiler is satisfied then this is probably not an issue 247 | "no-internal-module": false, // only enable this if you are not using internal modules 248 | "no-magic-numbers": false, // by default it will find too many false positives 249 | "no-mergeable-namespace": false, // your project may require mergeable namespaces 250 | "no-namespace": false, // only enable this if you are not using modules/namespaces 251 | "no-reference": true, // in general you should use a module system and not /// reference imports 252 | "no-unexternalized-strings": false, // the VS Code team has a specific localization process that this rule enforces 253 | "object-literal-shorthand": false, // object-literal-shorthand offers an abbreviation not an abstraction 254 | "prefer-conditional-expression": false, // unnecessarily strict 255 | "prefer-switch": false, // more of a style preference 256 | "prefer-type-cast": false, // pick either type-cast format and use it consistently // fkleuver: as-cast seems to be the most popular flavor at the moment, and I suppose with a purely style preference thing such as this, it's best to just go mainstream 257 | "return-undefined": false, // this actually affect the readability of the code 258 | "space-before-function-paren": false, // turn this on if this is really your coding standard 259 | 260 | /** 261 | * Deprecated rules. The following rules are deprecated for various reasons. 262 | */ 263 | "missing-optional-annotation": false, // now supported by TypeScript compiler 264 | //"no-duplicate-case": true, // fkleuver: deprecated and replaced with no-duplicate-switch-case 265 | "no-duplicate-parameter-names": false, // now supported by TypeScript compiler 266 | "no-empty-interfaces": false, // use tslint no-empty-interface rule instead 267 | "no-missing-visibility-modifiers": false, // use tslint member-access rule instead 268 | "no-multiple-var-decl": false, // use tslint one-variable-per-declaration rule instead 269 | //"no-stateless-class": true, // fkleuver: deprecated and replaced with no-unnecessary-class 270 | "no-switch-case-fall-through": false, // now supported by TypeScript compiler 271 | //"no-var-self": true, // fkleuver: deprecated and replaced with no-this-assignment 272 | "react-tsx-curly-spacing": true, 273 | "typeof-compare": false, // the valid-typeof rule is currently superior to this version 274 | "valid-typeof": true 275 | }, 276 | "rulesDirectory": "node_modules/tslint-microsoft-contrib", 277 | "defaultSeverity": "warning" 278 | } 279 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-implicit-dependencies 2 | // tslint:disable:import-name 3 | import { AureliaPlugin, ModuleDependenciesPlugin } from "aurelia-webpack-plugin"; 4 | import { readFileSync } from "fs"; 5 | import HtmlWebpackPlugin from "html-webpack-plugin"; 6 | import MonacoWebpackPlugin from "monaco-editor-webpack-plugin"; 7 | import * as path from "path"; 8 | import * as webpack from "webpack"; 9 | 10 | const pkg = JSON.parse(readFileSync("package.json", "utf-8")); 11 | 12 | const title = "Aurelia Dynamic HTML Plugin"; 13 | interface IEnv { 14 | server?: boolean; 15 | production?: boolean; 16 | } 17 | 18 | const devBaseUrl: string = "/"; 19 | const prodBaseUrl: string = `/${pkg.name}/`; 20 | export default (env: IEnv = {}): webpack.Configuration => { 21 | const alias = { 22 | "bluebird": path.resolve(__dirname, "node_modules/bluebird/js/browser/bluebird.core") 23 | }; 24 | 25 | return { 26 | mode: "development", 27 | resolve: { 28 | extensions: [".ts", ".js"], 29 | modules: [path.resolve(__dirname, "src"), path.resolve(__dirname, "demo"), "node_modules"], 30 | alias 31 | }, 32 | entry: { 33 | app: ["aurelia-bootstrapper"], 34 | vendor: ["bluebird"] 35 | }, 36 | output: { 37 | path: path.resolve(__dirname), 38 | publicPath: env.production ? prodBaseUrl : devBaseUrl, 39 | filename: "[name].bundle.js" 40 | }, 41 | devtool: "cheap-module-eval-source-map", 42 | devServer: { 43 | historyApiFallback: true, 44 | lazy: false, 45 | open: true 46 | }, 47 | module: { 48 | rules: [ 49 | { 50 | test: /\.css$/, 51 | use: [{ loader: "style-loader" }, { loader: "css-loader" }], 52 | issuer: [{ not: [{ test: /\.html$/i }] }] 53 | }, 54 | { 55 | test: /\.css$/, 56 | use: [{ loader: "css-loader" }], 57 | issuer: [{ test: /\.html$/i }] 58 | }, 59 | { 60 | test: /\.html$/, 61 | use: [{ loader: "html-loader" }] 62 | }, 63 | { 64 | test: /\.ts$/, 65 | loader: "ts-loader", 66 | exclude: /node_modules/, 67 | options: { 68 | configFile: path.resolve(__dirname, "configs/tsconfig-demo.json") 69 | } 70 | }, 71 | { 72 | test: /[\/\\]node_modules[\/\\]bluebird[\/\\].+\.js$/, 73 | use: [{ loader: "expose-loader?Promise" }] 74 | } 75 | ] 76 | }, 77 | plugins: [ 78 | new HtmlWebpackPlugin({ 79 | template: "demo/index.ejs", 80 | metadata: { 81 | title, 82 | server: env.server, 83 | baseUrl: env.production ? prodBaseUrl : devBaseUrl 84 | } 85 | }), 86 | new AureliaPlugin(), 87 | new webpack.ProvidePlugin({ 88 | Promise: "bluebird" 89 | }), 90 | new MonacoWebpackPlugin(), 91 | new webpack.IgnorePlugin( 92 | /^((fs)|(path)|(os)|(crypto)|(source-map-support))$/, 93 | /vs(\/|\\)language(\/|\\)typescript(\/|\\)lib/ 94 | ) 95 | ] 96 | }; 97 | }; 98 | --------------------------------------------------------------------------------