Browse Source

Add back x-html

Caleb Porzio 3 years ago
parent
commit
1b7de28e0f

+ 1 - 0
packages/alpinejs/src/directives/index.js

@@ -5,6 +5,7 @@ import './x-model'
 import './x-cloak'
 import './x-cloak'
 import './x-init'
 import './x-init'
 import './x-text'
 import './x-text'
+import './x-html'
 import './x-bind'
 import './x-bind'
 import './x-data'
 import './x-data'
 import './x-show'
 import './x-show'

+ 15 - 0
packages/alpinejs/src/directives/x-html.js

@@ -0,0 +1,15 @@
+import { evaluateLater } from '../evaluator'
+import { directive } from '../directives'
+import { mutateDom } from '../mutation'
+
+directive('html', (el, { expression }, { effect, evaluateLater }) => {
+    let evaluate = evaluateLater(expression)
+
+    effect(() => {
+        evaluate(value => {
+            mutateDom(() => {
+                el.innerHTML = value
+            })
+        })
+    })
+})

+ 2 - 3
packages/alpinejs/src/directives/x-text.js

@@ -1,9 +1,8 @@
-import { evaluateLater } from '../evaluator'
 import { directive } from '../directives'
 import { directive } from '../directives'
 import { mutateDom } from '../mutation'
 import { mutateDom } from '../mutation'
 
 
-directive('text', (el, { expression }, { effect }) => {
-    let evaluate = evaluateLater(el, expression)
+directive('text', (el, { expression }, { effect, evaluateLater }) => {
+    let evaluate = evaluateLater(expression)
 
 
     effect(() => {
     effect(() => {
         evaluate(value => {
         evaluate(value => {

+ 29 - 0
packages/docs/src/en/directives/html.md

@@ -0,0 +1,29 @@
+---
+order: 7
+title: html
+---
+
+# `x-html`
+
+`x-html` sets the "innerHTML" an element to the result of a given expression.
+
+> ⚠️ Only use on trusted content and never on user-provided content. ⚠️
+> Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.
+
+Here's a basic example of using `x-html` to display a user's username.
+
+```html
+<div x-data="{ username: '<strong>calebporzio</strong>' }">
+    Username: <span x-html="username"></span>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div class="demo">
+    <div x-data="{ username: '<strong>calebporzio</strong>' }">
+        Username: <span x-html="username"></span>
+    </div>
+</div>
+<!-- END_VERBATIM -->
+
+Now the `<span>` tag's inner HTML will be set to "<strong>calebporzio</strong>".

+ 24 - 0
packages/docs/src/en/essentials/templating.md

@@ -342,3 +342,27 @@ Similar to `x-if`, `x-for` must be applied to a `<template>` tag. Internally, Al
 As you can see the new `status` variable is available in the scope of the iterated templates.
 As you can see the new `status` variable is available in the scope of the iterated templates.
 
 
 [→ Read more about `x-for`](/directives/for)
 [→ Read more about `x-for`](/directives/for)
+
+<a name="inner-html"></a>
+## Inner HTML
+
+Alpine makes it easy to control the HTML content of an element with the `x-html` directive.
+
+```html
+<div x-data="{ title: '<h1>Start Here</h1>' }">
+    <div x-html="title"></div>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div x-data="{ title: '<h1>Start Here</h1>' }" class="demo">
+    <div x-html="title"></div>
+</div>
+<!-- END_VERBATIM -->
+
+Now, Alpine will set the text content of the `<div>` with the element `<h1>Start Here</h1>`. When `title` changes, so will the contents of `<h1>`.
+
+> ⚠️ Only use on trusted content and never on user-provided content. ⚠️
+> Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.
+
+[→ Read more about `x-html`](/directives/html)

+ 0 - 45
packages/docs/src/en/upgrade-guide.md

@@ -35,7 +35,6 @@ Upgrading from Alpine V2 to V3 should be fairly painless. In many cases, NOTHING
 * [`x-spread` is now `x-bind`](#x-spread-is-now-x-bind)
 * [`x-spread` is now `x-bind`](#x-spread-is-now-x-bind)
 * [Use global lifecycle events instead of `Alpine.deferLoadingAlpine()`](#use-global-events-now)
 * [Use global lifecycle events instead of `Alpine.deferLoadingAlpine()`](#use-global-events-now)
 * [IE11 no longer supported](#no-ie-11)
 * [IE11 no longer supported](#no-ie-11)
-* [`x-html` has been removed](#no-x-html)
 
 
 <a name="el-no-longer-root"></a>
 <a name="el-no-longer-root"></a>
 ### `$el` is now always the current element
 ### `$el` is now always the current element
@@ -302,50 +301,6 @@ One of Alpine's stories for re-using functionality is abstracting Alpine directi
 
 
 Alpine will no longer officially support Internet Explorer 11. If you need support for IE11, we recommend still using Alpine V2.
 Alpine will no longer officially support Internet Explorer 11. If you need support for IE11, we recommend still using Alpine V2.
 
 
-<a name="no-x-html"></a>
-### `x-html` has been removed
-
-`x-html` was a seldom used directive in Alpine V2. In an effort to keep the API slimmed down to only valued features, V3 is removing this directive.
-
-You can reproduce this exact functionality using `x-effect` like so:
-
-```html
-<!-- 🚫 Before -->
-<div x-data="{ someHtml: '<h1>...</h1>' }">
-    <div x-html="someHtml">
-</div>
-
-<!-- ✅ After -->
-<div x-data="{ someHtml: '<h1>...</h1>' }">
-    <div x-effect="$el.innerHTML = someHtml">
-</div>
-```
-
-Or, using V3's new custom directive API, it's trivial to polyfill this directive:
-
-```html
-<!-- 🚫 Before -->
-<div x-data="{ someHtml: '<h1>...</h1>' }">
-    <div x-html="someHTML">
-</div>
-
-<!-- ✅ After -->
-<!-- The above will now work with the following script added to the page: -->
-<script>
-    document.addEventListener('alpine:init', () => {
-        Alpine.directive('html', (el, { expression }, { evaluateLater, effect }) => {
-            let getHtml = evaluateLater(expression)
-
-            effect(() => {
-                getHtml(html => {
-                    el.innerHTML = html
-                })
-            })
-        })
-    })
-</script>
-```
-
 ## Deprecated APIs
 ## Deprecated APIs
 
 
 The following 2 APIs will still work in V3, but are considered deprecated and are likely to be removed at some point in the future.
 The following 2 APIs will still work in V3, but are considered deprecated and are likely to be removed at some point in the future.

+ 28 - 0
tests/cypress/integration/directives/x-html.spec.js

@@ -0,0 +1,28 @@
+import { haveText, notHaveText, html, test } from '../../utils'
+
+test('sets html on init',
+    html`
+        <div x-data="{ foo: '<h1>hey</h1>' }">
+            <span x-html="foo"></span>
+        </div>
+    `,
+    ({ get }) => {
+        get('h1').should(haveText('hey'))
+    }
+)
+
+test('sets html on update',
+    html`
+        <div x-data="{ foo: '' }">
+            <button x-on:click="foo = '<h1>hey</h1>'">Show "bar"</button>
+
+            <span x-html="foo"></span>
+        </div>
+    `,
+    ({ get }) => {
+        get('span').should(notHaveText('hey'))
+        get('button').click()
+        get('h1').should(haveText('hey'))
+    }
+
+)