diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d490d25..0215caac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## [1.8.0] - 2022-12-7 + +* **NOTE**: This release involved some changes to toughy code (e.g. history support) so please test thoroughly and let + us know if you see any issues +* Boosted forms now will automatically push URLs into history as with links. The [response URL](https://caniuse.com/mdn-api_xmlhttprequest_responseurl) + detection API support is good enough that we feel comfortable making this the default now. + * If you do not want this behavior you can add `hx-push-url='false'` to your boosted forms +* The [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url) attribute was introduced, allowing you to replace + the current URL in history (to complement `hx-push-url`) +* Bug fix - if htmx is included in a page more than once, we do not process elements multiple times +* Bug fix - When localStorage is not available we do not attempt to save history in it +* [Bug fix](https://github.com/bigskysoftware/htmx/issues/908) - `hx-boost` respects the `enctype` attribute +* `m` is now a valid timing modifier (e.g. `hx-trigger="every 2m"`) +* `next` and `previous` are now valid extended query selector modifiers, e.g. `hx-target="next div"` will target the + next div from the current element +* Bug fix - `hx-boost` will boost anchor tags with a `_self` target +* The `load` event now properly supports event filters +* The websocket extension has had many improvements: (A huge thank you to Denis Palashevskii, our newest committer on the project!) + * Implement proper `hx-trigger` support + * Expose trigger handling API to extensions + * Implement safe message sending with sending queue + * Fix `ws-send` attributes connecting in new elements + * Fix OOB swapping of multiple elements in response +* The `HX-Location` response header now implements client-side redirects entirely within htmx +* The `HX-Reswap` response header allows you to change the swap behavior of htmx +* The new [`hx-select-oob`](/attributes/hx-select-oob) attribute selects one or more elements from a server response to swap in via an out of band swap +* The new [`hx-replace-url`](/attributes/hx-replace-url) attribute can be used to replace the current URL in the location + bar (very similar to `hx-push-url` but no new history entry is created). The corresponding `HX-Replace-Url` response header can be used as well. +* htmx now properly handles anchors in both boosted links, as well as in `hx-get`, etc. attributes + ## [1.7.0] - 2022-02-2 * The new [`hx-sync`](/attributes/hx-sync) attribute allows you to synchronize multiple element requests on a single diff --git a/README.md b/README.md index 000a48edb..fe664c7c1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ By removing these arbitrary constraints htmx completes HTML as a ## quick start ```html - + ' + + '
' + + '
' + + '') + var btn = byId("b1") + var div1 = byId("d1") + var div2 = byId("d2") + var div3 = byId("d3") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + div2.innerHTML.should.equal(""); + div3.innerHTML.should.equal(""); + }); + + it('targets a `previous` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + make('
' + + '
' + + ' ' + + '
' + + '
' + + '
') + var btn = byId("b1") + var div1 = byId("d1") + var div2 = byId("d2") + var div3 = byId("d3") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal(""); + div2.innerHTML.should.equal(""); + div3.innerHTML.should.equal("Clicked!"); + }); }) diff --git a/test/attributes/hx-trigger.js b/test/attributes/hx-trigger.js index b40d0a035..3bcf6dd50 100644 --- a/test/attributes/hx-trigger.js +++ b/test/attributes/hx-trigger.js @@ -714,5 +714,39 @@ describe("hx-trigger attribute", function(){ div.innerText.should.equal("Requests: 1"); }); + it('load event works w/ positive filters', function() + { + this.server.respondWith("GET", "/test", "Loaded!"); + var div = make('
Load Me!
'); + div.innerHTML.should.equal("Load Me!"); + this.server.respond(); + div.innerHTML.should.equal("Loaded!"); + }); + + it('load event works w/ negative filters', function() + { + this.server.respondWith("GET", "/test", "Loaded!"); + var div = make('
Load Me!
'); + div.innerHTML.should.equal("Load Me!"); + this.server.respond(); + div.innerHTML.should.equal("Load Me!"); + }); + + it('reveal event works on two elements', function() + { + this.server.respondWith("GET", "/test1", "test 1"); + this.server.respondWith("GET", "/test2", "test 2"); + var div = make('
'); + var div2 = make('
'); + div.innerHTML.should.equal(""); + div2.innerHTML.should.equal(""); + htmx.trigger(div, 'revealed') + htmx.trigger(div2, 'revealed') + this.server.respondAll(); + div.innerHTML.should.equal("test 1"); + div2.innerHTML.should.equal("test 2"); + }); + + }) diff --git a/test/core/headers.js b/test/core/headers.js index 216f0ac65..e7d1ec258 100644 --- a/test/core/headers.js +++ b/test/core/headers.js @@ -179,6 +179,14 @@ describe("Core htmx AJAX headers", function () { div2.innerHTML.should.equal("Result"); }) + it("should handle HX-Reswap", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Reswap": "innerHTML"}, "Result"]); + + var div1 = make('
'); + div1.click(); + this.server.respond(); + div1.innerHTML.should.equal("Result"); + }) it("should handle simple string HX-Trigger-After-Swap response header properly w/ outerHTML swap", function () { this.server.respondWith("GET", "/test", [200, {"HX-Trigger-After-Swap": "foo"}, ""]); @@ -209,4 +217,13 @@ describe("Core htmx AJAX headers", function () { }) + it("should change body content on HX-Location", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Location": '{"path":"/test2", "target":"#testdiv"}'}, ""]); + this.server.respondWith("GET", "/test2", [200, {}, "
Yay! Welcome
"]); + var div = make('
'); + div.click(); + this.server.respond(); + this.server.respond(); + div.innerHTML.should.equal('
Yay! Welcome
'); + }) }); diff --git a/test/ext/disable-element.js b/test/ext/disable-element.js new file mode 100644 index 000000000..cde1ad229 --- /dev/null +++ b/test/ext/disable-element.js @@ -0,0 +1,62 @@ +describe("disable-element extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('disables the triggering element during htmx request', function () { + // GIVEN: + // - A button triggering an htmx request with disable-element extension + // - The button is enabled + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}) + }); + var btn = make('') + btn.disabled.should.equal(false); + + // WHEN clicking + btn.click(); + + // THEN it's disabled + btn.disabled.should.equal(true); + + // WHEN server response has arrived + this.server.respond(); + + // THEN it's re-enabled + btn.disabled.should.equal(false); + }); + + it('disables the designated element during htmx request', function () { + // GIVEN: + // - A button triggering an htmx request with disable-element extension + // - Another button that needs to be disabled during the htmx request + // - Both buttons are enabled + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}) + }); + var btn = make('') + var btn2 = make('') + btn.disabled.should.equal(false); + btn2.disabled.should.equal(false); + + // WHEN clicking + btn.click(); + + // THEN it's not disabled, but the other one is + btn.disabled.should.equal(false); + btn2.disabled.should.equal(true); + + // WHEN server response has arrived + this.server.respond(); + + // THEN both buttons are back enabled + btn.disabled.should.equal(false); + btn2.disabled.should.equal(false); + }); + +}); \ No newline at end of file diff --git a/test/ext/hyperscript.js b/test/ext/hyperscript.js index 47e008ce9..6d49a2dae 100644 --- a/test/ext/hyperscript.js +++ b/test/ext/hyperscript.js @@ -34,7 +34,7 @@ describe("hyperscript integration", function() { htmx.trigger(btn, "htmx:load"); btn.click(); this.server.respond(); - div.innerHTML.should.equal("Response Status Error Code 404 from /test"); + div.innerHTML.startsWith("Response Status Error Code 404 from"); }); it('hyperscript in non-htmx annotated nodes is evaluated', function () { diff --git a/test/ext/loading-states.js b/test/ext/loading-states.js new file mode 100644 index 000000000..d64c2b47b --- /dev/null +++ b/test/ext/loading-states.js @@ -0,0 +1,143 @@ +describe("loading states extension", function() { + beforeEach(function () { + this.server = makeServer(); + this.clock = sinon.useFakeTimers(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + this.clock.restore(); + clearWorkArea(); + }); + + it('works on basic setup', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
'); + btn.click(); + element.style.display.should.be.equal("inline-block"); + this.server.respond(); + element.style.display.should.be.equal("none"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with custom display', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
'); + btn.click(); + element.style.display.should.be.equal("flex"); + this.server.respond(); + element.style.display.should.be.equal("none"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with classes', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
'); + btn.click(); + element.should.have.class("test"); + this.server.respond(); + element.should.not.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with classes removal', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
'); + btn.click(); + element.should.not.have.class("test"); + this.server.respond(); + element.should.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with disabling', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make(''); + var element = make(''); + var element = make(''); + var element = make('
'); + btn.click(); + element.should.have.class("test"); + this.clock.tick(1000); + element.should.not.have.class("test"); + this.server.respond(); + element.should.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with custom targets', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
'); + btn.click(); + element.should.have.class("test"); + this.server.respond(); + element.should.not.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with path filters', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var matchingRequestElement = make('
'); + var nonMatchingPathElement = make('
'); + btn.click(); + matchingRequestElement.should.have.class("test"); + nonMatchingPathElement.should.not.have.class("test"); + this.server.respond(); + matchingRequestElement.should.not.have.class("test"); + nonMatchingPathElement.should.not.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with scopes', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('
'); + var element = make('
'); + btn.getElementsByTagName("button")[0].click(); + element.should.not.have.class("test"); + this.server.respond(); + element.should.not.have.class("test"); + btn.getElementsByTagName("button")[0].innerHTML.should.equal("Clicked!"); + }); + +}); diff --git a/test/index.html b/test/index.html index ad5b8b140..861131437 100644 --- a/test/index.html +++ b/test/index.html @@ -29,6 +29,7 @@

Mocha Test Suite

[ALL] + @@ -71,6 +72,7 @@

Mocha Test Suite

+ @@ -112,6 +114,9 @@

Mocha Test Suite

+ + + @@ -126,6 +131,9 @@

Mocha Test Suite

+ + + diff --git a/test/manual/anchors/has-anchors.html b/test/manual/anchors/has-anchors.html new file mode 100644 index 000000000..54da5828e --- /dev/null +++ b/test/manual/anchors/has-anchors.html @@ -0,0 +1,19 @@ +
+ +
+

Anchor 1

+
+ +
+

Anchor 2

+
+ +
+

Anchor 3

+
+ +
+

Anchor 4

+
+ +
diff --git a/test/manual/anchors/index.html b/test/manual/anchors/index.html new file mode 100644 index 000000000..3c4426e6d --- /dev/null +++ b/test/manual/anchors/index.html @@ -0,0 +1,13 @@ + + + + Test if indicators are invisible by default + + + + +Anchor 1 + + + + \ No newline at end of file diff --git a/test/manual/history_regression3/1.html b/test/manual/history_regression3/1.html new file mode 100644 index 000000000..4738794a2 --- /dev/null +++ b/test/manual/history_regression3/1.html @@ -0,0 +1,16 @@ + + + + + + History - 1 + + +

History Test

+1 +2 +3 +4 +

Page 1

+ + diff --git a/test/manual/history_regression3/2.html b/test/manual/history_regression3/2.html new file mode 100644 index 000000000..b1c5b2fa8 --- /dev/null +++ b/test/manual/history_regression3/2.html @@ -0,0 +1,16 @@ + + + + + + History - 2 + + +

History Test

+1 +2 +3 +4 +

Page 2

+ + diff --git a/test/manual/history_regression3/3.html b/test/manual/history_regression3/3.html new file mode 100644 index 000000000..e5ae872eb --- /dev/null +++ b/test/manual/history_regression3/3.html @@ -0,0 +1,16 @@ + + + + + + History - 3 + + +

History Test

+1 +2 +3 +4 +

Page 3

+ + diff --git a/test/manual/history_regression3/4.html b/test/manual/history_regression3/4.html new file mode 100644 index 000000000..50f594c9f --- /dev/null +++ b/test/manual/history_regression3/4.html @@ -0,0 +1,16 @@ + + + + + + History - 4 + + +

History Test

+1 +2 +3 +4 +

Page 4

+ + diff --git a/test/manual/history_regression3/index.html b/test/manual/history_regression3/index.html new file mode 100644 index 000000000..ada84eaf8 --- /dev/null +++ b/test/manual/history_regression3/index.html @@ -0,0 +1,19 @@ + + + + + History - Index + + + + +

History Test

+1 +2 +3 +4 +

Index

+ + diff --git a/test/manual/index.html b/test/manual/index.html index e7f1941dc..0a05c4ae5 100644 --- a/test/manual/index.html +++ b/test/manual/index.html @@ -30,6 +30,7 @@

Functionality

  • History Refresh Test
  • Restored Test
  • Navigate, Refresh, Back Regression
  • +
  • Anchors
  • diff --git a/test/realtime/static/stylesheet.css b/test/realtime/static/stylesheet.css index 0bc44014a..402d9fe56 100644 --- a/test/realtime/static/stylesheet.css +++ b/test/realtime/static/stylesheet.css @@ -136,6 +136,10 @@ body { padding:8px!important; } +.container.elastic { + height:auto!important; +} + /*************************** * TAB STYLES ***************************/ diff --git a/test/realtime/static/ws-echo-ext.html b/test/realtime/static/ws-echo-ext.html index 2e1b39741..e0b573fd5 100644 --- a/test/realtime/static/ws-echo-ext.html +++ b/test/realtime/static/ws-echo-ext.html @@ -23,7 +23,7 @@

    Example HTML

    </div> -
    +

    Send a Message

    diff --git a/test/realtime/static/ws-echo.html b/test/realtime/static/ws-echo.html index 4afcebf77..9564587e1 100644 --- a/test/realtime/static/ws-echo.html +++ b/test/realtime/static/ws-echo.html @@ -23,7 +23,7 @@

    Example HTML

    </div> -
    +

    Send a Message

    diff --git a/www/attributes/hx-boost.md b/www/attributes/hx-boost.md index 21a3976ed..b36b4d0bc 100644 --- a/www/attributes/hx-boost.md +++ b/www/attributes/hx-boost.md @@ -27,6 +27,18 @@ Here is an example of some boosted links: Go To Page 2
    ``` +These links will issue an ajax `GET` request to the respective URLs and replace the body's inner content with it. + +Here is an example of a boosted form: + +```html + + + + +``` +This form will issue an ajax `POST` to the given URL and replace the body's inner content with it. + ### Notes diff --git a/www/attributes/hx-push-url.md b/www/attributes/hx-push-url.md index 6bc052a77..c51968004 100644 --- a/www/attributes/hx-push-url.md +++ b/www/attributes/hx-push-url.md @@ -39,5 +39,5 @@ This will push the URL `/account/home' into the location history. ### Notes * `hx-push-url` is inherited and can be placed on a parent element -* The [`HX-Push` response header](/headers/hx-push) has similar behavior and can override this attribute. +* The [`HX-Push-Url` response header](/headers/hx-push) has similar behavior and can override this attribute. * The [`hx-history-elt` attribute](/attributes/hx-history-elt) allows changing which element is saved in the history cache. diff --git a/www/attributes/hx-replace-url.md b/www/attributes/hx-replace-url.md new file mode 100644 index 000000000..8cbdbbb1c --- /dev/null +++ b/www/attributes/hx-replace-url.md @@ -0,0 +1,43 @@ +--- +layout: layout.njk +title: htmx - hx-replace-url +--- + +## `hx-replace-url` + +The `hx-replace-url` attribute allows you to replace the current url of the browser [location history](https://developer.mozilla.org/en-US/docs/Web/API/History_API). + +The possible values of this attribute are: + +1. `true`, which replaces the fetched URL in the browser navigation bar. +2. `false`, which disables replacing the fetched URL if it would otherwise be replaced due to inheritance +3. A URL to be replaced into the location bar. + This may be relative or absolute, as per [`history.replaceState()`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState). + +Here is an example: + +```html +
    + Go to My Account +
    +``` + +This will cause htmx to snapshot the current DOM to `localStorage` and replace the URL `/account' in the browser location bar. + +Another example: + +```html +
    + Go to My Account +
    +``` + +This will replace the URL `/account/home' in the browser location bar. + +### Notes + +* `hx-replace-url` is inherited and can be placed on a parent element +* The [`HX-Replace` response header](/headers/hx-replace) has similar behavior and can override this attribute. +* The [`hx-history-elt` attribute](/attributes/hx-history-elt) allows changing which element is saved in the history cache. +* The [`hx-push-url` attribute](/attributes/hx-push-url) is a similar and more commonly used attribute, which creates a + new history entry rather than replacing the current one. diff --git a/www/attributes/hx-select-oob.md b/www/attributes/hx-select-oob.md new file mode 100644 index 000000000..03de42d9d --- /dev/null +++ b/www/attributes/hx-select-oob.md @@ -0,0 +1,32 @@ +--- +layout: layout.njk +title: htmx - hx-select-oob +--- + +## `hx-select-oob` + +The `hx-select-oob` attribute allows you to select content from a response to be swapped in via an out-of-band swap. +The value of this attribute is comma separated list of elements to be swapped out of band. This attribute is almost +always paired with [hx-select](/attributes/hx-select). + +Here is an example that selects a subset of the response content: + +```html +
    +
    + +
    +``` + +This button will issue a `GET` to `/info` and then select the element with the id `info-detail`, +which will replace the entire button in the DOM, and, in addition, pick out an element with the id `alert` +in the response and swap it in for div in the DOM with the same ID. + +### Notes + +* `hx-select` is inherited and can be placed on a parent element diff --git a/www/docs.md b/www/docs.md index 665c06aa1..3d6659c4a 100644 --- a/www/docs.md +++ b/www/docs.md @@ -112,7 +112,7 @@ The fastest way to get going with htmx is to load it via a CDN. You can simply a and get going: ```html - + ``` While the CDN approach is extremely simple, you may want to consider [not using CDNs in production](https://blog.wesleyac.com/posts/why-not-javascript-cdn). @@ -522,6 +522,9 @@ Note that out of band elements must be in the top level of the response, and not If you want to select a subset of the response HTML to swap into the target, you can use the [hx-select](/attributes/hx-select) attribute, which takes a CSS selector and selects the matching elements from the response. +You can also pick out pieces of content for an out-of-band swap by using the [hx-select-oob](/attributes/hx-select-oob) +attribute, which takes a list of element IDs to pick out and swap. + #### Preserving Content During A Swap If there is content that you wish to be preserved across swaps (e.g. a video player that you wish to remain playing @@ -829,6 +832,7 @@ htmx supports some htmx-specific response headers: * `HX-Push` - pushes a new URL into the browser’s address bar * `HX-Redirect` - triggers a client-side redirect to a new location +* `HX-Location` - triggers a client-side redirect to a new location that acts as a swap * `HX-Refresh` - if set to "true" the client side will do a full refresh of the page * `HX-Trigger` - triggers client side events * `HX-Trigger-After-Swap` - triggers client side events after the swap step diff --git a/www/extensions.md b/www/extensions.md index e0a919f44..58c7fe97b 100644 --- a/www/extensions.md +++ b/www/extensions.md @@ -61,6 +61,7 @@ against `htmx` in each distribution | [`class-tools`](/extensions/class-tools) | an extension for manipulating timed addition and removal of classes on HTML elements | [`client-side-templates`](/extensions/client-side-templates) | support for client side template processing of JSON responses | [`debug`](/extensions/debug) | an extension for debugging of a particular element using htmx +| [`disable-element`](/extensions/disable-element) | an extension for disabling an element during an htmx request | [`event-header`](/extensions/event-header) | includes a JSON serialized version of the triggering event, if any | [`include-vals`](/extensions/include-vals) | allows you to include additional values in a request | [`json-enc`](/extensions/json-enc) | use JSON encoding in the body of requests, rather than the default `x-www-form-urlencoded` diff --git a/www/extensions/disable-element.md b/www/extensions/disable-element.md new file mode 100644 index 000000000..fb1c19bef --- /dev/null +++ b/www/extensions/disable-element.md @@ -0,0 +1,25 @@ +--- +layout: layout.njk +title: htmx - high power tools for html +--- + +## The `disable-element` Extension + +This extension disables an element during an htmx request, when configured on the element triggering the request. + +### Usage + +#### Nominal case: disabling the element triggering the request +```html + +``` + +#### Disabling another element +```html + + +``` + +### Source + + diff --git a/www/extensions/loading-states.md b/www/extensions/loading-states.md index 9bfda0233..fde267211 100644 --- a/www/extensions/loading-states.md +++ b/www/extensions/loading-states.md @@ -60,6 +60,14 @@ Add the following class to your stylesheet to make sure elements are hidden by d ``` +- `data-loading-aria-busy` + + Add [`aria-busy="true"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-busy) attribute to the element for the duration of the request + + ```html + + ``` + - `data-loading-delay` Some actions may update quickly and showing a loading state in these cases may be more of a distraction. This attribute ensures that the loading state changes are applied only after 200ms if the request is not finished. The default delay can be modified through the attribute value and expressed in milliseconds: diff --git a/www/extensions/server-sent-events.md b/www/extensions/server-sent-events.md index 4c6ee23ea..276e83c9c 100644 --- a/www/extensions/server-sent-events.md +++ b/www/extensions/server-sent-events.md @@ -108,7 +108,7 @@ Previous versions of htmx used a built-in tag `hx-sse` to implement Server Sent | Old Attribute | New Attribute | Comments | |--------------------------------|--------------------------|------------------| -| `hx-sse=""` | `hs-ext="sse"` | Use the `hx-ext="sse"` attribute to install the SSE extension into any HTML element. | +| `hx-sse=""` | `hx-ext="sse"` | Use the `hx-ext="sse"` attribute to install the SSE extension into any HTML element. | | `hx-sse="connect:"` | `sse-connect=""` | Add a new attribute `sse-connect` to the tag that specifies the URL of the Event Stream. This attribute must be in the same tag as the `hx-ext` attribute. | | `hx-sse="swap:"` | `sse-swap=""` | Add a new attribute `sse-swap` to any elements that will be swapped in via the SSE extension. This attribute must be placed **on** or **inside of** the tag containing the `hx-ext` attribute. | | `hx-trigger="sse:"` | NO CHANGE | any `hx-trigger` attributes do not need to change. The extension will identify these attributes and add listeners for any events prefixed with `sse:` | diff --git a/www/extensions/web-sockets.md b/www/extensions/web-sockets.md index 7351eb16d..80746eab5 100644 --- a/www/extensions/web-sockets.md +++ b/www/extensions/web-sockets.md @@ -18,10 +18,11 @@ of the event specified by [`hx-trigger`]) ```html
    +
    ...
    -
    +
    @@ -32,6 +33,24 @@ of the event specified by [`hx-trigger`]) The example above establishes a WebSocket to the `/chatroom` end point. Content that is sent down from the websocket will be parsed as HTML and swapped in by the `id` property, using the same logic as [Out of Band Swaps](/attributes/hx-swap-oob). +As such, if you want to change the swapping method (e.g., append content at the end of an element or delegate swapping to an extension), +you need to specify that in the message body, sent by the server. + +```html + +
    + ... +
    + +
    + New message received +
    + +
    + .... +
    +``` + ### Sending Messages to a WebSocket In the example above, the form uses the `ws-send` attribute to indicate that when it is submitted, the form values should be **serialized as JSON** @@ -54,6 +73,7 @@ htmx.config.wsReconnectDelay = function(retryCount) { } ``` +The extension also implements a simple queuing mechanism that keeps messages in memory when the socket is not in `OPEN` state and sends them once the connection is restored. ### Testing with the Demo Server Htmx includes a demo WebSockets server written in Go that will help you to see WebSockets in action, and begin bootstrapping your own WebSockets code. It is located in the /test/servers/ws folder of the htmx distribution. Look at /test/servers/ws/README.md for instructions on running and using the test server. @@ -64,9 +84,9 @@ Previous versions of htmx used a built-in tag `hx-ws` to implement WebSockets. | Old Attribute | New Attribute | Comments | |-------------------------|----------------------|-------------------| -| `hx-ws=""` | `hs-ext="ws"` | Use the `hx-ext="ws"` attribute to install the WebSockets extension into any HTML element. | +| `hx-ws=""` | `hx-ext="ws"` | Use the `hx-ext="ws"` attribute to install the WebSockets extension into any HTML element. | | `hx-ws="connect:"` | `ws-connect=""` | Add a new attribute `ws-connect` to the tag that defines the extension to specify the URL of the WebSockets server you're using. | -| `hs-ws="send"` | `ws-send=""` | Add a new attribute `ws-send` to mark any child forms that should send data to your WebSocket server | +| `hx-ws="send"` | `ws-send=""` | Add a new attribute `ws-send` to mark any child forms that should send data to your WebSocket server | ### Source diff --git a/www/headers/hx-location.md b/www/headers/hx-location.md new file mode 100644 index 000000000..a9310b0e7 --- /dev/null +++ b/www/headers/hx-location.md @@ -0,0 +1,33 @@ +--- +layout: layout.njk +title: htmx - HX-Location Response Headers +--- + +## `HX-Location` Response Header + + +This response header can be used to trigger a client side redirection without reloading the whole page. Instead of changing the page's location it will act like following a [`hx-boost` link](/attributes/hx-boost), creating a new history entry, issuing an ajax request to the value of the header and pushing the path into history. + +A sample response would be: + +``` +HX-Location: /test +``` + +Which would push the client to test as if the user had clicked on `` + + +If you want to redirect to a specific target on the page rather than the default of document.body, you can pass more details along with the event, by using JSON for the value of the header: + +``` +HX-Location: {"path":"/test2", "target":"#testdiv"} +``` +Path is required and is url to load the response from. The rest of the data mirrors the [`ajax` api](/api#ajax) context, which is: + +* `source` - the source element of the request +* `event` - an event that "triggered" the request +* `handler` - a callback that will handle the response HTML +* `target` - the target to swap the response into +* `swap` - how the response will be swapped in relative to the target +* `values` - values to submit with the request +* `headers` - headers to submit with the request diff --git a/www/headers/hx-push-url.md b/www/headers/hx-push-url.md new file mode 100644 index 000000000..1e0a451c6 --- /dev/null +++ b/www/headers/hx-push-url.md @@ -0,0 +1,18 @@ +--- +layout: layout.njk +title: htmx - HX-Push Response Headers +--- + +## `HX-Push-Url` Response Header + +The `HX-Push-Url` header allows you to push a URL into the browser [location history](https://developer.mozilla.org/en-US/docs/Web/API/History_API). +This creates a new history entry, allowing navigation with the browser’s back and forward buttons. +This is similar to the [`hx-push-url` attribute](/attributes/hx-push-url). + +If present, this header overrides any behavior defined with attributes. + +The possible values for this header are: + +1. A URL to be pushed into the location bar. + This may be relative or absolute, as per [`history.pushState()`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState). +2. `false`, which prevents the browser’s history from being updated. diff --git a/www/headers/hx-push.md b/www/headers/hx-push.md index b64e8bcaa..bd8ad2b69 100644 --- a/www/headers/hx-push.md +++ b/www/headers/hx-push.md @@ -1,18 +1,8 @@ --- layout: layout.njk -title: htmx - HX-Trigger Response Headers +title: htmx - HX-Push Response Headers --- ## `HX-Push` Response Header -The `HX-Push` header allows you to push a URL into the browser [location history](https://developer.mozilla.org/en-US/docs/Web/API/History_API). -This creates a new history entry, allowing navigation with the browser’s back and forward buttons. -This is similar to the [`hx-push-url` attribute](/attributes/hx-push-url). - -If present, this header overrides any behavior defined with attributes. - -The possible values for this header are: - -1. A URL to be pushed into the location bar. - This may be relative or absolute, as per [`history.pushState()`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState). -2. `false`, which prevents the browser’s history from being updated. +The `HX-Push` header has been replaced by the [`HX-Push-Url`](/headers/hx-push-url) \ No newline at end of file diff --git a/www/index.md b/www/index.md index a62cf4d44..9a626f3bb 100644 --- a/www/index.md +++ b/www/index.md @@ -33,7 +33,7 @@ By removing these arbitrary constraints, htmx completes HTML as a [hypertext](ht ## quick start ```html - +
    '); + var btn = byId('b1'); + btn.click(); + this.server.respond(); + div.innerHTML.should.equal("Boosted"); + }) + + it('handles basic form get properly', function () { + this.server.respondWith("GET", "/test", "Boosted"); + var div = make('
    '); + var btn = byId('b1'); + btn.click(); + this.server.respond(); + div.innerHTML.should.equal("Boosted"); + }) + + it('handles basic form with no explicit method property', function () { + this.server.respondWith("GET", "/test", "Boosted"); + var div = make('
    '); + var btn = byId('b1'); + btn.click(); + this.server.respond(); + div.innerHTML.should.equal("Boosted"); + }) + + it('handles basic anchor properly w/ data-* prefix', function () { + this.server.respondWith("GET", "/test", "Boosted"); + var div = make(''); + var a = byId('a1'); + a.click(); + this.server.respond(); + div.innerHTML.should.equal("Boosted"); + }) + + + it('overriding default swap style does not effect boosting', function () { + htmx.config.defaultSwapStyle = "afterend"; + try { + this.server.respondWith("GET", "/test", "Boosted"); + var a = make('Foo'); + a.click(); + this.server.respond(); + a.innerHTML.should.equal("Boosted"); + } finally { + htmx.config.defaultSwapStyle = "innerHTML"; + } + }) + + it('anchors w/ explicit targets are not boosted', function () { + var a = make('Foo'); + var internalData = htmx._("getInternalData")(a); + should.equal(undefined, internalData.boosted); + }) + + it('includes an HX-Boosted Header', function() + { + this.server.respondWith("GET", "/test", function(xhr){ + should.equal(xhr.requestHeaders['HX-Boosted'], "true"); + xhr.respond(200, {}, "Boosted!"); + }); + + var btn = make('Click Me!') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Boosted!"); + }); + +}); + diff --git a/www/test/1.8.0/test/attributes/hx-delete.js b/www/test/1.8.0/test/attributes/hx-delete.js new file mode 100644 index 000000000..22b0f7607 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-delete.js @@ -0,0 +1,34 @@ +describe("hx-delete attribute", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('issues a DELETE request', function() + { + this.server.respondWith("DELETE", "/test", function(xhr){ + xhr.respond(200, {}, "Deleted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Deleted!"); + }); + + it('issues a DELETE request w/ data-* prefix', function() + { + this.server.respondWith("DELETE", "/test", function(xhr){ + xhr.respond(200, {}, "Deleted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Deleted!"); + }); +}) diff --git a/www/test/1.8.0/test/attributes/hx-disinherit.js b/www/test/1.8.0/test/attributes/hx-disinherit.js new file mode 100644 index 000000000..6482f65e1 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-disinherit.js @@ -0,0 +1,142 @@ +describe("hx-disinherit attribute", function() { + + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('basic inheritance sanity-check', function () { + var response_inner = '
    Hello world
    ' + var response = '
    ' + response_inner + '
    ' + this.server.respondWith("GET", "/test", response); + + var div = make('
    ') + var btn = byId("bx1"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal(response_inner); + }) + + + it('disinherit exclude single attribute', function () { + var response_inner = '
    Hello world
    ' + var response = '
    ' + response_inner + '
    ' + this.server.respondWith("GET", "/test", response); + + var div = make('
    ') + var btn = byId("bx1"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal(response + 'Click Me!'); + }); + + it('disinherit exclude multiple attributes', function () { + var response_inner = '
    Hello world
    ' + var response = '
    ' + response_inner + '
    ' + this.server.respondWith("GET", "/test", response); + + var div = make('
    ' + + ' ' + + '
    ') + var btn = byId("bx1"); + btn.click(); + this.server.respond(); + console.log(btn.innerHTML); + console.log(response); + btn.innerHTML.should.equal('' + response + ''); + }); + + it('disinherit exclude all attributes', function () { + var response_inner = '
    Hello world
    ' + var response = '
    ' + response_inner + '
    ' + this.server.respondWith("GET", "/test", response); + var div = make('
    ' + + ' ' + + '
    ') + var btn = byId("bx1"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal(response); + }); + + it('same-element inheritance disable', function () { + var response_inner = '
    Hello world
    ' + var response = '
    ' + response_inner + '
    ' + this.server.respondWith("GET", "/test", response); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal(response_inner); + }); + + it('same-element inheritance disable with child nodes', function () { + var response_inner = '
    Hello world
    ' + var response = '
    ' + response_inner + '
    ' + this.server.respondWith("GET", "/test", response); + this.server.respondWith("GET", "/test2", 'unique-snowflake'); + + var div = make('
    ') + var btn = byId("bx1"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal('
    unique-snowflake
    '); + var count = (div.parentElement.innerHTML.match(/snowflake/g) || []).length; + count.should.equal(2); // hx-select of parent div and newly loaded inner content + }); + + it('boosted element hx-disinherit sanity check', function () { + try { + var request; + var handler = htmx.on("htmx:beforeRequest", function (evt) { + request = evt; + }); + var div = make(''); + var link = byId("a1"); + link.click(); + should.equal(request.detail.requestConfig.path, '/test'); + should.equal(request.detail.elt["htmx-internal-data"].boosted, true); + } finally { + htmx.off("htmx:beforeRequest", handler); + } + }); + + it('boosted element inheritance manual unset', function () { + try { + var request; + var handler = htmx.on("htmx:beforeRequest", function (evt) { + request = evt; + }); + var div = make(''); + var link = byId("a1"); + should.equal(link["htmx-internal-data"].boosted, undefined); + } finally { + htmx.off("htmx:beforeRequest", handler); + } + }); + + it('nested htmx-node with boosting parent', function () { + try { + var request; + var handler = htmx.on("htmx:beforeRequest", function (evt) { + request = evt; + }); + var div = make(''); + var link = byId("a1"); + link.click(); + should.equal(request.detail.requestConfig.path, '/test2'); + should.equal(request.detail.elt["htmx-internal-data"].boosted, undefined); + should.equal(request.detail.target.id, "a1"); + } finally { + htmx.off("htmx:beforeRequest", handler); + } + }); + +}); + diff --git a/www/test/1.8.0/test/attributes/hx-ext.js b/www/test/1.8.0/test/attributes/hx-ext.js new file mode 100644 index 000000000..740b9bd8b --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-ext.js @@ -0,0 +1,138 @@ +describe("hx-ext attribute", function() { + + var ext1Calls, ext2Calls, ext3Calls, ext4Calls; + + beforeEach(function () { + ext1Calls = ext2Calls = ext3Calls = ext4Calls = 0; + this.server = makeServer(); + clearWorkArea(); + htmx.defineExtension("ext-1", { + onEvent : function(name, evt) { + if(name === "htmx:afterRequest"){ + ext1Calls++; + } + } + }); + htmx.defineExtension("ext-2", { + onEvent : function(name, evt) { + if(name === "htmx:afterRequest"){ + ext2Calls++; + } + } + }); + htmx.defineExtension("ext-3", { + onEvent : function(name, evt) { + if(name === "htmx:afterRequest"){ + ext3Calls++; + } + } + }); + htmx.defineExtension("ext-4", { + onEvent : function(name, evt) { + if(name === "namespace:example"){ + ext4Calls++; + } + } + }); + }); + + afterEach(function () { + this.server.restore(); + clearWorkArea(); + htmx.removeExtension("ext-1"); + htmx.removeExtension("ext-2"); + htmx.removeExtension("ext-3"); + }); + + it('A simple extension is invoked properly', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make('') + btn.click(); + this.server.respond(); + ext1Calls.should.equal(1); + ext2Calls.should.equal(0); + ext3Calls.should.equal(0); + }); + + it('Extensions are merged properly', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + make('
    ' + + '
    ') + var btn1 = byId("btn-1"); + var btn2 = byId("btn-2"); + + btn1.click(); + this.server.respond(); + ext1Calls.should.equal(1); + ext2Calls.should.equal(1); + ext3Calls.should.equal(0); + + btn2.click(); + this.server.respond(); + ext1Calls.should.equal(2); + ext2Calls.should.equal(1); + ext3Calls.should.equal(1); + }); + + it('supports comma separated lists', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + make('
    ') + var btn1 = byId("btn-1"); + var btn2 = byId("btn-2"); + + btn1.click(); + this.server.respond(); + ext1Calls.should.equal(1); + ext2Calls.should.equal(1); + ext3Calls.should.equal(1); + }); + + it('A simple extension is invoked properly w/ data-* prefix', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make('') + btn.click(); + this.server.respond(); + ext1Calls.should.equal(1); + ext2Calls.should.equal(0); + ext3Calls.should.equal(0); + }); + + it('A simple extension is invoked properly when an HX-Trigger event w/ a namespace fires', function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger":"namespace:example"}, ""]); + var btn = make('') + btn.click(); + this.server.respond(); + ext1Calls.should.equal(0); + ext2Calls.should.equal(0); + ext3Calls.should.equal(0); + ext4Calls.should.equal(1); + + }); + + it('Extensions are ignored properly', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + make('
    ' + + '
    ') + + var btn1 = byId("btn-AA"); + var btn2 = byId("btn-BB"); + + btn1.click(); + this.server.respond(); + ext1Calls.should.equal(1); + ext2Calls.should.equal(1); + ext3Calls.should.equal(0); + + btn2.click(); + this.server.respond(); + ext1Calls.should.equal(1); + ext2Calls.should.equal(2); + ext3Calls.should.equal(0); + }) + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-get.js b/www/test/1.8.0/test/attributes/hx-get.js new file mode 100644 index 000000000..7326c39ef --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-get.js @@ -0,0 +1,76 @@ +describe("hx-get attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('issues a GET request on click and swaps content', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('GET does not include surrounding data by default', function () { + this.server.respondWith("GET", "/test", function (xhr) { + should.equal(getParameters(xhr)["i1"], undefined); + xhr.respond(200, {}, "Clicked!"); + }); + make('
    ') + var btn = byId("b1"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('GET on form includes its own data by default', function () { + this.server.respondWith("GET", /\/test.*/, function (xhr) { + getParameters(xhr)["i1"].should.equal("value"); + xhr.respond(200, {}, "Clicked!"); + }); + var form = make('
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('GET on form with existing parameters works properly', function () { + this.server.respondWith("GET", /\/test.*/, function (xhr) { + getParameters(xhr)["foo"].should.equal("bar"); + getParameters(xhr)["i1"].should.equal("value"); + xhr.respond(200, {}, "Clicked!"); + }); + var form = make('
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('GET on form with anchor works properly', function () { + this.server.respondWith("GET", /\/test.*/, function (xhr) { + getParameters(xhr)["foo"].should.equal("bar"); + getParameters(xhr)["i1"].should.equal("value"); + xhr.respond(200, {}, "Clicked!"); + }); + var form = make('
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + + it('issues a GET request on click and swaps content w/ data-* prefix', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }); +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-headers.js b/www/test/1.8.0/test/attributes/hx-headers.js new file mode 100644 index 000000000..0333533ca --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-headers.js @@ -0,0 +1,164 @@ +describe("hx-headers attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('basic hx-headers works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('basic hx-headers works with braces', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('multiple hx-headers works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['v1'].should.equal("test"); + xhr.requestHeaders['v2'].should.equal("42"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers can be on parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make("
    "); + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers can override parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + make("
    "); + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers overrides inputs', function () { + this.server.respondWith("POST", "/include", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('basic hx-headers javascript: works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers works with braces', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('multiple hx-headers works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['v1'].should.equal("test"); + xhr.requestHeaders['v2'].should.equal("42"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers can be on parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ') + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers can override parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ') + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-headers overrides inputs', function () { + this.server.respondWith("POST", "/include", function (xhr) { + + xhr.requestHeaders['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-include.js b/www/test/1.8.0/test/attributes/hx-include.js new file mode 100644 index 000000000..0a7d10525 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-include.js @@ -0,0 +1,228 @@ +describe("hx-include attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('By default an input includes itself', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('non-GET includes closest form', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("d1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('non-GET includes closest form and overrides values included that exist outside the form', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ' + + '' + + '
    ' + + '') + var input = byId("d1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('GET does not include closest form by default', function () { + this.server.respondWith("GET", "/include", function (xhr) { + var params = getParameters(xhr); + should.equal(params['i1'], undefined); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("d1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('Single input not included twice when in form', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('Two inputs are included twice when they have the same name', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.deep.equal(["test", "test2"]); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ' + + '' + + '' + + '
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('Two inputs are included twice when in form when they have the same name', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.deep.equal(["test", "test2"]); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ' + + '' + + '' + + '
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('Input not included twice when it explicitly refers to parent form', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ' + + '' + + '
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('Input can be referred to externally', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make(''); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('Two inputs can be referred to externally', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + params['i2'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make(''); + make(''); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('A form can be referred to externally', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + params['i2'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ' + + '' + + '' + + '
    '); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('By default an input includes itself w/ data-* prefix', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('If the element is not includeable, its descendant inputs are included', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + params['i2'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    '); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }) + + it('The `closest` modifier can be used in the hx-include selector', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + params['i2'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    '+ + '
    '); + var btn = byId('btn') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }) + + it('The `this` modifier can be used in the hx-include selector', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + params['i2'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    '+ + '
    '); + var btn = byId('btn') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }) + +}); diff --git a/www/test/1.8.0/test/attributes/hx-indicator.js b/www/test/1.8.0/test/attributes/hx-indicator.js new file mode 100644 index 000000000..5e71ca432 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-indicator.js @@ -0,0 +1,92 @@ +describe("hx-indicator attribute", function(){ + beforeEach(function() { + this.server = sinon.fakeServer.create(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('Indicator classes are properly put on element with no explicit indicator', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + btn.click(); + btn.classList.contains("htmx-request").should.equal(true); + this.server.respond(); + btn.classList.contains("htmx-request").should.equal(false); + }); + + it('Indicator classes are properly put on element with explicit indicator', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + var a1 = make('') + var a2 = make('') + btn.click(); + btn.classList.contains("htmx-request").should.equal(false); + a1.classList.contains("htmx-request").should.equal(true); + a2.classList.contains("htmx-request").should.equal(true); + this.server.respond(); + btn.classList.contains("htmx-request").should.equal(false); + a1.classList.contains("htmx-request").should.equal(false); + a2.classList.contains("htmx-request").should.equal(false); + }); + + it('Indicator classes are properly put on element with explicit indicator w/ data-* prefix', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + var a1 = make('') + var a2 = make('') + btn.click(); + btn.classList.contains("htmx-request").should.equal(false); + a1.classList.contains("htmx-request").should.equal(true); + a2.classList.contains("htmx-request").should.equal(true); + this.server.respond(); + btn.classList.contains("htmx-request").should.equal(false); + a1.classList.contains("htmx-request").should.equal(false); + a2.classList.contains("htmx-request").should.equal(false); + }); + + it('allows closest syntax in hx-indicator', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make('
    ') + var btn = byId("b1"); + btn.click(); + btn.classList.contains("htmx-request").should.equal(false); + div.classList.contains("htmx-request").should.equal(true); + this.server.respond(); + btn.classList.contains("htmx-request").should.equal(false); + div.classList.contains("htmx-request").should.equal(false); + }); + + it('is removed when initiating element is removed from the DOM', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var indicator = make('
    Indicator
    ') + var div = make('
    ') + var btn = byId("b1"); + btn.click(); + indicator.classList.contains("htmx-request").should.equal(true); + this.server.respond(); + indicator.classList.contains("htmx-request").should.equal(false); + }); + + it('allows this syntax in hx-indicator', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make('
    ') + var btn = byId("b1"); + btn.click(); + btn.classList.contains("htmx-request").should.equal(false); + div.classList.contains("htmx-request").should.equal(true); + this.server.respond(); + btn.classList.contains("htmx-request").should.equal(false); + div.classList.contains("htmx-request").should.equal(false); + }); + + +}) diff --git a/www/test/1.8.0/test/attributes/hx-params.js b/www/test/1.8.0/test/attributes/hx-params.js new file mode 100644 index 000000000..b2b19ca25 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-params.js @@ -0,0 +1,101 @@ +describe("hx-params attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('none excludes all params', function () { + this.server.respondWith("POST", "/params", function (xhr) { + var params = getParameters(xhr); + should.equal(params['i1'], undefined); + should.equal(params['i2'], undefined); + should.equal(params['i3'], undefined); + xhr.respond(200, {}, "Clicked!") + }); + var form = make('
    ' + + '' + + '' + + '' + + '
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('"*" includes all params', function () { + this.server.respondWith("POST", "/params", function (xhr) { + var params = getParameters(xhr); + should.equal(params['i1'], "test"); + should.equal(params['i2'], "test"); + should.equal(params['i3'], "test"); + xhr.respond(200, {}, "Clicked!") + }); + var form = make('
    ' + + '' + + '' + + '' + + '
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('named includes works', function () { + this.server.respondWith("POST", "/params", function (xhr) { + var params = getParameters(xhr); + should.equal(params['i1'], "test"); + should.equal(params['i2'], undefined); + should.equal(params['i3'], "test"); + xhr.respond(200, {}, "Clicked!") + }); + var form = make('
    ' + + '' + + '' + + '' + + '
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('named exclude works', function () { + this.server.respondWith("POST", "/params", function (xhr) { + var params = getParameters(xhr); + should.equal(params['i1'], undefined); + should.equal(params['i2'], "test"); + should.equal(params['i3'], undefined); + xhr.respond(200, {}, "Clicked!") + }); + var form = make('
    ' + + '' + + '' + + '' + + '
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('named exclude works w/ data-* prefix', function () { + this.server.respondWith("POST", "/params", function (xhr) { + var params = getParameters(xhr); + should.equal(params['i1'], undefined); + should.equal(params['i2'], "test"); + should.equal(params['i3'], undefined); + xhr.respond(200, {}, "Clicked!") + }); + var form = make('
    ' + + '' + + '' + + '' + + '
    '); + form.click(); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-patch.js b/www/test/1.8.0/test/attributes/hx-patch.js new file mode 100644 index 000000000..a3c519847 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-patch.js @@ -0,0 +1,34 @@ +describe("hx-patch attribute", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('issues a PATCH request', function() + { + this.server.respondWith("PATCH", "/test", function(xhr){ + xhr.respond(200, {}, "Patched!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Patched!"); + }); + + it('issues a PATCH request w/ data-* prefix', function() + { + this.server.respondWith("PATCH", "/test", function(xhr){ + xhr.respond(200, {}, "Patched!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Patched!"); + }); +}) diff --git a/www/test/1.8.0/test/attributes/hx-post.js b/www/test/1.8.0/test/attributes/hx-post.js new file mode 100644 index 000000000..6734e30d2 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-post.js @@ -0,0 +1,36 @@ +describe("hx-post attribute", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('issues a POST request with proper headers', function() + { + this.server.respondWith("POST", "/test", function(xhr){ + should.equal(xhr.requestHeaders['X-HTTP-Method-Override'], undefined); + xhr.respond(200, {}, "Posted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Posted!"); + }); + + it('issues a POST request with proper headers w/ data-* prefix', function() + { + this.server.respondWith("POST", "/test", function(xhr){ + should.equal(xhr.requestHeaders['X-HTTP-Method-Override'], undefined); + xhr.respond(200, {}, "Posted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Posted!"); + }); +}) diff --git a/www/test/1.8.0/test/attributes/hx-preserve.js b/www/test/1.8.0/test/attributes/hx-preserve.js new file mode 100644 index 000000000..1e1c2368a --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-preserve.js @@ -0,0 +1,39 @@ +describe("hx-preserve attribute", function () { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('handles basic response properly', function () { + this.server.respondWith("GET", "/test", "
    New Content
    New Content
    "); + var div = make("
    Old Content
    Old Content
    "); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("Old Content"); + byId("d2").innerHTML.should.equal("New Content"); + }) + + it('handles preserved element that might not be existing', function () { + this.server.respondWith("GET", "/test", "
    New Content
    New Content
    "); + var div = make("
    Old Content
    "); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("New Content"); + byId("d2").innerHTML.should.equal("New Content"); + }) + + it('preserved element should not be swapped if it lies outside of hx-select', function () { + this.server.respondWith("GET", "/test", "
    New Content
    New Content
    "); + var div = make("
    Old Content
    Old Content
    "); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("Old Content"); + byId("d2").innerHTML.should.equal("New Content"); + }) + +}); + diff --git a/www/test/1.8.0/test/attributes/hx-push-url.js b/www/test/1.8.0/test/attributes/hx-push-url.js new file mode 100644 index 000000000..50b88d806 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-push-url.js @@ -0,0 +1,223 @@ +describe("hx-push-url attribute", function() { + + var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache"; + + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); + }); + + it("navigation should push an element into the cache when true", function () { + this.server.respondWith("GET", "/test", "second"); + getWorkArea().innerHTML.should.be.equal(""); + var div = make('
    first
    '); + div.click(); + this.server.respond(); + div.click(); + this.server.respond(); + getWorkArea().textContent.should.equal("second") + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + console.log(cache); + cache[cache.length - 1].url.should.equal("/test"); + }); + + it("navigation should push an element into the cache when string", function () { + this.server.respondWith("GET", "/test", "second"); + getWorkArea().innerHTML.should.be.equal(""); + var div = make('
    first
    '); + div.click(); + this.server.respond(); + div.click(); + this.server.respond(); + getWorkArea().textContent.should.equal("second") + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + cache.length.should.equal(2); + cache[1].url.should.equal("/abc123"); + }); + + it("restore should return old value", function () { + this.server.respondWith("GET", "/test1", '
    test1
    '); + this.server.respondWith("GET", "/test2", '
    test2
    '); + + make('
    init
    '); + + byId("d1").click(); + this.server.respond(); + var workArea = getWorkArea(); + workArea.textContent.should.equal("test1") + + byId("d2").click(); + this.server.respond(); + workArea.textContent.should.equal("test2") + + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + + cache.length.should.equal(2); + htmx._('restoreHistory')("/test1") + getWorkArea().textContent.should.equal("test1") + }); + + it("history restore should not have htmx support classes in content", function () { + this.server.respondWith("GET", "/test1", '
    test1
    '); + this.server.respondWith("GET", "/test2", '
    test2
    '); + + make('
    init
    '); + + byId("d1").click(); + this.server.respond(); + var workArea = getWorkArea(); + workArea.textContent.should.equal("test1") + + byId("d2").click(); + this.server.respond(); + workArea.textContent.should.equal("test2") + + htmx._('restoreHistory')("/test1") + getWorkArea().getElementsByClassName("htmx-request").length.should.equal(0); + }); + + it("cache should only store 10 entries", function () { + var x = 0; + this.server.respondWith("GET", /test.*/, function(xhr){ + x++; + xhr.respond(200, {}, '
    ') + }); + getWorkArea().innerHTML.should.be.equal(""); + make('
    '); + for (var i = 0; i < 20; i++) { // issue 20 requests + byId("d1").click(); + this.server.respond(); + } + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + cache.length.should.equal(10); // should only be 10 elements + }); + + it("cache miss should issue another GET", function () { + this.server.respondWith("GET", "/test1", '
    test1
    '); + this.server.respondWith("GET", "/test2", '
    test2
    '); + + make('
    init
    '); + + byId("d1").click(); + this.server.respond(); + var workArea = getWorkArea(); + workArea.textContent.should.equal("test1") + + byId("d2").click(); + this.server.respond(); + workArea.textContent.should.equal("test2") + + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + + cache.length.should.equal(2); + localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); // clear cache + htmx._('restoreHistory')("/test1") + this.server.respond(); + getWorkArea().textContent.should.equal("test1") + }); + + it("navigation should push an element into the cache w/ data-* prefix", function () { + this.server.respondWith("GET", "/test", "second"); + getWorkArea().innerHTML.should.be.equal(""); + var div = make('
    first
    '); + div.click(); + this.server.respond(); + getWorkArea().textContent.should.equal("second") + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + cache.length.should.equal(1); + }); + + it("deals with malformed JSON in history cache when getting", function () { + localStorage.setItem(HTMX_HISTORY_CACHE_NAME, "Invalid JSON"); + var history = htmx._('getCachedHistory')('url'); + should.equal(history, null); + }); + + it("deals with malformed JSON in history cache when saving", function () { + localStorage.setItem(HTMX_HISTORY_CACHE_NAME, "Invalid JSON"); + htmx._('saveToHistoryCache')('url', 'content', 'title', 'scroll'); + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + cache.length.should.equal(1); + }); + + it("does not blow out cache when saving a URL twice", function () { + htmx._('saveToHistoryCache')('url1', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url2', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url3', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url2', 'content', 'title', 'scroll'); + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + cache.length.should.equal(3); + }); + + it("history cache is LRU", function () { + htmx._('saveToHistoryCache')('url1', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url2', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url3', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url2', 'content', 'title', 'scroll'); + htmx._('saveToHistoryCache')('url1', 'content', 'title', 'scroll'); + var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME)); + cache.length.should.equal(3); + cache[0].url.should.equal("url3"); + cache[1].url.should.equal("url2"); + cache[2].url.should.equal("url1"); + }); + + it("htmx:afterSettle is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterSettle", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterSettle", handler); + } + }); + + it("should include parameters on a get", function () { + var path = ""; + var handler = htmx.on("htmx:pushedIntoHistory", function (evt) { + path = evt.detail.path; + }); + try { + this.server.respondWith("GET", /test.*/, function (xhr) { + xhr.respond(200, {}, "second") + }); + var form = make('
    first
    '); + form.click(); + this.server.respond(); + form.textContent.should.equal("second") + path.should.equal("/test?foo=bar") + } finally { + htmx.off("htmx:pushedIntoHistory", handler); + } + }); + + it("saveToHistoryCache should not throw", function () { + var bigContent = "Dummy"; + for (var i = 0; i < 20; i++) { + bigContent += bigContent; + } + try { + localStorage.removeItem("htmx-history-cache"); + htmx._("saveToHistoryCache")("/dummy", bigContent, "Foo", 0); + should.equal(localStorage.getItem("htmx-history-cache"), null); + } finally { + // clear history cache afterwards + localStorage.removeItem("htmx-history-cache"); + } + }); + +}); diff --git a/www/test/1.8.0/test/attributes/hx-put.js b/www/test/1.8.0/test/attributes/hx-put.js new file mode 100644 index 000000000..589ec199d --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-put.js @@ -0,0 +1,34 @@ +describe("hx-put attribute", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('issues a PUT request', function() + { + this.server.respondWith("PUT", "/test", function(xhr){ + xhr.respond(200, {}, "Putted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Putted!"); + }); + + it('issues a PUT request w/ data-* prefix', function() + { + this.server.respondWith("PUT", "/test", function(xhr){ + xhr.respond(200, {}, "Putted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Putted!"); + }); +}) diff --git a/www/test/1.8.0/test/attributes/hx-request.js b/www/test/1.8.0/test/attributes/hx-request.js new file mode 100644 index 000000000..e64f6481f --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-request.js @@ -0,0 +1,39 @@ +describe("hx-request attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('basic hx-request timeout works', function (done) { + var timedOut = false; + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make("
    ") + htmx.on(div, 'htmx:timeout', function(){ + timedOut = true; + }) + div.click(); + setTimeout(function(){ + div.innerHTML.should.equal(""); + // unfortunately it looks like sinon.js doesn't implement the timeout functionality + // timedOut.should.equal(true); + done(); + }, 400) + }); + + it('hx-request header works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + should.equal(xhr.requestHeaders['HX-Request'], undefined); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-select-oob.js b/www/test/1.8.0/test/attributes/hx-select-oob.js new file mode 100644 index 000000000..dbdc0e0db --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-select-oob.js @@ -0,0 +1,37 @@ +describe("hx-select-oob attribute", function () { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('basic hx-select-oob works', function() + { + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + var div2 = byId('d2'); + div2.innerHTML.should.equal("bar"); + }); + + it('basic hx-select-oob ignores bad selector', function() + { + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + var div2 = byId('d2'); + div2.innerHTML.should.equal(""); + }); + + +}); + diff --git a/www/test/1.8.0/test/attributes/hx-select.js b/www/test/1.8.0/test/attributes/hx-select.js new file mode 100644 index 000000000..c58684308 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-select.js @@ -0,0 +1,40 @@ +describe("BOOTSTRAP - htmx AJAX Tests", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('properly handles a partial of HTML', function() + { + var i = 1; + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + }); + + it('properly handles a full HTML document', function() + { + var i = 1; + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + }); + + it('properly handles a full HTML document w/ data-* prefix', function() + { + var i = 1; + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + }); +}) diff --git a/www/test/1.8.0/test/attributes/hx-sse.js b/www/test/1.8.0/test/attributes/hx-sse.js new file mode 100644 index 000000000..138734e55 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-sse.js @@ -0,0 +1,140 @@ +describe("hx-sse attribute", function() { + + function mockEventSource() { + var listeners = {}; + var wasClosed = false; + var mockEventSource = { + removeEventListener: function(name) { + delete listeners[name]; + }, + addEventListener: function (message, l) { + listeners[message] = l; + }, + sendEvent: function (eventName, data) { + var listener = listeners[eventName]; + if (listener) { + var event = htmx._("makeEvent")(eventName); + event.data = data; + listener(event); + } + }, + close: function () { + wasClosed = true; + }, + wasClosed: function () { + return wasClosed; + } + }; + return mockEventSource; + } + + beforeEach(function () { + this.server = makeServer(); + var eventSource = mockEventSource(); + this.eventSource = eventSource; + clearWorkArea(); + htmx.createEventSource = function(){ return eventSource }; + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('handles basic sse triggering', function () { + + this.server.respondWith("GET", "/d1", "div1 updated"); + this.server.respondWith("GET", "/d2", "div2 updated"); + + var div = make('
    ' + + '
    div1
    ' + + '
    div2
    ' + + '
    '); + + this.eventSource.sendEvent("e1"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1 updated"); + byId("d2").innerHTML.should.equal("div2"); + + this.eventSource.sendEvent("e2"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1 updated"); + byId("d2").innerHTML.should.equal("div2 updated"); + }) + + it('does not trigger events that arent named', function () { + + this.server.respondWith("GET", "/d1", "div1 updated"); + + var div = make('
    ' + + '
    div1
    ' + + '
    '); + + this.eventSource.sendEvent("foo"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1"); + + this.eventSource.sendEvent("e2"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1"); + + this.eventSource.sendEvent("e1"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1 updated"); + }) + + it('does not trigger events not on decendents', function () { + + this.server.respondWith("GET", "/d1", "div1 updated"); + + var div = make('
    ' + + '
    div1
    '); + + this.eventSource.sendEvent("foo"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1"); + + this.eventSource.sendEvent("e2"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1"); + + this.eventSource.sendEvent("e1"); + this.server.respond(); + byId("d1").innerHTML.should.equal("div1"); + }) + + it('is closed after removal', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make('
    ' + + '
    div1
    ' + + '
    '); + div.click(); + this.server.respond(); + this.eventSource.wasClosed().should.equal(true) + }) + + it('is closed after removal with no close and activity', function () { + var div = make('
    ' + + '
    div1
    ' + + '
    '); + div.parentElement.removeChild(div); + this.eventSource.sendEvent("e1") + this.eventSource.wasClosed().should.equal(true) + }) + + it('swaps content properly on SSE swap', function () { + var div = make('
    \n' + + '
    \n' + + '
    \n' + + '
    \n'); + byId("d1").innerText.should.equal("") + byId("d2").innerText.should.equal("") + this.eventSource.sendEvent("e1", "Event 1") + byId("d1").innerText.should.equal("Event 1") + byId("d2").innerText.should.equal("") + this.eventSource.sendEvent("e2", "Event 2") + byId("d1").innerText.should.equal("Event 1") + byId("d2").innerText.should.equal("Event 2") + }) + +}); + diff --git a/www/test/1.8.0/test/attributes/hx-swap-oob.js b/www/test/1.8.0/test/attributes/hx-swap-oob.js new file mode 100644 index 000000000..925082ada --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-swap-oob.js @@ -0,0 +1,132 @@ +describe("hx-swap-oob attribute", function () { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('handles basic response properly', function () { + this.server.respondWith("GET", "/test", "Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked"); + byId("d1").innerHTML.should.equal("Swapped"); + }) + + it('handles more than one oob swap properly', function () { + this.server.respondWith("GET", "/test", "Clicked
    Swapped1
    Swapped2
    "); + var div = make('
    click me
    '); + make('
    '); + make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked"); + byId("d1").innerHTML.should.equal("Swapped1"); + byId("d2").innerHTML.should.equal("Swapped2"); + }) + + it('handles no id match properly', function () { + this.server.respondWith("GET", "/test", "Clicked
    Swapped
    "); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + div.innerText.should.equal("Clicked"); + }) + + it('handles basic response properly w/ data-* prefix', function () { + this.server.respondWith("GET", "/test", "Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked"); + byId("d1").innerHTML.should.equal("Swapped"); + }) + + it('handles outerHTML response properly', function () { + this.server.respondWith("GET", "/test", "Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    '); + div.click(); + this.server.respond(); + byId("d1").getAttribute("foo").should.equal("bar"); + div.innerHTML.should.equal("Clicked"); + byId("d1").innerHTML.should.equal("Swapped"); + }) + + it('handles innerHTML response properly', function () { + this.server.respondWith("GET", "/test", "Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    '); + div.click(); + this.server.respond(); + should.equal(byId("d1").getAttribute("foo"), null); + div.innerHTML.should.equal("Clicked"); + byId("d1").innerHTML.should.equal("Swapped"); + }) + + it('oob swaps can be nested in content', function () { + this.server.respondWith("GET", "/test", "
    Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    '); + div.click(); + this.server.respond(); + should.equal(byId("d1").getAttribute("foo"), null); + div.innerHTML.should.equal("
    Clicked
    "); + byId("d1").innerHTML.should.equal("Swapped"); + }) + + it('oob swaps can use selectors to match up', function () { + this.server.respondWith("GET", "/test", "
    Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    '); + div.click(); + this.server.respond(); + should.equal(byId("d1").getAttribute("foo"), "bar"); + div.innerHTML.should.equal("
    Clicked
    "); + byId("d1").innerHTML.should.equal("Swapped"); + }) + + it('swaps into all targets that match the selector (innerHTML)', function () { + this.server.respondWith("GET", "/test", "
    Clicked
    Swapped
    "); + var div = make('
    click me
    '); + make('
    No swap
    '); + make('
    Not swapped
    '); + make('
    Not swapped
    '); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("No swap"); + byId("d2").innerHTML.should.equal("Swapped"); + byId("d3").innerHTML.should.equal("Swapped"); + }) + + it('swaps into all targets that match the selector (outerHTML)', function () { + var oobSwapContent = '
    Swapped
    '; + this.server.respondWith("GET", "/test", "
    Clicked
    " + oobSwapContent); + var div = make('
    click me
    '); + make('
    No swap
    '); + make('
    Not swapped
    '); + make('
    Not swapped
    '); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("
    No swap
    "); + byId("d2").innerHTML.should.equal(oobSwapContent); + byId("d3").innerHTML.should.equal(oobSwapContent); + }) + + it('oob swap delete works properly', function() + { + this.server.respondWith("GET", "/test", '
    '); + + var div = make('
    Foo
    ') + div.click(); + this.server.respond(); + should.equal(byId("d1"), null); + }); +}); + diff --git a/www/test/1.8.0/test/attributes/hx-swap.js b/www/test/1.8.0/test/attributes/hx-swap.js new file mode 100644 index 000000000..744f73cf4 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-swap.js @@ -0,0 +1,301 @@ +describe("hx-swap attribute", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('swap innerHTML properly', function() + { + this.server.respondWith("GET", "/test", 'Click Me'); + this.server.respondWith("GET", "/test2", "Clicked!"); + + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal('Click Me'); + var a = div.querySelector('a'); + a.click(); + this.server.respond(); + a.innerHTML.should.equal('Clicked!'); + }); + + it('swap outerHTML properly', function() + { + this.server.respondWith("GET", "/test", 'Click Me'); + this.server.respondWith("GET", "/test2", "Clicked!"); + + var div = make('
    ') + div.click(); + should.equal(byId("d1"), div); + this.server.respond(); + should.equal(byId("d1"), null); + byId("a1").click(); + this.server.respond(); + byId("a1").innerHTML.should.equal('Clicked!'); + }); + + it('swap beforebegin properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, '' + i + ''); + }); + this.server.respondWith("GET", "/test2", "*"); + + var div = make('
    *
    ') + var parent = div.parentElement; + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("1*"); + + byId("a1").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("**"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("*2*"); + + byId("a2").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("***"); + }); + + it('swap afterbegin properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    *
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("1*"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("21*"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("321*"); + }); + + it('swap afterbegin properly with no initial content', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("21"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("321"); + }); + + it('swap afterend properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, '' + i + ''); + }); + this.server.respondWith("GET", "/test2", "*"); + + var div = make('
    *
    ') + var parent = div.parentElement; + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("*1"); + + byId("a1").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("**"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("*2*"); + + byId("a2").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("***"); + }); + + it('handles beforeend properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    *
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("*1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*12"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*123"); + }); + + it('handles beforeend properly with no initial content', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("12"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("123"); + }); + + it('properly parses various swap specifications', function(){ + var swapSpec = htmx._("getSwapSpecification"); // internal function for swap spec + swapSpec(make("
    ")).swapStyle.should.equal("innerHTML") + swapSpec(make("
    ")).swapStyle.should.equal("innerHTML") + swapSpec(make("
    ")).swapDelay.should.equal(0) + swapSpec(make("
    ")).settleDelay.should.equal(0) // set to 0 in tests + swapSpec(make("
    ")).swapDelay.should.equal(10) + swapSpec(make("
    ")).settleDelay.should.equal(10) + swapSpec(make("
    ")).swapDelay.should.equal(10) + swapSpec(make("
    ")).settleDelay.should.equal(11) + swapSpec(make("
    ")).swapDelay.should.equal(10) + swapSpec(make("
    ")).settleDelay.should.equal(11) + swapSpec(make("
    ")).settleDelay.should.equal(11) + swapSpec(make("
    ")).settleDelay.should.equal(11) + }) + + it('works with a swap delay', function(done) { + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make("
    "); + div.click(); + this.server.respond(); + div.innerText.should.equal(""); + setTimeout(function () { + div.innerText.should.equal("Clicked!"); + done(); + }, 30); + }); + + it('works with a settle delay', function(done) { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + div.classList.contains('foo').should.equal(false); + setTimeout(function () { + byId('d1').classList.contains('foo').should.equal(true); + done(); + }, 30); + }); + + it('swap outerHTML properly w/ data-* prefix', function() + { + this.server.respondWith("GET", "/test", 'Click Me'); + this.server.respondWith("GET", "/test2", "Clicked!"); + + var div = make('
    ') + div.click(); + should.equal(byId("d1"), div); + this.server.respond(); + should.equal(byId("d1"), null); + byId("a1").click(); + this.server.respond(); + byId("a1").innerHTML.should.equal('Clicked!'); + }); + + it('swap none works properly', function() + { + this.server.respondWith("GET", "/test", 'Ooops, swapped'); + + var div = make('
    Foo
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal('Foo'); + }); + + + it('swap outerHTML does not trigger htmx:afterSwap on original element', function() + { + this.server.respondWith("GET", "/test", 'Clicked!'); + var div = make('
    ') + div.addEventListener("htmx:afterSwap", function(){ + count++; + }) + div.click(); + var count = 0; + should.equal(byId("d1"), div); + this.server.respond(); + should.equal(byId("d1"), null); + count.should.equal(0); + }); + it('swap delete works properly', function() + { + this.server.respondWith("GET", "/test", 'Oops, deleted!'); + + var div = make('
    Foo
    ') + div.click(); + this.server.respond(); + should.equal(byId("d1"), null); + }); + + it('in presence of bad swap spec, it uses the default swap strategy', function() + { + var initialSwapStyle = htmx.config.defaultSwapStyle; + htmx.config.defaultSwapStyle = "outerHTML"; + try { + this.server.respondWith("GET", "/test", "Clicked!"); + + var div = make('
    ') + var b1 = byId("b1"); + b1.click(); + this.server.respond(); + div.innerHTML.should.equal('Clicked!'); + } finally { + htmx.config.defaultSwapStyle = initialSwapStyle; + } + }); + + +}) diff --git a/www/test/1.8.0/test/attributes/hx-sync.js b/www/test/1.8.0/test/attributes/hx-sync.js new file mode 100644 index 000000000..6f4d0d10d --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-sync.js @@ -0,0 +1,224 @@ +describe("hx-sync attribute", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('can use drop strategy', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + '
    ') + var b1 = byId("b1"); + var b2 = byId("b2"); + b1.click(); + b2.click(); + this.server.respond(); + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + }); + + it('defaults to the drop strategy', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + '
    ') + var b1 = byId("b1"); + var b2 = byId("b2"); + b1.click(); + b2.click(); + this.server.respond(); + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + }); + + it('can use replace strategy', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + '
    ') + var b1 = byId("b1"); + var b2 = byId("b2"); + b1.click(); + b2.click(); + this.server.respond(); + this.server.respond(); + b1.innerHTML.should.equal('Initial'); + b2.innerHTML.should.equal('Click 0'); + }); + + it('can use queue all strategy', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + ' ' + + '
    ') + var b1 = byId("b1"); + b1.click(); + + var b2 = byId("b2"); + b2.click(); + + var b3 = byId("b3"); + b3.click(); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + b3.innerHTML.should.equal('Initial'); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Click 1'); + b3.innerHTML.should.equal('Initial'); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Click 1'); + b3.innerHTML.should.equal('Click 2'); + }); + + it('can use queue last strategy', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + ' ' + + '
    ') + var b1 = byId("b1"); + b1.click(); + + var b2 = byId("b2"); + b2.click(); + + var b3 = byId("b3"); + b3.click(); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + b3.innerHTML.should.equal('Initial'); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + b3.innerHTML.should.equal('Click 1'); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + b3.innerHTML.should.equal('Click 1'); + }); + + it('can use queue first strategy', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + ' ' + + '
    ') + var b1 = byId("b1"); + b1.click(); + + var b2 = byId("b2"); + b2.click(); + + var b3 = byId("b3"); + b3.click(); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Initial'); + b3.innerHTML.should.equal('Initial'); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Click 1'); + b3.innerHTML.should.equal('Initial'); + + this.server.respond(); + b1.innerHTML.should.equal('Click 0'); + b2.innerHTML.should.equal('Click 1'); + b3.innerHTML.should.equal('Initial'); + }); + + it('can use abort strategy to end existing abortable request', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + '
    ') + var b1 = byId("b1"); + var b2 = byId("b2"); + b1.click(); + b2.click(); + this.server.respond(); + this.server.respond(); + b1.innerHTML.should.equal('Initial'); + b2.innerHTML.should.equal('Click 0'); + }); + + it('can use abort strategy to drop abortable request when one is in flight', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + '
    ') + var b1 = byId("b1"); + var b2 = byId("b2"); + b2.click(); + b1.click(); + this.server.respond(); + this.server.respond(); + b1.innerHTML.should.equal('Initial'); + b2.innerHTML.should.equal('Click 0'); + }); + + it('can abort a request programmatically', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "Click " + count++); + }); + make('
    ' + + '
    ') + var b1 = byId("b1"); + var b2 = byId("b2"); + b1.click(); + b2.click(); + + htmx.trigger(b1, "htmx:abort"); + + this.server.respond(); + this.server.respond(); + b1.innerHTML.should.equal('Initial'); + b2.innerHTML.should.equal('Click 0'); + }); + +}) diff --git a/www/test/1.8.0/test/attributes/hx-target.js b/www/test/1.8.0/test/attributes/hx-target.js new file mode 100644 index 000000000..da48ea034 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-target.js @@ -0,0 +1,134 @@ +describe("hx-target attribute", function(){ + beforeEach(function() { + this.server = sinon.fakeServer.create(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('targets an adjacent element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + var div1 = make('
    ') + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + }); + + it('targets a parent element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div1 = make('
    ') + var btn = byId("b1") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + }); + + it('targets a `this` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div1 = make('
    ') + var btn = byId("b1") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + }); + + it('targets a `closest` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div1 = make('

    ') + var btn = byId("b1") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + }); + + it('targets a `find` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var div1 = make('
    Click Me!
    ') + div1.click(); + this.server.respond(); + var span1 = byId("s1") + var span2 = byId("s2") + span1.innerHTML.should.equal("Clicked!"); + span2.innerHTML.should.equal(""); + }); + + it('targets an inner element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + var div1 = byId("d1") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + }); + + + it('handles bad target gracefully', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Click Me!"); + }); + + + it('targets an adjacent element properly w/ data-* prefix', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + var div1 = make('
    ') + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + }); + + it('targets a `next` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + make('
    ' + + '
    ' + + ' ' + + '
    ' + + '
    ' + + '
    ') + var btn = byId("b1") + var div1 = byId("d1") + var div2 = byId("d2") + var div3 = byId("d3") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked!"); + div2.innerHTML.should.equal(""); + div3.innerHTML.should.equal(""); + }); + + it('targets a `previous` element properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + make('
    ' + + '
    ' + + ' ' + + '
    ' + + '
    ' + + '
    ') + var btn = byId("b1") + var div1 = byId("d1") + var div2 = byId("d2") + var div3 = byId("d3") + btn.click(); + this.server.respond(); + div1.innerHTML.should.equal(""); + div2.innerHTML.should.equal(""); + div3.innerHTML.should.equal("Clicked!"); + }); + +}) diff --git a/www/test/1.8.0/test/attributes/hx-trigger.js b/www/test/1.8.0/test/attributes/hx-trigger.js new file mode 100644 index 000000000..3bcf6dd50 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-trigger.js @@ -0,0 +1,752 @@ +describe("hx-trigger attribute", function(){ + beforeEach(function() { + this.server = sinon.fakeServer.create(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('non-default value works', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + + var form = make('
    Click Me!
    '); + form.click(); + form.innerHTML.should.equal("Click Me!"); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('changed modifier works', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var input = make(''); + var div = make('
    '); + input.click(); + this.server.respond(); + div.innerHTML.should.equal(""); + input.click(); + this.server.respond(); + div.innerHTML.should.equal(""); + input.value = "bar"; + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + }); + + it('once modifier works', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var input = make(''); + var div = make('
    '); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.value = "bar"; + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + }); + + it('once modifier works with multiple triggers', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var input = make(''); + var div = make('
    '); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.value = "bar"; + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + htmx.trigger(input, "foo"); + this.server.respond(); + div.innerHTML.should.equal("Requests: 2"); + }); + + it('polling works', function(complete) + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + if (requests > 5) { + complete(); + // cancel polling with a + xhr.respond(286, {}, "Requests: " + requests); + } else { + xhr.respond(200, {}, "Requests: " + requests); + } + }); + this.server.autoRespond = true; + this.server.autoRespondAfter = 0; + make('
    '); + }); + + + it('non-default value works w/ data-* prefix', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + var form = make('
    Click Me!
    '); + form.click(); + form.innerHTML.should.equal("Click Me!"); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('works with multiple events', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div = make('
    Requests: 0
    '); + div.innerHTML.should.equal("Requests: 0"); + this.server.respond(); + div.innerHTML.should.equal("Requests: 1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Requests: 2"); + }); + + it("parses spec strings", function() + { + var specExamples = { + "": [{trigger: 'click'}], + "every 1s": [{trigger: 'every', pollInterval: 1000}], + "click": [{trigger: 'click'}], + "customEvent": [{trigger: 'customEvent'}], + "event changed": [{trigger: 'event', changed: true}], + "event once": [{trigger: 'event', once: true}], + "event delay:1s": [{trigger: 'event', delay: 1000}], + "event throttle:1s": [{trigger: 'event', throttle: 1000}], + "event delay:1s, foo": [{trigger: 'event', delay: 1000}, {trigger: 'foo'}], + "event throttle:1s, foo": [{trigger: 'event', throttle: 1000}, {trigger: 'foo'}], + "event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}], + "event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}], + "event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}], + "event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}], + "event1,": [{trigger: 'event1'}], + " ": [{trigger: 'click'}], + } + + for (var specString in specExamples) { + var div = make("
    "); + var spec = htmx._('getTriggerSpecs')(div); + spec.should.deep.equal(specExamples[specString], "Found : " + JSON.stringify(spec) + ", expected : " + JSON.stringify(specExamples[specString]) + " for spec: " + specString); + } + }); + + it('sets default trigger for forms', function() + { + var form = make('
    '); + var spec = htmx._('getTriggerSpecs')(form); + spec.should.deep.equal([{trigger: 'submit'}]); + }) + + it('sets default trigger for form elements', function() + { + var form = make(''); + var spec = htmx._('getTriggerSpecs')(form); + spec.should.deep.equal([{trigger: 'change'}]); + }) + + it('filters properly with false filter spec', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var form = make('
    Not Called
    '); + form.click(); + form.innerHTML.should.equal("Not Called"); + var event = htmx._("makeEvent")('evt'); + form.dispatchEvent(event); + this.server.respond(); + form.innerHTML.should.equal("Not Called"); + }) + + it('filters properly with true filter spec', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var form = make('
    Not Called
    '); + form.click(); + form.innerHTML.should.equal("Not Called"); + var event = htmx._("makeEvent")('evt'); + event.foo = true; + form.dispatchEvent(event); + this.server.respond(); + form.innerHTML.should.equal("Called!"); + }) + + it('filters properly compound filter spec', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var event = htmx._("makeEvent")('evt'); + event.foo = true; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + event.bar = true; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + }) + + it('can refer to target element in condition', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var event = htmx._("makeEvent")('evt'); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + div.classList.add("doIt"); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + }) + + it('can refer to target element in condition w/ equality', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var event = htmx._("makeEvent")('evt'); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + div.id = "foo"; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + }) + + it('negative condition', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + div.classList.add("disabled"); + var event = htmx._("makeEvent")('evt'); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + div.classList.remove("disabled"); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + }) + + it('global function call works', function(){ + window.globalFun = function(evt) { + return evt.bar; + } + try { + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var event = htmx._("makeEvent")('evt'); + event.bar = false; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + event.bar = true; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + } finally { + delete window.globalFun; + } + }) + + it('global property event filter works', function(){ + window.foo = { + bar:false + } + try { + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var event = htmx._("makeEvent")('evt'); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + foo.bar = true; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + } finally { + delete window.foo; + } + }) + + it('global variable filter works', function(){ + try { + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var event = htmx._("makeEvent")('evt'); + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Not Called"); + foo = true; + div.dispatchEvent(event); + this.server.respond(); + div.innerHTML.should.equal("Called!"); + } finally { + delete window.foo; + } + }) + + it('can filter polling', function(complete){ + this.server.respondWith("GET", "/test", "Called!"); + window.foo = false; + var div = make('
    Not Called
    '); + var div2 = make('
    Not Called
    '); + this.server.autoRespond = true; + this.server.autoRespondAfter = 0; + setTimeout(function () { + div.innerHTML.should.equal("Not Called"); + div2.innerHTML.should.equal("Called!"); + delete window.foo; + complete(); + }, 100); + }) + + it('bad condition issues error', function(){ + this.server.respondWith("GET", "/test", "Called!"); + var div = make('
    Not Called
    '); + var errorEvent = null; + var handler = htmx.on("htmx:eventFilter:error", function (event) { + errorEvent = event; + }); + try { + var event = htmx._("makeEvent")('evt'); + div.dispatchEvent(event); + should.not.equal(null, errorEvent); + should.not.equal(null, errorEvent.detail.source); + console.log(errorEvent.detail.source); + } finally { + htmx.off("htmx:eventFilter:error", handler); + } + }) + + it('from clause works', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div2 = make('
    '); + var div1 = make('
    Requests: 0
    '); + div1.innerHTML.should.equal("Requests: 0"); + div1.click(); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 0"); + div2.click(); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 1"); + }); + + it('from clause works with body selector', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div1 = make('
    Requests: 0
    '); + div1.innerHTML.should.equal("Requests: 0"); + document.body.click(); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 1"); + }); + + it('from clause works with document selector', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div1 = make('
    Requests: 0
    '); + div1.innerHTML.should.equal("Requests: 0"); + htmx.trigger(document, 'foo'); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 1"); + }); + + it('from clause works with window selector', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div1 = make('
    Requests: 0
    '); + div1.innerHTML.should.equal("Requests: 0"); + htmx.trigger(window, 'foo'); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 1"); + }); + + it('from clause works with closest clause', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div1 = make(''); + var a1 = byId('a1'); + a1.innerHTML.should.equal("Requests: 0"); + div1.click(); + this.server.respond(); + a1.innerHTML.should.equal("Requests: 1"); + }); + + it('from clause works with find clause', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div1 = make(''); + var a1 = byId('a1'); + a1.innerHTML.should.equal("Requests: 0"); + a1.click(); + this.server.respond(); + a1.innerHTML.should.equal("Requests: 1"); + }); + + it('event listeners on other elements are removed when an element is swapped out', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/test2", "Clicked"); + + var div1 = make('
    ' + + '
    Requests: 0
    ' + + '
    '); + var div2 = byId("d2"); + + div2.innerHTML.should.equal("Requests: 0"); + document.body.click(); + this.server.respond(); + requests.should.equal(1); + + requests.should.equal(1); + + div1.click(); + this.server.respond(); + div1.innerHTML.should.equal("Clicked"); + + requests.should.equal(2); + + document.body.click(); + this.server.respond(); + + requests.should.equal(2); + }); + + it('multiple triggers with from clauses mixed in work', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + var div2 = make('
    '); + var div1 = make('
    Requests: 0
    '); + div1.innerHTML.should.equal("Requests: 0"); + div1.click(); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 1"); + div2.click(); + this.server.respond(); + div1.innerHTML.should.equal("Requests: 2"); + }); + + it('event listeners can filter on target', function() + { + var requests = 0; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + + var div1 = make('
    ' + + '
    Requests: 0
    ' + + '
    ' + + '
    ' + + '
    '); + var div1 = byId("d1"); + var div2 = byId("d2"); + var div3 = byId("d3"); + + div1.innerHTML.should.equal("Requests: 0"); + document.body.click(); + this.server.respond(); + requests.should.equal(0); + + div1.click(); + this.server.respond(); + requests.should.equal(0); + + div2.click(); + this.server.respond(); + requests.should.equal(0); + + div3.click(); + this.server.respond(); + requests.should.equal(1); + + }); + + it('consume prevents event propogation', function() + { + this.server.respondWith("GET", "/foo", "foo"); + this.server.respondWith("GET", "/bar", "bar"); + var div = make("
    " + + "
    " + + "
    "); + + byId("d1").click(); + this.server.respond(); + + // should not have been replaced by click + byId("d1").parentElement.should.equal(div); + byId("d1").innerText.should.equal("bar"); + }); + + it('throttle prevents multiple requests from happening', function(done) + { + var requests = 0; + var server = this.server; + server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + server.respondWith("GET", "/bar", "bar"); + var div = make("
    "); + + div.click(); + server.respond(); + + div.click(); + server.respond(); + + div.click(); + server.respond(); + + div.click(); + server.respond(); + + // should not have been replaced by click + div.innerText.should.equal("Requests: 1"); + + setTimeout(function () { + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 2"); + + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 2"); + + done(); + }, 50); + }); + + it('delay delays the request', function(done) + { + var requests = 0; + var server = this.server; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/bar", "bar"); + var div = make("
    "); + + div.click(); + this.server.respond(); + + div.click(); + this.server.respond(); + + div.click(); + this.server.respond(); + + div.click(); + this.server.respond(); + div.innerText.should.equal(""); + + setTimeout(function () { + server.respond(); + div.innerText.should.equal("Requests: 1"); + + div.click(); + server.respond(); + div.innerText.should.equal("Requests: 1"); + + done(); + }, 50); + }); + + it('requests are queued with last one winning by default', function() + { + var requests = 0; + var server = this.server; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/bar", "bar"); + var div = make("
    "); + + div.click(); + div.click(); + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + + this.server.respond(); + div.innerText.should.equal("Requests: 2"); + + this.server.respond(); + div.innerText.should.equal("Requests: 2"); + }); + + it('queue:all queues all requests', function() + { + var requests = 0; + var server = this.server; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/bar", "bar"); + var div = make("
    "); + + div.click(); + div.click(); + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + + this.server.respond(); + div.innerText.should.equal("Requests: 2"); + + this.server.respond(); + div.innerText.should.equal("Requests: 3"); + }); + + + it('queue:first queues first request', function() + { + var requests = 0; + var server = this.server; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/bar", "bar"); + var div = make("
    "); + + div.click(); + div.click(); + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + + this.server.respond(); + div.innerText.should.equal("Requests: 2"); + + this.server.respond(); + div.innerText.should.equal("Requests: 2"); + }); + + it('queue:none queues no requests', function() + { + var requests = 0; + var server = this.server; + this.server.respondWith("GET", "/test", function (xhr) { + requests++; + xhr.respond(200, {}, "Requests: " + requests); + }); + this.server.respondWith("GET", "/bar", "bar"); + var div = make("
    "); + + div.click(); + div.click(); + div.click(); + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + + this.server.respond(); + div.innerText.should.equal("Requests: 1"); + }); + + it('load event works w/ positive filters', function() + { + this.server.respondWith("GET", "/test", "Loaded!"); + var div = make('
    Load Me!
    '); + div.innerHTML.should.equal("Load Me!"); + this.server.respond(); + div.innerHTML.should.equal("Loaded!"); + }); + + it('load event works w/ negative filters', function() + { + this.server.respondWith("GET", "/test", "Loaded!"); + var div = make('
    Load Me!
    '); + div.innerHTML.should.equal("Load Me!"); + this.server.respond(); + div.innerHTML.should.equal("Load Me!"); + }); + + it('reveal event works on two elements', function() + { + this.server.respondWith("GET", "/test1", "test 1"); + this.server.respondWith("GET", "/test2", "test 2"); + var div = make('
    '); + var div2 = make('
    '); + div.innerHTML.should.equal(""); + div2.innerHTML.should.equal(""); + htmx.trigger(div, 'revealed') + htmx.trigger(div2, 'revealed') + this.server.respondAll(); + div.innerHTML.should.equal("test 1"); + div2.innerHTML.should.equal("test 2"); + }); + + + +}) diff --git a/www/test/1.8.0/test/attributes/hx-vals.js b/www/test/1.8.0/test/attributes/hx-vals.js new file mode 100644 index 000000000..949df8182 --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-vals.js @@ -0,0 +1,188 @@ +describe("hx-vals attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('basic hx-vals works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('basic hx-vals works with braces', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('multiple hx-vals works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['v1'].should.equal("test"); + params['v2'].should.equal("42"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals can be on parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make("
    "); + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals can override parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + make("
    "); + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals overrides inputs', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals overrides hx-vars', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('basic hx-vals javascript: works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals works with braces', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('multiple hx-vals works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['v1'].should.equal("test"); + params['v2'].should.equal("42"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals can be on parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ') + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals can override parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ') + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals overrides inputs', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vals treats objects as JSON', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("{\"i2\":\"test\"}"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    ") + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-vars.js b/www/test/1.8.0/test/attributes/hx-vars.js new file mode 100644 index 000000000..6d487c39e --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-vars.js @@ -0,0 +1,87 @@ +describe("hx-vars attribute", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('basic hx-vars works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vars works with braces', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('multiple hx-vars works', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['v1'].should.equal("test"); + params['v2'].should.equal("42"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vars can be on parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ') + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vars can override parents', function () { + this.server.respondWith("POST", "/vars", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + make('
    ') + var div = byId("d1"); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + + it('hx-vars overrides inputs', function () { + this.server.respondWith("POST", "/include", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("best"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make('
    ') + var input = byId("i1") + input.click(); + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/attributes/hx-ws.js b/www/test/1.8.0/test/attributes/hx-ws.js new file mode 100644 index 000000000..a509b7b7b --- /dev/null +++ b/www/test/1.8.0/test/attributes/hx-ws.js @@ -0,0 +1,75 @@ +describe("hx-ws attribute", function() { + + function mockWebsocket() { + var listener; + var lastSent; + var wasClosed = false; + var mockSocket = { + addEventListener : function(message, l) { + listener = l; + }, + write : function(content) { + return listener({data:content}); + }, + send : function(data) { + lastSent = data; + }, + getLastSent : function() { + return lastSent; + }, + close : function() { + wasClosed = true; + }, + wasClosed : function () { + return wasClosed; + } + }; + return mockSocket; + } + + beforeEach(function () { + this.server = makeServer(); + var socket = mockWebsocket(); + this.socket = socket; + clearWorkArea(); + htmx.createWebSocket = function(){ + return socket + }; + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('handles a basic call back', function () { + var div = make('
    div1
    div2
    '); + this.socket.write("
    replaced
    ") + byId("d1").innerHTML.should.equal("replaced"); + byId("d2").innerHTML.should.equal("div2"); + }) + + it('handles a basic send', function () { + var div = make('
    div1
    '); + byId("d1").click(); + var lastSent = this.socket.getLastSent(); + var data = JSON.parse(lastSent); + data.HEADERS["HX-Request"].should.equal("true"); + }) + + it('is closed after removal', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var div = make('
    '); + div.click(); + this.server.respond(); + this.socket.wasClosed().should.equal(true) + }) + + it('is closed after removal with no close and activity', function () { + var div = make('
    '); + div.parentElement.removeChild(div); + this.socket.write("
    replaced
    ") + this.socket.wasClosed().should.equal(true) + }) + +}); + diff --git a/www/test/1.8.0/test/core/ajax.js b/www/test/1.8.0/test/core/ajax.js new file mode 100644 index 000000000..1498f1051 --- /dev/null +++ b/www/test/1.8.0/test/core/ajax.js @@ -0,0 +1,956 @@ +describe("Core htmx AJAX Tests", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + // bootstrap test + it('issues a GET request on click and swaps content', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('processes inner content properly', function() + { + this.server.respondWith("GET", "/test", 'Click Me'); + this.server.respondWith("GET", "/test2", "Clicked!"); + + var div = make('
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal('Click Me'); + var a = div.querySelector('a'); + a.click(); + this.server.respond(); + a.innerHTML.should.equal('Clicked!'); + }); + + it('handles swap outerHTML properly', function() + { + this.server.respondWith("GET", "/test", 'Click Me'); + this.server.respondWith("GET", "/test2", "Clicked!"); + + var div = make('
    ') + div.click(); + should.equal(byId("d1"), div); + this.server.respond(); + should.equal(byId("d1"), null); + byId("a1").click(); + this.server.respond(); + byId("a1").innerHTML.should.equal('Clicked!'); + }); + + it('handles beforebegin properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, '' + i + ''); + }); + this.server.respondWith("GET", "/test2", "*"); + + var div = make('
    *
    ') + var parent = div.parentElement; + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("1*"); + + byId("a1").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("**"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("*2*"); + + byId("a2").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("***"); + }); + + it('handles afterbegin properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    *
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("1*"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("21*"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("321*"); + }); + + it('handles afterbegin properly with no initial content', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("21"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("321"); + }); + + it('handles afterend properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, '' + i + ''); + }); + this.server.respondWith("GET", "/test2", "*"); + + var div = make('
    *
    ') + var parent = div.parentElement; + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("*1"); + + byId("a1").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("**"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*"); + removeWhiteSpace(parent.innerText).should.equal("*2*"); + + byId("a2").click(); + this.server.respond(); + removeWhiteSpace(parent.innerText).should.equal("***"); + }); + + it('handles beforeend properly', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    *
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("*1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*12"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("*123"); + }); + + it('handles beforeend properly with no initial content', function() + { + var i = 0; + this.server.respondWith("GET", "/test", function(xhr){ + i++; + xhr.respond(200, {}, "" + i); + }); + + var div = make('
    ') + + div.click(); + this.server.respond(); + div.innerText.should.equal("1"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("12"); + + div.click(); + this.server.respond(); + div.innerText.should.equal("123"); + }); + + it('handles hx-target properly', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make(''); + var target = make('Initial'); + btn.click(); + target.innerHTML.should.equal("Initial"); + this.server.respond(); + target.innerHTML.should.equal("Clicked!"); + }); + + it('handles 204 NO CONTENT responses properly', function() + { + this.server.respondWith("GET", "/test", [204, {}, "No Content!"]); + + var btn = make(''); + btn.click(); + btn.innerHTML.should.equal("Click Me!"); + this.server.respond(); + btn.innerHTML.should.equal("Click Me!"); + }); + + it('handles 304 NOT MODIFIED responses properly', function() + { + this.server.respondWith("GET", "/test-1", [200, {}, "Content for Tab 1"]); + this.server.respondWith("GET", "/test-2", [200, {}, "Content for Tab 2"]); + + var target = make('
    ') + var btn1 = make(''); + var btn2 = make(''); + + btn1.click(); + target.innerHTML.should.equal(""); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 1"); + + btn2.click(); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 2"); + + this.server.respondWith("GET", "/test-1", [304, {}, "Content for Tab 1"]); + this.server.respondWith("GET", "/test-2", [304, {}, "Content for Tab 2"]); + + btn1.click(); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 1"); + + btn2.click(); + this.server.respond(); + target.innerHTML.should.equal("Content for Tab 2"); + }); + + it('handles hx-trigger with non-default value', function() + { + this.server.respondWith("GET", "/test", "Clicked!"); + + var form = make('
    Click Me!
    '); + form.click(); + form.innerHTML.should.equal("Click Me!"); + this.server.respond(); + form.innerHTML.should.equal("Clicked!"); + }); + + it('handles hx-trigger with load event', function() + { + this.server.respondWith("GET", "/test", "Loaded!"); + var div = make('
    Load Me!
    '); + div.innerHTML.should.equal("Load Me!"); + this.server.respond(); + div.innerHTML.should.equal("Loaded!"); + }); + + it('sets the content type of the request properly', function (done) { + this.server.respondWith("GET", "/test", function(xhr){ + xhr.respond(200, {}, "done"); + xhr.overriddenMimeType.should.equal("text/html"); + done(); + }); + var div = make('
    Click Me!
    '); + div.click(); + this.server.respond(); + }); + + it('issues two requests when clicked twice before response', function() + { + var i = 1; + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, "click " + i); + i++ + }); + var div = make('
    '); + div.click(); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("click 1"); + this.server.respond(); + div.innerHTML.should.equal("click 2"); + }); + + it('issues two requests when clicked three times before response', function() + { + var i = 1; + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, "click " + i); + i++ + }); + var div = make('
    '); + div.click(); + div.click(); + div.click(); + this.server.respondAll(); + div.innerHTML.should.equal("click 2"); + }); + + it('properly handles hx-select for basic situation', function() + { + var i = 1; + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + }); + + it('properly handles hx-select for full html document situation', function() + { + this.server.respondWith("GET", "/test", "
    foo
    bar
    "); + var div = make('
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("
    foo
    "); + }); + + it('properly settles attributes on interior elements', function(done) + { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + should.equal(byId("d1").getAttribute("width"), null); + setTimeout(function () { + should.equal(byId("d1").getAttribute("width"), "bar"); + done(); + }, 20); + }); + + it('properly handles multiple select input', function() + { + var values; + this.server.respondWith("Post", "/test", function (xhr) { + values = getParameters(xhr); + xhr.respond(204, {}, ""); + }); + + var form = make('
    ' + + ''+ + '
    '); + + form.click(); + this.server.respond(); + values.should.deep.equal({}); + + byId("m1").selected = true; + form.click(); + this.server.respond(); + values.should.deep.equal({multiSelect:"m1"}); + + byId("m1").selected = true; + byId("m3").selected = true; + form.click(); + this.server.respond(); + values.should.deep.equal({multiSelect:["m1", "m3"]}); + }); + + it('properly handles multiple select input when "multiple" attribute is empty string', function() + { + var values; + this.server.respondWith("Post", "/test", function (xhr) { + values = getParameters(xhr); + xhr.respond(204, {}, ""); + }); + + var form = make('
    ' + + '' + + '
    '); + + form.click(); + this.server.respond(); + values.should.deep.equal({}); + + byId("m1").selected = true; + form.click(); + this.server.respond(); + values.should.deep.equal({multiSelect:"m1"}); + + byId("m1").selected = true; + byId("m3").selected = true; + form.click(); + this.server.respond(); + values.should.deep.equal({multiSelect:["m1", "m3"]}); + }); + + it('properly handles two multiple select inputs w/ same name', function() + { + var values; + this.server.respondWith("Post", "/test", function (xhr) { + values = getParameters(xhr); + xhr.respond(204, {}, ""); + }); + + var form = make('
    ' + + ''+ + ''+ + '
    '); + + form.click(); + this.server.respond(); + values.should.deep.equal({}); + + byId("m1").selected = true; + form.click(); + this.server.respond(); + values.should.deep.equal({multiSelect:"m1"}); + + byId("m1").selected = true; + byId("m3").selected = true; + byId("m7").selected = true; + byId("m8").selected = true; + form.click(); + this.server.respond(); + values.should.deep.equal({multiSelect:["m1", "m3", "m7", "m8"]}); + }); + + it('properly handles checkbox inputs', function() + { + var values; + this.server.respondWith("Post", "/test", function (xhr) { + values = getParameters(xhr); + xhr.respond(204, {}, ""); + }); + + var form = make('
    ' + + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
    '); + + form.click(); + this.server.respond(); + values.should.deep.equal({}); + + byId("cb1").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:"cb1"}); + + byId("cb1").checked = true; + byId("cb2").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:["cb1", "cb2"]}); + + byId("cb1").checked = true; + byId("cb2").checked = true; + byId("cb3").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:["cb1", "cb2", "cb3"]}); + + byId("cb1").checked = true; + byId("cb2").checked = true; + byId("cb3").checked = true; + byId("cb4").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:"cb4"}); + + byId("cb1").checked = true; + byId("cb2").checked = true; + byId("cb3").checked = true; + byId("cb4").checked = true; + byId("cb5").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:["cb4", "cb5"]}); + + byId("cb1").checked = true; + byId("cb2").checked = true; + byId("cb3").checked = true; + byId("cb4").checked = true; + byId("cb5").checked = true; + byId("cb6").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:["cb4", "cb5"], c3:"cb6"}); + + byId("cb1").checked = true; + byId("cb2").checked = false; + byId("cb3").checked = true; + byId("cb4").checked = false; + byId("cb5").checked = true; + byId("cb6").checked = true; + form.click(); + this.server.respond(); + values.should.deep.equal({c1:["cb1", "cb3"], c2:"cb5", c3:"cb6"}); + + }); + + it('text nodes dont screw up settling via variable capture', function() + { + this.server.respondWith("GET", "/test", "
    fooo"); + this.server.respondWith("GET", "/test2", "clicked"); + var div = make("
    "); + div.click(); + this.server.respond(); + byId("d1").click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("clicked"); + }); + + it('script nodes evaluate', function() + { + var globalWasCalled = false; + window.callGlobal = function() { + globalWasCalled = true; + } + try { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + globalWasCalled.should.equal(true); + } finally { + delete window.callGlobal; + } + }); + + it('stand alone script nodes evaluate', function() + { + var globalWasCalled = false; + window.callGlobal = function() { + globalWasCalled = true; + } + try { + this.server.respondWith("GET", "/test", ""); + var div = make("
    "); + div.click(); + this.server.respond(); + globalWasCalled.should.equal(true); + } finally { + delete window.callGlobal; + } + }); + + it('script nodes can define global functions', function() + { + try { + window.foo = {} + this.server.respondWith("GET", "/test", ""); + var div = make("
    "); + div.click(); + this.server.respond(); + foo.bar().should.equal(42); + } finally { + delete foo; + } + }); + + it('child script nodes evaluate when children', function() + { + var globalWasCalled = false; + window.callGlobal = function() { + globalWasCalled = true; + } + try { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + globalWasCalled.should.equal(true); + } finally { + delete window.callGlobal; + } + }); + + it('child script nodes evaluate when first child', function() + { + var globalWasCalled = false; + window.callGlobal = function() { + globalWasCalled = true; + } + try { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + globalWasCalled.should.equal(true); + } finally { + delete window.callGlobal; + } + }); + + it('child script nodes evaluate when not explicitly marked javascript', function() + { + var globalWasCalled = false; + window.callGlobal = function() { + globalWasCalled = true; + } + try { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + globalWasCalled.should.equal(true); + } finally { + delete window.callGlobal; + } + }); + + it('script nodes do not evaluate when explicity marked as something other than javascript', function() + { + var globalWasCalled = false; + window.callGlobal = function() { + globalWasCalled = true; + } + try { + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + globalWasCalled.should.equal(false); + } finally { + delete window.callGlobal; + } + }); + + it('script nodes evaluate after swap', function() + { + window.callGlobal = function() { + console.log("Here..."); + window.tempVal = byId("d1").innerText + } + try { + this.server.respondWith("GET", "/test", "
    After settle...
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + window.tempVal.should.equal("After settle..."); + } finally { + delete window.callGlobal; + delete window.tempVal; + } + }); + + it('script node exceptions do not break rendering', function() + { + this.skip("Rendering does not break, but the exception bubbles up and mocha reports it"); + this.server.respondWith("GET", "/test", "clicked"); + var div = make("
    "); + div.click(); + this.server.respond(); + div.innerText.should.equal("clicked"); + console.log(div.innerText); + console.log("here"); + }); + + it('allows empty verb values', function() + { + var path = null; + var div = make("
    "); + htmx.on(div, "htmx:configRequest", function (evt) { + path = evt.detail.path; + return false; + }); + div.click(); + this.server.respond(); + path.should.not.be.null; + }); + + it('allows blank verb values', function() + { + var path = null; + var div = make("
    "); + htmx.on(div, "htmx:configRequest", function (evt) { + path = evt.detail.path; + return false; + }); + div.click(); + this.server.respond(); + path.should.not.be.null; + }); + + it('input values are not settle swapped (causes flicker)', function() + { + this.server.respondWith("GET", "/test", ""); + var input = make(""); + input.click(); + this.server.respond(); + input = byId('i1'); + input.value.should.equal('bar'); + }); + + it('autofocus attribute works properly', function() + { + this.server.respondWith("GET", "/test", ""); + var input = make(""); + input.focus(); + input.click(); + document.activeElement.should.equal(input); + this.server.respond(); + var input2 = byId('i2'); + document.activeElement.should.equal(input2); + }); + + it('autofocus attribute works properly w/ child', function() + { + this.server.respondWith("GET", "/test", "
    "); + var input = make(""); + input.focus(); + input.click(); + document.activeElement.should.equal(input); + this.server.respond(); + var input2 = byId('i2'); + document.activeElement.should.equal(input2); + }); + + it('autofocus attribute works properly w/ true value', function() + { + this.server.respondWith("GET", "/test", "
    "); + var input = make(""); + input.focus(); + input.click(); + document.activeElement.should.equal(input); + this.server.respond(); + var input2 = byId('i2'); + document.activeElement.should.equal(input2); + }); + + it('multipart/form-data encoding works', function() + { + this.server.respondWith("POST", "/test", function(xhr){ + should.equal(xhr.requestHeaders['Content-Type'], undefined); + if (xhr.requestBody.get) { //IE 11 does not support + xhr.requestBody.get("i1").should.equal('foo'); + } + xhr.respond(200, {}, "body: " + xhr.requestBody); + }); + var form = make("
    " + + "" + + "
    "); + form.focus(); + form.click(); + this.server.respond(); + }); + + it('removed elements do not issue requests', function() + { + var count = 0; + this.server.respondWith("GET", "/test", function (xhr) { + count++; + xhr.respond(200, {}, ""); + }); + var btn = make('') + htmx.remove(btn); + btn.click(); + this.server.respond(); + count.should.equal(0); + }); + + it('title tags update title', function() + { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, "htmx rocks!Clicked!"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Clicked!"); + window.document.title.should.equal("htmx rocks!"); + }); + + it('svg title tags do not update title', function() + { + var originalTitle = window.document.title + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, "" + originalTitle + "UPDATE" + "Clicked!"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Clicked!"); + window.document.title.should.equal(originalTitle); + }); + + it('first title tag outside svg title tags updates title', function() + { + var originalTitle = window.document.title + var newTitle = originalTitle + "!!!"; + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, "" + newTitle + "fooClicked!x"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Clicked!"); + window.document.title.should.equal(newTitle); + }); + + it('title update does not URL escape', function() + { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, "</> htmx rocks!Clicked!"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Clicked!"); + window.document.title.should.equal(" htmx rocks!"); + }); + + it('by default 400 content is not swapped', function() + { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(400, {}, "Clicked!"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Click Me!"); + }); + + it('400 content can be swapped if configured to do so', function() + { + var handler = htmx.on("htmx:beforeSwap", function (event) { + if (event.detail.xhr.status === 400) { + event.detail.shouldSwap = true; + } + }); + + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(400, {}, "Clicked!"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Clicked!"); + htmx.off("htmx:beforeSwap", handler); + }); + + it('400 content can be retargeted if configured to do so', function() + { + var handler = htmx.on("htmx:beforeSwap", function (event) { + if (event.detail.xhr.status === 400) { + event.detail.shouldSwap = true; + event.detail.target = byId('d1') + } + }); + + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(400, {}, "Clicked!"); + }); + var btn = make('') + var div = make('
    ') + btn.click(); + this.server.respond(); + div.innerText.should.equal("Clicked!"); + htmx.off("htmx:beforeSwap", handler); + }); + + it('errors are triggered only on 400+', function() + { + var errors = 0; + var handler = htmx.on("htmx:responseError", function(){ + errors++; + }) + this.server.respondWith("GET", "/test1", function (xhr) { + xhr.respond(204, {}, "Clicked!"); + }); + this.server.respondWith("GET", "/test2", function (xhr) { + xhr.respond(400, {}, "Clicked!"); + }); + var btn1 = make('') + var btn2 = make('') + btn1.click(); + btn2.click(); + this.server.respond(); + this.server.respond(); + errors.should.equal(1); + htmx.off("htmx:responseError", handler); + }); + + + it('content can be modified if configured to do so', function() + { + var handler = htmx.on("htmx:beforeSwap", function (event) { + if (event.detail.xhr.status === 400) { + event.detail.shouldSwap = true; + event.detail.serverResponse = event.detail.serverResponse + "!!"; + } + }); + + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(400, {}, "Clicked!"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerText.should.equal("Clicked!!!"); + htmx.off("htmx:beforeSwap", handler); + }); + + it('scripts w/ src attribute are properly loaded', function(done) + { + try { + this.server.respondWith("GET", "/test", ""); + var div = make("
    "); + div.click(); + this.server.respond(); + setTimeout(function () { + window.globalWasCalled.should.equal(true); + delete window.globalWasCalled; + done(); + }, 400); + } finally { + delete window.globalWasCalled; + } + }); + +}) diff --git a/www/test/1.8.0/test/core/api.js b/www/test/1.8.0/test/core/api.js new file mode 100644 index 000000000..25bc9d22a --- /dev/null +++ b/www/test/1.8.0/test/core/api.js @@ -0,0 +1,321 @@ +describe("Core htmx API test", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('onLoad is called... onLoad', function(){ + // also tests on/off + this.server.respondWith("GET", "/test", "
    ") + var helper = htmx.onLoad(function (elt) { + elt.setAttribute("foo", "bar"); + }); + try { + var div = make("
    "); + div.click(); + this.server.respond(); + byId("d1").getAttribute("foo").should.equal("bar"); + } finally { + htmx.off("htmx:load", helper); + } + }); + + it('triggers properly', function () { + var div = make("
    "); + var myEventCalled = false; + var detailStr = ""; + htmx.on("myEvent", function(evt){ + myEventCalled = true; + detailStr = evt.detail.str; + }) + htmx.trigger(div, "myEvent", {str:"foo"}) + + myEventCalled.should.equal(true); + detailStr.should.equal("foo"); + }); + + it('triggers properly w/ selector', function () { + var div = make("
    "); + var myEventCalled = false; + var detailStr = ""; + htmx.on("myEvent", function(evt){ + myEventCalled = true; + detailStr = evt.detail.str; + }) + htmx.trigger("#div1", "myEvent", {str:"foo"}) + + myEventCalled.should.equal(true); + detailStr.should.equal("foo"); + }); + + it('triggers with no details properly', function () { + var div = make("
    "); + var myEventCalled = false; + htmx.on("myEvent", function(evt){ + myEventCalled = true; + }) + htmx.trigger(div, "myEvent") + myEventCalled.should.equal(true); + }); + + it('should find properly', function(){ + var div = make("
    "); + div.should.equal(htmx.find("#d1")); + div.should.equal(htmx.find(".c1")); + div.should.equal(htmx.find(".c2")); + div.should.equal(htmx.find(".c1.c2")); + }); + + it('should find properly from elt', function(){ + var div = make("
    "); + htmx.find(div, "a").id.should.equal('a1'); + }); + + it('should find all properly', function(){ + var div = make("
    "); + htmx.findAll(".c1").length.should.equal(3); + htmx.findAll(".c2").length.should.equal(2); + htmx.findAll(".c3").length.should.equal(1); + }); + + it('should find all properly from elt', function(){ + var div = make("
    "); + htmx.findAll(div, ".c1").length.should.equal(3); + htmx.findAll(div, ".c2").length.should.equal(2); + htmx.findAll(div,".c3").length.should.equal(1); + }); + + it('should find closest element properly', function () { + var div = make("
    "); + var a = htmx.find(div, "a"); + htmx.closest(a, "div").should.equal(div); + }); + + it('should remove element properly', function () { + var div = make("
    "); + var a = htmx.find(div, "a"); + htmx.remove(a); + div.innerHTML.should.equal(""); + }); + + it('should remove element properly w/ selector', function () { + var div = make("
    "); + var a = htmx.find(div, "a"); + htmx.remove("#a1"); + div.innerHTML.should.equal(""); + }); + + it('should add class properly', function () { + var div = make("
    "); + div.classList.contains("foo").should.equal(false); + htmx.addClass(div, "foo"); + div.classList.contains("foo").should.equal(true); + }); + + + it('should add class properly w/ selector', function () { + var div = make("
    "); + div.classList.contains("foo").should.equal(false); + htmx.addClass("#div1", "foo"); + div.classList.contains("foo").should.equal(true); + }); + + it('should add class properly after delay', function (done) { + var div = make("
    "); + div.classList.contains("foo").should.equal(false); + htmx.addClass(div, "foo", 10); + div.classList.contains("foo").should.equal(false); + setTimeout(function () { + div.classList.contains("foo").should.equal(true); + done(); + }, 20); + }); + + it('should remove class properly', function () { + var div = make("
    "); + htmx.addClass(div, "foo"); + div.classList.contains("foo").should.equal(true); + htmx.removeClass(div, "foo"); + div.classList.contains("foo").should.equal(false); + }); + + it('should remove class properly w/ selector', function () { + var div = make("
    "); + htmx.addClass(div, "foo"); + div.classList.contains("foo").should.equal(true); + htmx.removeClass("#div1", "foo"); + div.classList.contains("foo").should.equal(false); + }); + + it('should add class properly after delay', function (done) { + var div = make("
    "); + htmx.addClass(div, "foo"); + div.classList.contains("foo").should.equal(true); + htmx.removeClass(div, "foo", 10); + div.classList.contains("foo").should.equal(true); + setTimeout(function () { + div.classList.contains("foo").should.equal(false); + done(); + }, 20); + }); + + it('should toggle class properly', function () { + var div = make("
    "); + div.classList.contains("foo").should.equal(false); + htmx.toggleClass(div, "foo"); + div.classList.contains("foo").should.equal(true); + htmx.toggleClass(div, "foo"); + div.classList.contains("foo").should.equal(false); + }); + + it('should toggle class properly w/ selector', function () { + var div = make("
    "); + div.classList.contains("foo").should.equal(false); + htmx.toggleClass("#div1", "foo"); + div.classList.contains("foo").should.equal(true); + htmx.toggleClass("#div1", "foo"); + div.classList.contains("foo").should.equal(false); + }); + + it('should take class properly', function () { + var div1 = make("
    "); + var div2 = make("
    "); + var div3 = make("
    "); + + div1.classList.contains("foo").should.equal(false); + div2.classList.contains("foo").should.equal(false); + div3.classList.contains("foo").should.equal(false); + + htmx.takeClass(div1, "foo"); + + div1.classList.contains("foo").should.equal(true); + div2.classList.contains("foo").should.equal(false); + div3.classList.contains("foo").should.equal(false); + + htmx.takeClass(div2, "foo"); + + div1.classList.contains("foo").should.equal(false); + div2.classList.contains("foo").should.equal(true); + div3.classList.contains("foo").should.equal(false); + + htmx.takeClass(div3, "foo"); + + div1.classList.contains("foo").should.equal(false); + div2.classList.contains("foo").should.equal(false); + div3.classList.contains("foo").should.equal(true); + }); + + it('should take class properly w/ selector', function () { + var div1 = make("
    "); + var div2 = make("
    "); + var div3 = make("
    "); + + div1.classList.contains("foo").should.equal(false); + div2.classList.contains("foo").should.equal(false); + div3.classList.contains("foo").should.equal(false); + + htmx.takeClass("#div1", "foo"); + + div1.classList.contains("foo").should.equal(true); + div2.classList.contains("foo").should.equal(false); + div3.classList.contains("foo").should.equal(false); + + htmx.takeClass("#div2", "foo"); + + div1.classList.contains("foo").should.equal(false); + div2.classList.contains("foo").should.equal(true); + div3.classList.contains("foo").should.equal(false); + + htmx.takeClass("#div3", "foo"); + + div1.classList.contains("foo").should.equal(false); + div2.classList.contains("foo").should.equal(false); + div3.classList.contains("foo").should.equal(true); + }); + + it('logAll works', function () { + var initialLogger = htmx.config.logger + try { + htmx.logAll(); + } finally { + htmx.config.logger = initialLogger; + } + }); + + it('eval can be suppressed', function () { + var calledEvent = false; + var handler = htmx.on("htmx:evalDisallowedError", function(){ + calledEvent = true; + }); + try { + htmx.config.allowEval = false; + should.equal(htmx._("tokenizeString"), undefined); + } finally { + htmx.config.allowEval = true; + htmx.off("htmx:evalDisallowedError", handler); + } + calledEvent.should.equal(true); + }); + + it('ajax api works', function() + { + this.server.respondWith("GET", "/test", "foo!"); + var div = make("
    "); + htmx.ajax("GET", "/test", div) + this.server.respond(); + div.innerHTML.should.equal("foo!"); + }); + + it('ajax api works by ID', function() + { + this.server.respondWith("GET", "/test", "foo!"); + var div = make("
    "); + htmx.ajax("GET", "/test", "#d1") + this.server.respond(); + div.innerHTML.should.equal("foo!"); + }); + + it('ajax api works with swapSpec', function() + { + this.server.respondWith("GET", "/test", "

    foo!

    "); + var div = make("
    "); + htmx.ajax("GET", "/test", {target: "#target", swap:"outerHTML"}); + this.server.respond(); + div.innerHTML.should.equal('

    foo!

    '); + }); + + it('ajax returns a promise', function(done) + { + // in IE we do not return a promise + if (typeof Promise !== "undefined") { + this.server.respondWith("GET", "/test", "foo!"); + var div = make("
    "); + var promise = htmx.ajax("GET", "/test", "#d1"); + this.server.respond(); + div.innerHTML.should.equal("foo!"); + promise.then(function(){ + done(); + }) + } else { + done(); + } + }); + + it('ajax api can pass parameters', function() + { + this.server.respondWith("POST", "/test", function (xhr) { + var params = getParameters(xhr); + params['i1'].should.equal("test"); + xhr.respond(200, {}, "Clicked!") + }); + var div = make("
    "); + htmx.ajax("POST", "/test", {target:"#d1", values:{i1: 'test'}}) + this.server.respond(); + div.innerHTML.should.equal("Clicked!"); + }); + +}) diff --git a/www/test/1.8.0/test/core/events.js b/www/test/1.8.0/test/core/events.js new file mode 100644 index 000000000..433f58623 --- /dev/null +++ b/www/test/1.8.0/test/core/events.js @@ -0,0 +1,587 @@ +describe("Core htmx Events", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it("htmx:load fires properly", function () { + var called = false; + var handler = htmx.on("htmx:load", function (evt) { + called = true; + }); + try { + this.server.respondWith("GET", "/test", ""); + this.server.respondWith("GET", "/test", "
    "); + var div = make("
    "); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:load", handler); + } + }); + + it("htmx:configRequest allows attribute addition", function () { + var handler = htmx.on("htmx:configRequest", function (evt) { + evt.detail.parameters['param'] = "true"; + }); + try { + var param = null; + this.server.respondWith("POST", "/test", function (xhr) { + param = getParameters(xhr)['param']; + xhr.respond(200, {}, ""); + }); + var div = make("
    "); + div.click(); + this.server.respond(); + param.should.equal("true"); + } finally { + htmx.off("htmx:configRequest", handler); + } + }); + + it("htmx:configRequest is also dispatched in kebab-case", function () { + var handler = htmx.on("htmx:config-request", function (evt) { + evt.detail.parameters['param'] = "true"; + }); + try { + var param = null; + this.server.respondWith("POST", "/test", function (xhr) { + param = getParameters(xhr)['param']; + xhr.respond(200, {}, ""); + }); + var div = make("
    "); + div.click(); + this.server.respond(); + param.should.equal("true"); + } finally { + htmx.off("htmx:config-request", handler); + } + }); + + it("events are only dispatched once if kebab and camel case match", function () { + var invoked = 0; + var handler = htmx.on("custom", function () { + invoked = invoked + 1 + }); + try { + var div = make("
    "); + htmx.trigger(div, "custom"); + invoked.should.equal(1); + } finally { + htmx.off("custom", handler); + } + }); + + it("htmx:configRequest allows attribute removal", function () { + var param = "foo"; + var handler = htmx.on("htmx:configRequest", function (evt) { + delete evt.detail.parameters['param']; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + param = getParameters(xhr)['param']; + xhr.respond(200, {}, ""); + }); + var div = make("
    "); + div.click(); + this.server.respond(); + should.equal(param, undefined); + } finally { + htmx.off("htmx:configRequest", handler); + } + }); + + it("htmx:configRequest allows header tweaking", function () { + var header = "foo"; + var handler = htmx.on("htmx:configRequest", function (evt) { + evt.detail.headers['X-My-Header'] = "bar"; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + header = xhr.requestHeaders['X-My-Header']; + xhr.respond(200, {}, ""); + }); + var div = make("
    "); + div.click(); + this.server.respond(); + should.equal(header, "bar"); + } finally { + htmx.off("htmx:configRequest", handler); + } + }); + + it("htmx:afterSwap is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterSwap", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterSwap", handler); + } + }); + + it("htmx:oobBeforeSwap is called before swap", function () { + var called = false; + var handler = htmx.on("htmx:oobBeforeSwap", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "
    Baz
    "); + }); + var oob = make('
    Blip
    '); + var div = make(""); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("Baz"); + should.equal(called, true); + } finally { + htmx.off("htmx:oobBeforeSwap", handler); + } + }); + + it("htmx:oobBeforeSwap can abort a swap", function () { + var called = false; + var handler = htmx.on("htmx:oobBeforeSwap", function (evt) { + called = true; + evt.preventDefault(); + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "
    Baz
    "); + }); + var oob = make('
    Blip
    '); + var div = make(""); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("Blip"); + should.equal(called, true); + } finally { + htmx.off("htmx:oobBeforeSwap", handler); + } + }); + + + it("htmx:oobBeforeSwap is not called on an oob miss", function () { + var called = false; + var handler = htmx.on("htmx:oobBeforeSwap", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "
    Baz
    "); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, false); + } finally { + htmx.off("htmx:oobBeforeSwap", handler); + } + }); + + it("htmx:oobAfterSwap is called after swap", function () { + var called = false; + var handler = htmx.on("htmx:oobAfterSwap", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "
    Baz
    "); + }); + var oob = make('
    Blip
    '); + var div = make(""); + div.click(); + this.server.respond(); + byId("d1").innerHTML.should.equal("Baz"); + should.equal(called, true); + } finally { + htmx.off("htmx:oobAfterSwap", handler); + } + }); + + it("htmx:oobAfterSwap is not called on an oob miss", function () { + var called = false; + var handler = htmx.on("htmx:oobAfterSwap", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "
    Baz
    "); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, false); + } finally { + htmx.off("htmx:oobAfterSwap", handler); + } + }); + + it("htmx:afterSettle is called once when replacing outerHTML", function () { + var called = 0; + var handler = htmx.on("htmx:afterSettle", function (evt) { + called++; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, 1); + } finally { + htmx.off("htmx:afterSettle", handler); + } + }); + + it("htmx:afterSettle is called once when replacing outerHTML with whitespace", function () { + var called = 0; + var handler = htmx.on("htmx:afterSettle", function (evt) { + called++; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "\n"); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, 1); + } finally { + htmx.off("htmx:afterSettle", handler); + } + }); + + it("htmx:afterSettle is called twice when replacing outerHTML with whitespace separated elements", function () { + var called = 0; + var handler = htmx.on("htmx:afterSettle", function (evt) { + called++; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, "\n Foo"); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, 2); + } finally { + htmx.off("htmx:afterSettle", handler); + } + }); + + it("htmx:afterRequest is called after a successful request", function () { + var called = false; + var handler = htmx.on("htmx:afterRequest", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterRequest", handler); + } + }); + + it("htmx:afterOnLoad is called after a successful request", function () { + var called = false; + var handler = htmx.on("htmx:afterOnLoad", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterOnLoad", handler); + } + }); + + it("htmx:afterRequest is called after a failed request", function () { + var called = false; + var handler = htmx.on("htmx:afterRequest", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(500, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterRequest", handler); + } + }); + + it("htmx:sendError is called after a failed request", function (done) { + var called = false; + var handler = htmx.on("htmx:sendError", function (evt) { + called = true; + }); + this.server.restore(); // turn off server mock so connection doesn't work + var div = make(""); + div.click(); + setTimeout(function () { + htmx.off("htmx:sendError", handler); + should.equal(called, true); + done(); + }, 30); + }); + + it("htmx:afterRequest is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterRequest", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterRequest", handler); + } + }); + + it("htmx:afterOnLoad is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterOnLoad", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterOnLoad", handler); + } + }); + + it("htmx:beforeProcessNode is called when replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:beforeProcessNode", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:beforeProcessNode", handler); + } + }); + + it("htmx:beforeProcessNode allows htmx attribute tweaking", function () { + var called = false; + var handler = htmx.on("htmx:beforeProcessNode", function (evt) { + evt.target.setAttribute("hx-post", "/success") + called = true; + }); + try { + this.server.respondWith("POST", "/success", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:beforeProcessNode", handler); + } + }); + + it("htmx:afterProcessNode is called after replacing outerHTML", function () { + var called = false; + var handler = htmx.on("htmx:afterProcessNode", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterProcessNode", handler); + } + }); + + it("htmx:afterRequest is called when targeting a parent div", function () { + var called = false; + var handler = htmx.on("htmx:afterRequest", function (evt) { + called = true; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make("
    "); + var button = byId('b1'); + button.click(); + this.server.respond(); + should.equal(called, true); + } finally { + htmx.off("htmx:afterRequest", handler); + } + }); + + it("adding an error in htmx:configRequest stops the request", function () { + try { + var handler = htmx.on("htmx:configRequest", function (evt) { + evt.detail.errors.push("An error"); + }); + var request = false; + this.server.respondWith("POST", "/test", function (xhr) { + request = true; + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(request, false); + } finally { + htmx.off("htmx:configRequest", handler); + } + }); + + it("preventDefault() in htmx:configRequest stops the request", function () { + try { + var handler = htmx.on("htmx:configRequest", function (evt) { + evt.detail.errors.push("An error"); + }); + var request = false; + this.server.respondWith("POST", "/test", function (xhr) { + request = true; + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(request, false); + } finally { + htmx.off("htmx:configRequest", handler); + } + }); + + it("preventDefault() in the htmx:beforeRequest event cancels the request", function () { + try { + var handler = htmx.on("htmx:beforeRequest", function (evt) { + evt.preventDefault(); + }); + var request = false; + this.server.respondWith("POST", "/test", function (xhr) { + request = true; + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(request, false); + } finally { + htmx.off("htmx:beforeRequest", handler); + } + }); + + it("preventDefault() in the htmx:beforeOnLoad event cancels the swap", function () { + try { + var handler = htmx.on("htmx:beforeOnLoad", function (evt) { + evt.preventDefault(); + }); + var request = false; + this.server.respondWith("POST", "/test", function (xhr) { + request = true; + xhr.respond(200, {}, "Bar"); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(request, true); + div.innerText.should.equal("Foo"); + } finally { + htmx.off("htmx:beforeOnLoad", handler); + } + }); + + it("htmx:afterRequest event contains 'successful' and 'failed' properties indicating success after successful request", function () { + var successful = false; + var failed = true; + var handler = htmx.on("htmx:afterRequest", function (evt) { + successful = evt.detail.successful; + failed = evt.detail.failed; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(200, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(successful, true); + should.equal(failed, false); + } finally { + htmx.off("htmx:afterRequest", handler); + } + }); + + it("htmx:afterRequest event contains 'successful' and 'failed' properties indicating failure after failed request", function () { + var successful = true; + var failed = false; + var handler = htmx.on("htmx:afterRequest", function (evt) { + successful = evt.detail.successful; + failed = evt.detail.failed; + }); + try { + this.server.respondWith("POST", "/test", function (xhr) { + xhr.respond(500, {}, ""); + }); + var div = make(""); + div.click(); + this.server.respond(); + should.equal(successful, false); + should.equal(failed, true); + } finally { + htmx.off("htmx:afterRequest", handler); + } + }); + + +}); diff --git a/www/test/1.8.0/test/core/headers.js b/www/test/1.8.0/test/core/headers.js new file mode 100644 index 000000000..e7d1ec258 --- /dev/null +++ b/www/test/1.8.0/test/core/headers.js @@ -0,0 +1,229 @@ +describe("Core htmx AJAX headers", function () { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it("should include the HX-Request header", function () { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.requestHeaders['HX-Request'].should.be.equal('true'); + xhr.respond(200, {}, ""); + }); + var div = make('
    '); + div.click(); + this.server.respond(); + }) + + it("should include the HX-Trigger header", function () { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.requestHeaders['HX-Trigger'].should.equal('d1'); + xhr.respond(200, {}, ""); + }); + var div = make('
    '); + div.click(); + this.server.respond(); + }) + + it("should include the HX-Trigger-Name header", function () { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.requestHeaders['HX-Trigger-Name'].should.equal('n1'); + xhr.respond(200, {}, ""); + }); + var div = make(''); + div.click(); + this.server.respond(); + }) + + it("should include the HX-Target header", function () { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.requestHeaders['HX-Target'].should.equal('d1'); + xhr.respond(200, {}, ""); + }); + var div = make('
    '); + div.click(); + this.server.respond(); + }) + + it("should handle simple string HX-Trigger response header properly", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "foo"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("foo", function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should handle dot path HX-Trigger response header properly", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "foo.bar"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("foo.bar", function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should handle simple string HX-Trigger response header in different case properly", function () { + this.server.respondWith("GET", "/test", [200, {"hx-trigger": "foo"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("foo", function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should handle a namespaced HX-Trigger response header properly", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "namespace:foo"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("namespace:foo", function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should handle basic JSON HX-Trigger response header properly", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "{\"foo\":null}"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("foo", function (evt) { + invokedEvent = true; + should.equal(null, evt.detail.value); + evt.detail.elt.should.equal(div); + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should handle JSON with array arg HX-Trigger response header properly", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "{\"foo\":[1, 2, 3]}"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("foo", function (evt) { + invokedEvent = true; + evt.detail.elt.should.equal(div); + evt.detail.value.should.deep.equal([1, 2, 3]); + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should handle JSON with array arg HX-Trigger response header properly", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "{\"foo\":{\"a\":1, \"b\":2}}"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + div.addEventListener("foo", function (evt) { + invokedEvent = true; + evt.detail.elt.should.equal(div); + evt.detail.a.should.equal(1); + evt.detail.b.should.equal(2); + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + }) + + it("should survive malformed JSON in HX-Trigger response header", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "{not: valid}"}, ""]); + + var div = make('
    '); + div.click(); + this.server.respond(); + }) + + it("should handle simple string HX-Trigger response header properly w/ outerHTML swap", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger": "foo"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + var handler = htmx.on('foo', function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + htmx.off('foo', handler); + }) + + it("should handle HX-Retarget", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Retarget": "#d2"}, "Result"]); + + var div1 = make('
    '); + var div2 = make('
    '); + div1.click(); + this.server.respond(); + div1.innerHTML.should.equal(""); + div2.innerHTML.should.equal("Result"); + }) + + it("should handle HX-Reswap", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Reswap": "innerHTML"}, "Result"]); + + var div1 = make('
    '); + div1.click(); + this.server.respond(); + div1.innerHTML.should.equal("Result"); + }) + + it("should handle simple string HX-Trigger-After-Swap response header properly w/ outerHTML swap", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger-After-Swap": "foo"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + var handler = htmx.on('foo', function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + htmx.off('foo', handler); + }) + + it("should handle simple string HX-Trigger-After-Settle response header properly w/ outerHTML swap", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Trigger-After-Settle": "foo"}, ""]); + + var div = make('
    '); + var invokedEvent = false; + var handler = htmx.on('foo', function (evt) { + invokedEvent = true; + }); + div.click(); + this.server.respond(); + invokedEvent.should.equal(true); + htmx.off('foo', handler); + }) + + + it("should change body content on HX-Location", function () { + this.server.respondWith("GET", "/test", [200, {"HX-Location": '{"path":"/test2", "target":"#testdiv"}'}, ""]); + this.server.respondWith("GET", "/test2", [200, {}, "
    Yay! Welcome
    "]); + var div = make('
    '); + div.click(); + this.server.respond(); + this.server.respond(); + div.innerHTML.should.equal('
    Yay! Welcome
    '); + }) +}); diff --git a/www/test/1.8.0/test/core/internals.js b/www/test/1.8.0/test/core/internals.js new file mode 100644 index 000000000..2da08457d --- /dev/null +++ b/www/test/1.8.0/test/core/internals.js @@ -0,0 +1,135 @@ +describe("Core htmx internals Tests", function() { + + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it("makeFragment works with janky stuff", function(){ + htmx._("makeFragment")("").tagName.should.equal("BODY"); + htmx._("makeFragment")("").tagName.should.equal("BODY"); + + //NB - the tag name should be the *parent* element hosting the HTML since we use the fragment children + // for the swap + htmx._("makeFragment")("").tagName.should.equal("TR"); + htmx._("makeFragment")("").tagName.should.equal("TABLE"); + htmx._("makeFragment")("").tagName.should.equal("COLGROUP"); + htmx._("makeFragment")("").tagName.should.equal("TBODY"); + }) + + it("makeFragment works with template wrapping", function(){ + htmx.config.useTemplateFragments = true; + try { + htmx._("makeFragment")("").children.length.should.equal(0); + htmx._("makeFragment")("").children.length.should.equal(0); + + var fragment = htmx._("makeFragment")(""); + fragment.firstElementChild.tagName.should.equal("TD"); + + fragment = htmx._("makeFragment")(""); + fragment.firstElementChild.tagName.should.equal("THEAD"); + + fragment = htmx._("makeFragment")(""); + fragment.firstElementChild.tagName.should.equal("COL"); + + fragment = htmx._("makeFragment")(""); + fragment.firstElementChild.tagName.should.equal("TR"); + + } finally { + htmx.config.useTemplateFragments = false; + } + }) + + + it("makeFragment works with template wrapping and funky combos", function(){ + htmx.config.useTemplateFragments = true; + try { + var fragment = htmx._("makeFragment")("
    "); + fragment.children[0].tagName.should.equal("TD"); + fragment.children[1].tagName.should.equal("DIV"); + } finally { + htmx.config.useTemplateFragments = false; + } + }) + + it("set header works with non-ASCII values", function(){ + var xhr = new XMLHttpRequest(); + xhr.open("GET", "/dummy"); + htmx._("safelySetHeaderValue")(xhr, "Example", "привет"); + // unfortunately I can't test the value :/ + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + }) + + it("handles parseInterval correctly", function() { + chai.expect(htmx.parseInterval("1ms")).to.be.equal(1); + chai.expect(htmx.parseInterval("300ms")).to.be.equal(300); + chai.expect(htmx.parseInterval("1s")).to.be.equal(1000) + chai.expect(htmx.parseInterval("1.5s")).to.be.equal(1500) + chai.expect(htmx.parseInterval("2s")).to.be.equal(2000) + + chai.expect(htmx.parseInterval(null)).to.be.undefined + chai.expect(htmx.parseInterval("")).to.be.undefined + chai.expect(htmx.parseInterval("undefined")).to.be.undefined + chai.expect(htmx.parseInterval("true")).to.be.undefined + chai.expect(htmx.parseInterval("false")).to.be.undefined + }) + + it("tokenizes correctly", function() { + chai.expect(htmx._("tokenizeString")("a,")).to.be.deep.equal(['a', ',']); + chai.expect(htmx._("tokenizeString")("aa,")).to.be.deep.equal(['aa', ',']); + chai.expect(htmx._("tokenizeString")("aa,aa")).to.be.deep.equal(['aa', ',', 'aa']); + chai.expect(htmx._("tokenizeString")("aa.aa")).to.be.deep.equal(['aa', '.', 'aa']); + }) + + it("tags respond correctly to shouldCancel", function() { + var anchorThatShouldCancel = make(""); + htmx._("shouldCancel")({type:'click'}, anchorThatShouldCancel).should.equal(true); + + var anchorThatShouldCancel = make(""); + htmx._("shouldCancel")({type:'click'}, anchorThatShouldCancel).should.equal(true); + + var anchorThatShouldNotCancel = make(""); + htmx._("shouldCancel")({type:'click'}, anchorThatShouldNotCancel).should.equal(false); + + var form = make("
    "); + htmx._("shouldCancel")({type:'submit'}, form).should.equal(true); + + var form = make("
    "); + var input = byId("i1"); + htmx._("shouldCancel")({type:'click'}, input).should.equal(true); + + var form = make("
    '); + var input = byId('i1'); + var button = byId('b1'); + button.focus(); + var vals = htmx._('getInputValues')(form).values; + vals['foo'].should.equal('bar'); + vals['do'].should.equal('rey'); + vals['btn'].should.equal('bar'); + }) + + it('form includes last focused submit', function () { + var form = make('
    '); + var input = byId('i1'); + var button = byId('s1'); + button.focus(); + var vals = htmx._('getInputValues')(form).values; + vals['foo'].should.equal('bar'); + vals['do'].should.equal('rey'); + vals['s1'].should.equal('bar'); + }) + + it('form does not include button when focus is lost', function () { + var form = make('
    '); + var input = byId('i1'); + var button = byId('s1'); + button.focus(); + input.focus(); + var vals = htmx._('getInputValues')(form).values; + vals['foo'].should.equal('bar'); + vals['do'].should.equal('rey'); + should.equal(vals['s1'], undefined); + }) + + it('form does not include button when focus is lost outside of form', function () { + var form = make('
    '); + var anchor = make(''); + var button = byId('s1'); + button.focus(); + anchor.focus(); + var vals = htmx._('getInputValues')(form).values; + vals['foo'].should.equal('bar'); + vals['do'].should.equal('rey'); + should.equal(vals['s1'], undefined); + }) + +}); + diff --git a/www/test/1.8.0/test/core/perf.js b/www/test/1.8.0/test/core/perf.js new file mode 100644 index 000000000..c461aee17 --- /dev/null +++ b/www/test/1.8.0/test/core/perf.js @@ -0,0 +1,64 @@ +describe("Core htmx perf Tests", function() { + + var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache"; + + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); + }); + + function stringRepeat(str, num) { + num = Number(num); + + var result = ''; + while (true) { + if (num & 1) { // (1) + result += str; + } + num >>>= 1; // (2) + if (num <= 0) break; + str += str; + } + + return result; + } + + + it("history implementation should be fast", function(){ + // create an entry with a large content string (256k) and see how fast we can write and read it + // to local storage as a single entry + var entry = {url: stringRepeat("x", 32), content:stringRepeat("x", 256*1024)} + var array = []; + for (var i = 0; i < 10; i++) { + array.push(entry); + } + var start = performance.now(); + var string = JSON.stringify(array); + localStorage.setItem(HTMX_HISTORY_CACHE_NAME, string); + var reReadString = localStorage.getItem(HTMX_HISTORY_CACHE_NAME); + var finalJson = JSON.parse(reReadString); + var end = performance.now(); + var timeInMs = end - start; + chai.assert(timeInMs < 300, "Should take less than 300ms on most platforms"); + }) + + it("history snapshot cleaning should be fast", function(){ + // + var workArea = getWorkArea(); + var html = "
    Yay, really large HTML documents are fun!
    \n"; + html = stringRepeat(html, 5 * 1024); // ~350K in size, about the size of CNN's body tag :p + workArea.insertAdjacentHTML("beforeend", html) + var start = performance.now(); + htmx._("cleanInnerHtmlForHistory")(workArea); + var end = performance.now(); + var timeInMs = end - start; + chai.assert(timeInMs < 50, "Should take less than 50ms on most platforms"); + }) + +}) \ No newline at end of file diff --git a/www/test/1.8.0/test/core/regressions.js b/www/test/1.8.0/test/core/regressions.js new file mode 100644 index 000000000..d998f16ae --- /dev/null +++ b/www/test/1.8.0/test/core/regressions.js @@ -0,0 +1,188 @@ +describe("Core htmx Regression Tests", function(){ + + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('SVGs process properly in IE11', function() + { + var btn = make('\n' + + '\n' + + '\n' + + '\n' + + '') + }); + + it ('Handles https://github.com/bigskysoftware/htmx/issues/4 properly', function() { + this.server.respondWith("GET", "/index2a.php", + "
    I came from message oob swap I should be second
    " + + "
    I came from a message2 oob swap I should be third but I am in the wrong spot
    " + + "I'm page2 content (non-swap) I should be first") + + var h1 = make("" + + "
    " + + "
    " + + "
    " + + "

    Kutty CLICK ME

    ") + h1.click(); + this.server.respond(); + htmx.find("#page2").innerHTML.should.equal("I'm page2 content (non-swap) I should be first") + htmx.find("#message").innerHTML.should.equal("I came from message oob swap I should be second") + htmx.find("#message2").innerHTML.should.equal("I came from a message2 oob swap I should be third but I am in the wrong spot") + }); + + it ('Handles https://github.com/bigskysoftware/htmx/issues/33 "empty values" properly', function() { + this.server.respondWith("POST", "/htmx.php", function (xhr) { + xhr.respond(200, {}, xhr.requestBody); + }); + + var form = make('
    \n' + + '\n' + + '\n' + + '
    ') + form.click(); + this.server.respond(); + form.innerHTML.should.equal("variable=") + }); + + it ('name=id doesnt cause an error', function(){ + this.server.respondWith("GET", "/test", "Foo
    ") + var div = make('
    Get It
    ') + div.click(); + this.server.respond(); + div.innerText.should.contain("Foo") + }); + + it ('empty id doesnt cause an error', function(){ + this.server.respondWith("GET", "/test", "Foo\n
    ") + var div = make('
    Get It
    ') + div.click(); + this.server.respond(); + div.innerText.should.contain("Foo") + }); + + it ('id with dot in value doesnt cause an error', function(){ + this.server.respondWith("GET", "/test", "Foo
    "); + var div = make('
    Get It
    '); + div.click(); + this.server.respond(); + div.innerText.should.contain("Foo"); + }); + + it ('@ symbol in attributes does not break requests', function(){ + this.server.respondWith("GET", "/test", "
    Foo
    "); + var div = make('
    Get It
    '); + div.click(); + this.server.respond(); + byId("d1").getAttribute('@foo').should.equal('bar'); + }); + + it ('@ symbol in attributes does not break attribute settling requests', function(){ + this.server.respondWith("GET", "/test", "
    Foo
    "); + var div = make('
    Foo
    '); + div.click(); + this.server.respond(); + byId("d1").getAttribute('@foo').should.equal('bar'); + }); + + it ('selected element with ID does not cause NPE when it disappears', function(){ + this.server.respondWith("GET", "/test", "
    Replaced
    "); + var input = make(''); + input.focus(); + input.click(); + this.server.respond(); + byId("d1").innerText.should.equal('Replaced'); + }); + + it('does not submit with a false condition on a form', function() { + this.server.respondWith("POST", "/test", "Submitted"); + var defaultPrevented = false; + htmx.on("click", function(evt) { + defaultPrevented = evt.defaultPrevented; + }) + var form = make('
    '); + form.click() + this.server.respond(); + defaultPrevented.should.equal(true); + }) + + it('two elements can listen for the same event on another element', function() { + this.server.respondWith("GET", "/test", "triggered"); + + make('
    ' + + '
    '); + + + var div1 = byId("d1"); + var div2 = byId("d2"); + + document.body.click(); + this.server.respond(); + + div2.innerHTML.should.equal("triggered"); + div1.innerHTML.should.equal("triggered"); + }) + + it('a form can reset based on the htmx:afterRequest event', function() { + this.server.respondWith("POST", "/test", "posted"); + //htmx.logAll(); + + var form = make('
    ' + + ' ' + + ' ' + + '
    '); + htmx.trigger(form, "htmx:load"); // have to manually trigger the load event for non-AJAX dynamic content + + var div1 = byId("d1"); + var input = byId("i1"); + input.value = "foo"; + var submit = byId("s1"); + + input.value.should.equal("foo"); + submit.click(); + this.server.respond(); + + div1.innerHTML.should.equal("posted"); + input.value.should.equal(""); // form should be reset + }) + + it('supports image maps', function() { + this.server.respondWith("GET", "/test", "triggered"); + + make('
    ' + + '
    ' + + ' ' + + '' + + ' ' + + ' Computer' + + ' ' + + '
    '); + + var div1 = byId("d1"); + var area = document.getElementsByTagName('area')[0]; + + area.click(); + this.server.respond(); + + div1.innerHTML.should.equal("triggered"); + }) + + it("supports unset on hx-select", function(){ + this.server.respondWith("GET", "/test", "FooBar"); + htmx.logAll(); + make('
    \n' + + ' \n' + + '
    ') + var btn = byId("b1"); + btn.click() + this.server.respond(); + + btn.innerText.should.equal("FooBar"); + }) + +}) diff --git a/www/test/1.8.0/test/core/security.js b/www/test/1.8.0/test/core/security.js new file mode 100644 index 000000000..d4be72162 --- /dev/null +++ b/www/test/1.8.0/test/core/security.js @@ -0,0 +1,32 @@ +describe("security options", function() { + + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it("can disable a single elt", function(){ + this.server.respondWith("GET", "/test", "Clicked!"); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Initial"); + }) + + it("can disable a parent elt", function(){ + this.server.respondWith("GET", "/test", "Clicked!"); + + var div = make('
    ') + var btn = byId("b1"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Initial"); + }) + + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/core/tokenizer.js b/www/test/1.8.0/test/core/tokenizer.js new file mode 100644 index 000000000..3071ad9b1 --- /dev/null +++ b/www/test/1.8.0/test/core/tokenizer.js @@ -0,0 +1,48 @@ +describe("Core htmx tokenizer tests", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + function tokenize(str) { + return htmx._("tokenizeString")(str); + } + + function tokenizeTest(str, result) { + return tokenize(str).should.deep.equal(result); + } + + it('tokenizes properly', function() + { + tokenizeTest("", []); + tokenizeTest(" ", [" ", " "]); + tokenizeTest("(", ["("]); + tokenizeTest("()", ["(", ")"]); + tokenizeTest("(,)", ["(", ",", ")"]); + tokenizeTest(" ( ) ", [" ", "(", " ", ")", " "]); + tokenizeTest(" && ) ", [" ", "&", "&", " ", ")", " "]); + tokenizeTest(" && ) 'asdf'", [" ", "&", "&", " ", ")", " ", "'asdf'"]); + tokenizeTest(" && ) ',asdf'", [" ", "&", "&", " ", ")", " ", "',asdf'"]); + tokenizeTest('",asdf"', ['",asdf"']); + tokenizeTest('&& ) ",asdf"', ["&", "&", " ", ")", " ", '",asdf"']); + }); + + it('generates conditionals property', function() + { + var tokens = tokenize("[code==4||(code==5&&foo==true)]"); + var conditional = htmx._("maybeGenerateConditional")(null, tokens); + var func = eval(conditional); + func({code: 5, foo: true}).should.equal(true); + func({code: 5, foo: false}).should.equal(false); + func({code: 4, foo: false}).should.equal(true); + func({code: 3, foo: true}).should.equal(false); + }); + + + + +}) diff --git a/www/test/1.8.0/test/core/validation.js b/www/test/1.8.0/test/core/validation.js new file mode 100644 index 000000000..59c5ca463 --- /dev/null +++ b/www/test/1.8.0/test/core/validation.js @@ -0,0 +1,174 @@ +describe("Core htmx client side validation tests", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('HTML5 required validation error prevents request', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + form.textContent.should.equal("No Request"); + form.click(); + this.server.respond(); + form.textContent.should.equal("No Request"); + byId("i1").value = "foo"; + form.click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('Novalidate skips form validation', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + form.textContent.should.equal("No Request"); + form.click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('Validation skipped for indirect form submission', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '' + + '
    '); + form.textContent.should.equal("No Request"); + byId("button").click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('Formnovalidate skips form validation', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '' + + '
    '); + form.textContent.should.equal("No Request"); + byId("button").click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('HTML5 pattern validation error prevents request', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + byId("i1").value = "xyz"; + form.textContent.should.equal("No Request"); + form.click(); + this.server.respond(); + form.textContent.should.equal("No Request"); + byId("i1").value = "abc"; + form.click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('Custom validation error prevents request', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + byId("i1").setCustomValidity("Nope"); + form.textContent.should.equal("No Request"); + form.click(); + this.server.respond(); + form.textContent.should.equal("No Request"); + byId("i1").setCustomValidity(""); + form.click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('hyperscript validation error prevents request', function() + { + this.server.respondWith("POST", "/test", "Clicked!"); + + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + htmx.trigger(form, "htmx:load"); + byId("i1").value = "boo"; + form.textContent.should.equal("No Request"); + form.click(); + this.server.respond(); + form.textContent.should.equal("No Request"); + byId("i1").value = "foo"; + form.click(); + this.server.respond(); + form.textContent.should.equal("Clicked!"); + }); + + it('calls htmx:validation:failed on failure', function() + { + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + var calledEvent = false; + var handler = htmx.on(form, "htmx:validation:failed", function(){ + calledEvent = true; + }); + try { + form.click(); + this.server.respond(); + } finally { + htmx.off(form, handler); + } + calledEvent.should.equal(true); + }); + + it('calls htmx:validation:halted on failure', function() + { + var form = make('
    ' + + 'No Request' + + '' + + '
    '); + var errors = null; + var handler = htmx.on(form, "htmx:validation:halted", function(evt){ + errors = evt.detail.errors; + }); + try { + form.click(); + this.server.respond(); + } finally { + htmx.off(form, handler); + } + errors.length.should.equal(1); + byId("i1").should.equal(errors[0].elt); + errors[0].validity.valueMissing.should.equal(true); + }); + + + +}) diff --git a/www/test/1.8.0/test/core/verbs.js b/www/test/1.8.0/test/core/verbs.js new file mode 100644 index 000000000..1d0711c2f --- /dev/null +++ b/www/test/1.8.0/test/core/verbs.js @@ -0,0 +1,44 @@ +describe("Core htmx AJAX Verbs", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('handles basic posts properly', function () { + this.server.respondWith("POST", "/test", "post"); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("post"); + }) + + it('handles basic put properly', function () { + this.server.respondWith("PUT", "/test", "put"); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("put"); + }) + + it('handles basic patch properly', function () { + this.server.respondWith("PATCH", "/test", "patch"); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("patch"); + }) + + it('handles basic delete properly', function () { + this.server.respondWith("DELETE", "/test", "delete"); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + div.innerHTML.should.equal("delete"); + }) + +}); + diff --git a/www/test/1.8.0/test/ext/ajax-header.js b/www/test/1.8.0/test/ext/ajax-header.js new file mode 100644 index 000000000..0888ef3b5 --- /dev/null +++ b/www/test/1.8.0/test/ext/ajax-header.js @@ -0,0 +1,21 @@ +describe("ajax-header extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('Sends the X-Requested-With header', function () { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, xhr.requestHeaders['X-Requested-With']) + }); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("XMLHttpRequest"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/bad-extension.js b/www/test/1.8.0/test/ext/bad-extension.js new file mode 100644 index 000000000..fcf6a4384 --- /dev/null +++ b/www/test/1.8.0/test/ext/bad-extension.js @@ -0,0 +1,27 @@ +describe("bad extension", function() { + htmx.defineExtension("bad-extension", { + onEvent : function(name, evt) {throw "onEvent"}, + transformResponse : function(text, xhr, elt) {throw "transformRequest"}, + isInlineSwap : function(swapStyle) {throw "isInlineSwap"}, + handleSwap : function(swapStyle, target, fragment, settleInfo) {throw "handleSwap"}, + encodeParameters : function(xhr, parameters, elt) {throw "encodeParmeters"} + } + ) + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('does not blow up rendering', function () { + this.server.respondWith("GET", "/test", "clicked!"); + var div = make('
    Click Me!
    ') + div.click(); + this.server.respond(); + div.innerHTML.should.equal("clicked!"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/class-tools.js b/www/test/1.8.0/test/ext/class-tools.js new file mode 100644 index 000000000..0c5004b56 --- /dev/null +++ b/www/test/1.8.0/test/ext/class-tools.js @@ -0,0 +1,55 @@ +describe("class-tools extension", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('adds classes properly', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.length, 0); + setTimeout(function(){ + should.equal(div.classList.contains("c1"), true); + done(); + }, 100); + }); + + it('removes classes properly', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.contains("foo"), true); + should.equal(div.classList.contains("bar"), true); + setTimeout(function(){ + should.equal(div.classList.contains("foo"), true); + should.equal(div.classList.contains("bar"), false); + done(); + }, 100); + }); + + it('adds classes properly w/ data-* prefix', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.length, 0); + setTimeout(function(){ + should.equal(div.classList.contains("c1"), true); + done(); + }, 100); + }); + + it('extension can be on parent', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.length, 0); + setTimeout(function(){ + should.equal(div.classList.contains("c1"), false); + should.equal(byId("d1").classList.contains("c1"), true); + done(); + }, 100); + }); + + +}) diff --git a/www/test/1.8.0/test/ext/client-side-templates.js b/www/test/1.8.0/test/ext/client-side-templates.js new file mode 100644 index 000000000..29206d21b --- /dev/null +++ b/www/test/1.8.0/test/ext/client-side-templates.js @@ -0,0 +1,30 @@ +describe("client-side-templates extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('works on basic mustache template', function () { + this.server.respondWith("GET", "/test", '{"foo":"bar"}'); + var btn = make('') + make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("*bar*"); + }); + + it('works on basic handlebars template', function () { + this.server.respondWith("GET", "/test", '{"foo":"bar"}'); + var btn = make('') + Handlebars.partials["hb1"] = Handlebars.compile("*{{foo}}*"); + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("*bar*"); + }); + + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/debug.js b/www/test/1.8.0/test/ext/debug.js new file mode 100644 index 000000000..4eeca295e --- /dev/null +++ b/www/test/1.8.0/test/ext/debug.js @@ -0,0 +1,19 @@ +describe("debug extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('works on basic request', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/disable-element.js b/www/test/1.8.0/test/ext/disable-element.js new file mode 100644 index 000000000..cde1ad229 --- /dev/null +++ b/www/test/1.8.0/test/ext/disable-element.js @@ -0,0 +1,62 @@ +describe("disable-element extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('disables the triggering element during htmx request', function () { + // GIVEN: + // - A button triggering an htmx request with disable-element extension + // - The button is enabled + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}) + }); + var btn = make('') + btn.disabled.should.equal(false); + + // WHEN clicking + btn.click(); + + // THEN it's disabled + btn.disabled.should.equal(true); + + // WHEN server response has arrived + this.server.respond(); + + // THEN it's re-enabled + btn.disabled.should.equal(false); + }); + + it('disables the designated element during htmx request', function () { + // GIVEN: + // - A button triggering an htmx request with disable-element extension + // - Another button that needs to be disabled during the htmx request + // - Both buttons are enabled + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}) + }); + var btn = make('') + var btn2 = make('') + btn.disabled.should.equal(false); + btn2.disabled.should.equal(false); + + // WHEN clicking + btn.click(); + + // THEN it's not disabled, but the other one is + btn.disabled.should.equal(false); + btn2.disabled.should.equal(true); + + // WHEN server response has arrived + this.server.respond(); + + // THEN both buttons are back enabled + btn.disabled.should.equal(false); + btn2.disabled.should.equal(false); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/event-header.js b/www/test/1.8.0/test/ext/event-header.js new file mode 100644 index 000000000..a5a66cf58 --- /dev/null +++ b/www/test/1.8.0/test/ext/event-header.js @@ -0,0 +1,23 @@ +describe("event-header extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('Sends the Triggering-Event header', function () { + this.server.respondWith("GET", "/test", function (xhr) { + xhr.respond(200, {}, xhr.requestHeaders['Triggering-Event']) + }); + var btn = make('') + btn.click(); + this.server.respond(); + var json = JSON.parse(btn.innerText); + json.type.should.equal("click"); + json.target.should.equal("button"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/extension-swap.js b/www/test/1.8.0/test/ext/extension-swap.js new file mode 100644 index 000000000..0186b5250 --- /dev/null +++ b/www/test/1.8.0/test/ext/extension-swap.js @@ -0,0 +1,60 @@ +describe("default extensions behavior", function() { + + var loadCalls, afterSwapCalls, afterSettleCalls; + + beforeEach(function () { + loadCalls = []; + this.server = makeServer(); + clearWorkArea(); + + htmx.defineExtension("ext-testswap", { + onEvent : function(name, evt) { + if (name === "htmx:load") { + loadCalls.push(evt.detail.elt); + } + }, + handleSwap: function (swapStyle, target, fragment, settleInfo) { + // simple outerHTML replacement for tests + var parentEl = target.parentElement; + parentEl.removeChild(target); + return [parentEl.appendChild(fragment)]; // return the newly added element + } + + }); + + }); + + afterEach(function () { + this.server.restore(); + clearWorkArea(); + htmx.removeExtension("ext-testswap"); + }); + + it('handleSwap: afterSwap and afterSettle triggered if extension defined on parent', function () { + this.server.respondWith("GET", "/test", ''); + var div = make('
    '); + var btn = div.firstChild; + btn.click() + this.server.respond(); + loadCalls.length.should.equal(1); + loadCalls[0].textContent.should.equal('Clicked!'); // the new button is loaded + }); + + it('handleSwap: new content is handled by htmx', function() { + this.server.respondWith("GET", "/test", ''); + this.server.respondWith("GET", "/test-inner", 'Loaded!'); + make('
    ').querySelector('button').click(); + + this.server.respond(); // call /test via button trigger=click + var btn = byId('test-ext-testswap'); + btn.textContent.should.equal('Clicked!'); + loadCalls.length.should.equal(1); + loadCalls[0].textContent.should.equal('Clicked!'); // the new button is loaded + + this.server.respond(); // call /test-inner via span trigger=load + btn.textContent.should.equal("Clicked!Loaded!"); + loadCalls.length.should.equal(2); + loadCalls[1].textContent.should.equal('Loaded!'); // the new span is loaded + }); + +}); diff --git a/www/test/1.8.0/test/ext/hyperscript.js b/www/test/1.8.0/test/ext/hyperscript.js new file mode 100644 index 000000000..6d49a2dae --- /dev/null +++ b/www/test/1.8.0/test/ext/hyperscript.js @@ -0,0 +1,64 @@ +describe("hyperscript integration", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('can trigger with a custom event', function () { + this.server.respondWith("GET", "/test", "Custom Event Sent!"); + var btn = make('') + htmx.trigger(btn, "htmx:load"); // have to manually trigger the load event for non-AJAX dynamic content + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Custom Event Sent!"); + }); + + it('can handle htmx driven events', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('') + htmx.trigger(btn, "htmx:load"); + btn.classList.contains("afterSettle").should.equal(false); + btn.click(); + this.server.respond(); + btn.classList.contains("afterSettle").should.equal(true); + }); + + it('can handle htmx error events', function () { + this.server.respondWith("GET", "/test", [404, {}, "Bad request"]); + var div = make('
    ') + var btn = make('') + htmx.trigger(btn, "htmx:load"); + btn.click(); + this.server.respond(); + div.innerHTML.startsWith("Response Status Error Code 404 from"); + }); + + it('hyperscript in non-htmx annotated nodes is evaluated', function () { + this.server.respondWith("GET", "/test", "
    "); + var btn = make('') + btn.click(); + this.server.respond(); + var newDiv = byId("d1"); + newDiv.click(); + newDiv.innerText.should.equal("Clicked..."); + }); + + it('hyperscript removal example works', function (done) { + this.server.respondWith("GET", "/test", "
    To Remove
    "); + var btn = make('') + btn.click(); + this.server.respond(); + var newDiv = byId("d1"); + newDiv.innerText.should.equal("To Remove") + setTimeout(function(){ + newDiv = byId("d1"); + should.equal(newDiv, null); + done(); + }, 100); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/include-vals.js b/www/test/1.8.0/test/ext/include-vals.js new file mode 100644 index 000000000..fc05a067b --- /dev/null +++ b/www/test/1.8.0/test/ext/include-vals.js @@ -0,0 +1,23 @@ +describe("include-vals extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('Includes values properly', function () { + var params = {}; + this.server.respondWith("POST", "/test", function (xhr) { + params = getParameters(xhr); + xhr.respond(200, {}, "clicked"); + }); + var btn = make('') + btn.click(); + this.server.respond(); + params['foo'].should.equal("bar"); + }); + +}); \ No newline at end of file diff --git a/www/test/1.8.0/test/ext/json-enc.js b/www/test/1.8.0/test/ext/json-enc.js new file mode 100644 index 000000000..ed805a17c --- /dev/null +++ b/www/test/1.8.0/test/ext/json-enc.js @@ -0,0 +1,136 @@ +// +describe("json-enc extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('handles basic post properly', function () { + var jsonResponseBody = JSON.stringify({}); + this.server.respondWith("POST", "/test", jsonResponseBody); + var div = make("
    click me
    "); + div.click(); + this.server.respond(); + this.server.lastRequest.response.should.equal("{}"); + }) + + it('handles basic put properly', function () { + var jsonResponseBody = JSON.stringify({}); + this.server.respondWith("PUT", "/test", jsonResponseBody); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + this.server.lastRequest.response.should.equal("{}"); + }) + + it('handles basic patch properly', function () { + var jsonResponseBody = JSON.stringify({}); + this.server.respondWith("PATCH", "/test", jsonResponseBody); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + this.server.lastRequest.response.should.equal("{}"); + }) + + it('handles basic delete properly', function () { + var jsonResponseBody = JSON.stringify({}); + this.server.respondWith("DELETE", "/test", jsonResponseBody); + var div = make('
    click me
    '); + div.click(); + this.server.respond(); + this.server.lastRequest.response.should.equal("{}"); + }) + + it('handles post with form parameters', function () { + + this.server.respondWith("POST", "/test", function (xhr) { + var values = JSON.parse(xhr.requestBody); + values.should.have.keys("username","password"); + values["username"].should.be.equal("joe"); + values["password"].should.be.equal("123456"); + var ans = { "passwordok": values["password"] == "123456"}; + xhr.respond(200, {}, JSON.stringify(ans)); + }); + + var html = make('
    ' + + ' ' + + ' ' + + ' '); + + byId("btnSubmit").click(); + this.server.respond(); + this.server.lastRequest.response.should.equal('{"passwordok":true}'); + }) + + it('handles put with form parameters', function () { + + this.server.respondWith("PUT", "/test", function (xhr) { + var values = JSON.parse(xhr.requestBody); + values.should.have.keys("username","password"); + values["username"].should.be.equal("joe"); + values["password"].should.be.equal("123456"); + var ans = { "passwordok": values["password"] == "123456"}; + xhr.respond(200, {}, JSON.stringify(ans)); + }); + + var html = make(' ' + + ' ' + + ' ' + + ' '); + + byId("btnSubmit").click(); + this.server.respond(); + this.server.lastRequest.response.should.equal('{"passwordok":true}'); + }) + + + it('handles patch with form parameters', function () { + + this.server.respondWith("PATCH", "/test", function (xhr) { + var values = JSON.parse(xhr.requestBody); + values.should.have.keys("username","password"); + values["username"].should.be.equal("joe"); + values["password"].should.be.equal("123456"); + var ans = { "passwordok": values["password"] == "123456"}; + xhr.respond(200, {}, JSON.stringify(ans)); + }); + + var html = make(' ' + + ' ' + + ' ' + + ' '); + + byId("btnSubmit").click(); + this.server.respond(); + this.server.lastRequest.response.should.equal('{"passwordok":true}'); + }) + + it('handles delete with form parameters', function () { + + this.server.respondWith("DELETE", "/test", function (xhr) { + var values = JSON.parse(xhr.requestBody); + values.should.have.keys("username","password"); + values["username"].should.be.equal("joe"); + values["password"].should.be.equal("123456"); + var ans = { "passwordok": values["password"] == "123456"}; + xhr.respond(200, {}, JSON.stringify(ans)); + }); + + var html = make(' ' + + ' ' + + ' ' + + ' '); + + byId("btnSubmit").click(); + this.server.respond(); + this.server.lastRequest.response.should.equal('{"passwordok":true}'); + }) + + + +}); + diff --git a/www/test/1.8.0/test/ext/loading-states.js b/www/test/1.8.0/test/ext/loading-states.js new file mode 100644 index 000000000..d64c2b47b --- /dev/null +++ b/www/test/1.8.0/test/ext/loading-states.js @@ -0,0 +1,143 @@ +describe("loading states extension", function() { + beforeEach(function () { + this.server = makeServer(); + this.clock = sinon.useFakeTimers(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + this.clock.restore(); + clearWorkArea(); + }); + + it('works on basic setup', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
    '); + btn.click(); + element.style.display.should.be.equal("inline-block"); + this.server.respond(); + element.style.display.should.be.equal("none"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with custom display', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
    '); + btn.click(); + element.style.display.should.be.equal("flex"); + this.server.respond(); + element.style.display.should.be.equal("none"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with classes', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
    '); + btn.click(); + element.should.have.class("test"); + this.server.respond(); + element.should.not.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with classes removal', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
    '); + btn.click(); + element.should.not.have.class("test"); + this.server.respond(); + element.should.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with disabling', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make(''); + var element = make(''); + var element = make(''); + var element = make('
    '); + btn.click(); + element.should.have.class("test"); + this.clock.tick(1000); + element.should.not.have.class("test"); + this.server.respond(); + element.should.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with custom targets', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var element = make('
    '); + btn.click(); + element.should.have.class("test"); + this.server.respond(); + element.should.not.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with path filters', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make(''); + var matchingRequestElement = make('
    '); + var nonMatchingPathElement = make('
    '); + btn.click(); + matchingRequestElement.should.have.class("test"); + nonMatchingPathElement.should.not.have.class("test"); + this.server.respond(); + matchingRequestElement.should.not.have.class("test"); + nonMatchingPathElement.should.not.have.class("test"); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with scopes', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + var btn = make('
    '); + var element = make('
    '); + btn.getElementsByTagName("button")[0].click(); + element.should.not.have.class("test"); + this.server.respond(); + element.should.not.have.class("test"); + btn.getElementsByTagName("button")[0].innerHTML.should.equal("Clicked!"); + }); + +}); diff --git a/www/test/1.8.0/test/ext/method-override.js b/www/test/1.8.0/test/ext/method-override.js new file mode 100644 index 000000000..b0a21ea1d --- /dev/null +++ b/www/test/1.8.0/test/ext/method-override.js @@ -0,0 +1,53 @@ +describe("method-override extension", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('issues a DELETE request with proper headers', function() + { + this.server.respondWith("DELETE", "/test", function(xhr){ + xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('DELETE'); + xhr.method.should.equal("POST") + xhr.respond(200, {}, "Deleted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Deleted!"); + }); + + it('issues a PATCH request with proper headers', function() + { + this.server.respondWith("PATCH", "/test", function(xhr){ + xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PATCH'); + xhr.method.should.equal("POST") + xhr.respond(200, {}, "Patched!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Patched!"); + }); + + it('issues a PUT request with proper headers', function() + { + this.server.respondWith("PUT", "/test", function(xhr){ + xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PUT'); + xhr.method.should.equal("POST") + xhr.respond(200, {}, "Putted!"); + }); + + var btn = make('') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Putted!"); + }); + +}) diff --git a/www/test/1.8.0/test/ext/morphdom-swap.js b/www/test/1.8.0/test/ext/morphdom-swap.js new file mode 100644 index 000000000..439fa54cf --- /dev/null +++ b/www/test/1.8.0/test/ext/morphdom-swap.js @@ -0,0 +1,40 @@ +describe("morphdom-swap extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('works on basic request', function () { + this.server.respondWith("GET", "/test", "!"); + var btn = make('') + btn.click(); + should.equal(btn.getAttribute("hx-get"), "/test"); + this.server.respond(); + should.equal(btn.getAttribute("hx-get"), null); + btn.innerHTML.should.equal("Clicked!"); + }); + + it('works with htmx elements in new content', function () { + this.server.respondWith("GET", "/test", ''); + this.server.respondWith("GET", "/test-inner", 'Loaded!'); + var btn = make('
    ').querySelector('button'); + btn.click(); + this.server.respond(); // call /test via button trigger=click + this.server.respond(); // call /test-inner via span trigger=load + btn.innerHTML.should.equal("Clicked!Loaded!"); + }); + + it('works with hx-select', function () { + this.server.respondWith("GET", "/test", "!"); + var btn = make('') + btn.click(); + should.equal(btn.getAttribute("hx-get"), "/test"); + this.server.respond(); + should.equal(btn.getAttribute("hx-get"), null); + btn.innerHTML.should.equal("Clicked!"); + }); +}); diff --git a/www/test/1.8.0/test/ext/path-deps.js b/www/test/1.8.0/test/ext/path-deps.js new file mode 100644 index 000000000..9b1569ace --- /dev/null +++ b/www/test/1.8.0/test/ext/path-deps.js @@ -0,0 +1,173 @@ +describe("path-deps extension", function() { + beforeEach(function () { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function () { + this.server.restore(); + clearWorkArea(); + }); + + it('path-deps basic case works', function () { + this.server.respondWith("POST", "/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps works with trailing slash', function () { + this.server.respondWith("POST", "/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps GET does not trigger', function () { + this.server.respondWith("GET", "/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("FOO"); + }); + + it('path-deps dont trigger on path mismatch', function () { + this.server.respondWith("POST", "/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("FOO"); + }); + + it('path-deps dont trigger on path longer than request', function () { + this.server.respondWith("POST", "/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("FOO"); + }); + + it('path-deps trigger on path shorter than request', function () { + this.server.respondWith("POST", "/test/child", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps trigger on *-at-start path', function () { + this.server.respondWith("POST", "/test/child/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps trigger on *-in-middle path', function () { + this.server.respondWith("POST", "/test/child/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps trigger on *-at-end path', function () { + this.server.respondWith("POST", "/test/child/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps trigger all *s path', function () { + this.server.respondWith("POST", "/test/child/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var btn = make('') + var div = make('
    FOO
    ') + btn.click(); + this.server.respond(); + btn.innerHTML.should.equal("Clicked!"); + div.innerHTML.should.equal("FOO"); + this.server.respond(); + div.innerHTML.should.equal("Deps fired!"); + }); + + it('path-deps api basic refresh case works', function () { + this.server.respondWith("GET", "/test", "Path deps fired!"); + var div = make('
    FOO
    ') + PathDeps.refresh("/test"); + this.server.respond(); + div.innerHTML.should.equal("Path deps fired!"); + }); + + it('path-deps api parent path case works', function () { + this.server.respondWith("GET", "/test1", "Path deps 1 fired!"); + this.server.respondWith("GET", "/test2", "Path deps 2 fired!"); + var div = make('
    FOO
    ') + var div2 = make('
    BAR
    ') + PathDeps.refresh("/test/child"); + this.server.respond(); + div.innerHTML.should.equal("Path deps 1 fired!"); + this.server.respond(); + div2.innerHTML.should.equal("Path deps 2 fired!"); + }); + + it('path-deps replacing containing element fires event', function () { + this.server.respondWith("POST", "/test", "Clicked!"); + this.server.respondWith("GET", "/test2", "Deps fired!"); + var div1 = make('
    ') + var div2 = make('
    FOO
    ') + byId("buttonSubmit").click(); + this.server.respond(); + div1.innerHTML.should.equal('Clicked!'); + div2.innerHTML.should.equal("FOO"); + this.server.respond(); + div2.innerHTML.should.equal("Deps fired!"); + }); +}); diff --git a/www/test/1.8.0/test/ext/remove-me.js b/www/test/1.8.0/test/ext/remove-me.js new file mode 100644 index 000000000..cdfd51f83 --- /dev/null +++ b/www/test/1.8.0/test/ext/remove-me.js @@ -0,0 +1,53 @@ +describe("remove-me extension", function(){ + beforeEach(function() { + this.server = makeServer(); + clearWorkArea(); + }); + afterEach(function() { + this.server.restore(); + clearWorkArea(); + }); + + it('removes elements properly', function(done) + { + var div = make('
    Click Me!
    ') + byId("d1").should.equal(div) + setTimeout(function(){ + should.equal(byId("d1"), null); + done(); + }, 40); + }); + + + it('removes properly w/ data-* prefix', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.length, 0); + setTimeout(function(){ + should.equal(div.parentElement, null); + done(); + }, 100); + }); + + it('extension can be on parent', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.length, 0); + setTimeout(function(){ + should.equal(byId("d1"), null); + done(); + }, 100); + }); + + it('extension can be on a child', function(done) + { + var div = make('
    Click Me!
    ') + should.equal(div.classList.length, 0); + setTimeout(function(){ + should.equal(byId("d1"), null); + done(); + }, 100); + }); + + +}) diff --git a/www/test/1.8.0/test/img/bars.svg b/www/test/1.8.0/test/img/bars.svg new file mode 100644 index 000000000..7cb07e65a --- /dev/null +++ b/www/test/1.8.0/test/img/bars.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/test/1.8.0/test/index.html b/www/test/1.8.0/test/index.html new file mode 100644 index 000000000..861131437 --- /dev/null +++ b/www/test/1.8.0/test/index.html @@ -0,0 +1,150 @@ + + + + Mocha Tests + + + + + + + + + + + +

    htmx.js test suite

    + +

    Scratch Page

    + + +

    Manual Tests

    +Here + +

    Mocha Test Suite

    +[ALL] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +Work Area +
    +
    +
    + + diff --git a/www/test/1.8.0/test/lib/_hyperscript.js b/www/test/1.8.0/test/lib/_hyperscript.js new file mode 100644 index 000000000..114c95741 --- /dev/null +++ b/www/test/1.8.0/test/lib/_hyperscript.js @@ -0,0 +1,6067 @@ +///========================================================================= +/// This module provides the core runtime and grammar for hyperscript +///========================================================================= +//AMD insanity + +/** @var {HyperscriptObject} _hyperscript */ + +(function (root, factory) { + if (typeof define === "function" && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else { + // Browser globals + root._hyperscript = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + "use strict"; + + //==================================================================== + // Utilities + //==================================================================== + + /** + * mergeObjects combines the keys from obj2 into obj2, then returns obj1 + * + * @param {object} obj1 + * @param {object} obj2 + * @returns object + */ + function mergeObjects(obj1, obj2) { + for (var key in obj2) { + if (obj2.hasOwnProperty(key)) { + obj1[key] = obj2[key]; + } + } + return obj1; + } + + function getOrInitObject(root, prop) { + var value = root[prop]; + if (value) { + return value; + } else { + var newObj = {}; + root[prop] = newObj; + return newObj; + } + } + + /** + * parseJSON parses a JSON string into a corresponding value. If the + * value passed in is not valid JSON, then it logs an error and returns `null`. + * + * @param {string} jString + * @returns any + */ + function parseJSON(jString) { + try { + return JSON.parse(jString); + } catch (error) { + logError(error); + return null; + } + } + + /** + * logError writes an error message to the Javascript console. It can take any + * value, but msg should commonly be a simple string. + * @param {*} msg + */ + function logError(msg) { + if (console.error) { + console.error(msg); + } else if (console.log) { + console.log("ERROR: ", msg); + } + } + + // TODO: JSDoc description of what's happening here + function varargConstructor(Cls, args) { + return new (Cls.bind.apply(Cls, [Cls].concat(args)))(); + } + + var globalScope = typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this; + + //==================================================================== + // Lexer + //==================================================================== + + /** @type LexerObject */ + var _lexer = (function () { + var OP_TABLE = { + "+": "PLUS", + "-": "MINUS", + "*": "MULTIPLY", + "/": "DIVIDE", + ".": "PERIOD", + "..": "ELLIPSIS", + "\\": "BACKSLASH", + ":": "COLON", + "%": "PERCENT", + "|": "PIPE", + "!": "EXCLAMATION", + "?": "QUESTION", + "#": "POUND", + "&": "AMPERSAND", + $: "DOLLAR", + ";": "SEMI", + ",": "COMMA", + "(": "L_PAREN", + ")": "R_PAREN", + "<": "L_ANG", + ">": "R_ANG", + "<=": "LTE_ANG", + ">=": "GTE_ANG", + "==": "EQ", + "===": "EQQ", + "!=": "NEQ", + "!==": "NEQQ", + "{": "L_BRACE", + "}": "R_BRACE", + "[": "L_BRACKET", + "]": "R_BRACKET", + "=": "EQUALS", + }; + + /** + * isValidCSSClassChar returns `true` if the provided character is valid in a CSS class. + * @param {string} c + * @returns boolean + */ + function isValidCSSClassChar(c) { + return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":"; + } + + /** + * isValidCSSIDChar returns `true` if the provided character is valid in a CSS ID + * @param {string} c + * @returns boolean + */ + function isValidCSSIDChar(c) { + return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":"; + } + + /** + * isWhitespace returns `true` if the provided character is whitespace. + * @param {string} c + * @returns boolean + */ + function isWhitespace(c) { + return c === " " || c === "\t" || isNewline(c); + } + + /** + * positionString returns a string representation of a Token's line and column details. + * @param {Token} token + * @returns string + */ + function positionString(token) { + return "[Line: " + token.line + ", Column: " + token.col + "]"; + } + + /** + * isNewline returns `true` if the provided character is a carrage return or newline + * @param {string} c + * @returns boolean + */ + function isNewline(c) { + return c === "\r" || c === "\n"; + } + + /** + * isNumeric returns `true` if the provided character is a number (0-9) + * @param {string} c + * @returns boolean + */ + function isNumeric(c) { + return c >= "0" && c <= "9"; + } + + /** + * isAlpha returns `true` if the provided character is a letter in the alphabet + * @param {string} c + * @returns boolean + */ + function isAlpha(c) { + return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z"); + } + + /** + * @param {string} c + * @param {boolean} [dollarIsOp] + * @returns boolean + */ + function isIdentifierChar(c) { + return c === "_" || c === "$"; + } + + /** + * @param {string} c + * @returns boolean + */ + function isReservedChar(c) { + return c === "`" || c === "^"; + } + + /** + * @param {Token[]} tokens + * @param {Token[]} consumed + * @param {string} source + * @returns {TokensObject} + */ + function makeTokensObject(tokens, consumed, source) { + consumeWhitespace(); // consume initial whitespace + + /** @type Token | null */ + var _lastConsumed = null; + + function consumeWhitespace() { + while (token(0, true).type === "WHITESPACE") { + consumed.push(tokens.shift()); + } + } + + /** + * @param {Token[]} tokens + * @param {*} error + */ + function raiseError(tokens, error) { + _parser.raiseParseError(tokens, error); + } + + /** + * @param {string} value + * @returns {Token} + */ + function requireOpToken(value) { + var token = matchOpToken(value); + if (token) { + return token; + } else { + raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'"); + } + } + + /** + * @param {string} op1 + * @param {string} [op2] + * @param {string} [op3] + * @returns {Token | void} + */ + function matchAnyOpToken(op1, op2, op3) { + for (var i = 0; i < arguments.length; i++) { + var opToken = arguments[i]; + var match = matchOpToken(opToken); + if (match) { + return match; + } + } + } + + /** + * @param {string} op1 + * @param {string} [op2] + * @param {string} [op3] + * @returns {Token | void} + */ + function matchAnyToken(op1, op2, op3) { + for (var i = 0; i < arguments.length; i++) { + var opToken = arguments[i]; + var match = matchToken(opToken); + if (match) { + return match; + } + } + } + + /** + * @param {string} value + * @returns {Token | void} + */ + function matchOpToken(value) { + if (currentToken() && currentToken().op && currentToken().value === value) { + return consumeToken(); + } + } + + /** + * @param {string} type1 + * @param {string} [type2] + * @param {string} [type3] + * @param {string} [type4] + * @returns {Token | void} + */ + function requireTokenType(type1, type2, type3, type4) { + var token = matchTokenType(type1, type2, type3, type4); + if (token) { + return token; + } else { + raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3])); + } + } + + /** + * @param {string} type1 + * @param {string} [type2] + * @param {string} [type3] + * @param {string} [type4] + * @returns {Token | void} + */ + function matchTokenType(type1, type2, type3, type4) { + if ( + currentToken() && + currentToken().type && + [type1, type2, type3, type4].indexOf(currentToken().type) >= 0 + ) { + return consumeToken(); + } + } + + /** + * @param {string} value + * @param {string} [type] + * @returns {Token} + */ + function requireToken(value, type) { + var token = matchToken(value, type); + if (token) { + return token; + } else { + raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'"); + } + } + + /** + * @param {string} value + * @param {string} [type] + * @returns {Token | void} + */ + function matchToken(value, type) { + if (follows.indexOf(value) !== -1) { + return; // disallowed token here + } + var type = type || "IDENTIFIER"; + if (currentToken() && currentToken().value === value && currentToken().type === type) { + return consumeToken(); + } + } + + /** + * @returns {Token} + */ + function consumeToken() { + var match = tokens.shift(); + consumed.push(match); + _lastConsumed = match; + consumeWhitespace(); // consume any whitespace + return match; + } + + /** + * @param {string} value + * @param {string} [type] + * @returns {Token[]} + */ + function consumeUntil(value, type) { + /** @type Token[] */ + var tokenList = []; + var currentToken = token(0, true); + + while ( + (type == null || currentToken.type !== type) && + (value == null || currentToken.value !== value) && + currentToken.type !== "EOF" + ) { + var match = tokens.shift(); + consumed.push(match); + tokenList.push(currentToken); + currentToken = token(0, true); + } + consumeWhitespace(); // consume any whitespace + return tokenList; + } + + /** + * @returns {string} + */ + function lastWhitespace() { + if (consumed[consumed.length - 1] && consumed[consumed.length - 1].type === "WHITESPACE") { + return consumed[consumed.length - 1].value; + } else { + return ""; + } + } + + function consumeUntilWhitespace() { + return consumeUntil(null, "WHITESPACE"); + } + + /** + * @returns {boolean} + */ + function hasMore() { + return tokens.length > 0; + } + + /** + * @param {number} n + * @param {boolean} [dontIgnoreWhitespace] + * @returns {Token} + */ + function token(n, dontIgnoreWhitespace) { + var /**@type {Token}*/ token; + var i = 0; + do { + if (!dontIgnoreWhitespace) { + while (tokens[i] && tokens[i].type === "WHITESPACE") { + i++; + } + } + token = tokens[i]; + n--; + i++; + } while (n > -1); + if (token) { + return token; + } else { + return { + type: "EOF", + value: "<<>>", + }; + } + } + + /** + * @returns {Token} + */ + function currentToken() { + return token(0); + } + + /** + * @returns {Token | null} + */ + function lastMatch() { + return _lastConsumed; + } + + /** + * @returns {string} + */ + function sourceFor() { + return source.substring(this.startToken.start, this.endToken.end); + } + + /** + * @returns {string} + */ + function lineFor() { + return source.split("\n")[this.startToken.line - 1]; + } + + var follows = []; + + function pushFollow(str) { + follows.push(str); + } + + function popFollow() { + follows.pop(); + } + + function clearFollows() { + var tmp = follows; + follows = []; + return tmp; + } + + function restoreFollows(f) { + follows = f; + } + + /** @type {TokensObject} */ + return { + pushFollow: pushFollow, + popFollow: popFollow, + clearFollow: clearFollows, + restoreFollow: restoreFollows, + matchAnyToken: matchAnyToken, + matchAnyOpToken: matchAnyOpToken, + matchOpToken: matchOpToken, + requireOpToken: requireOpToken, + matchTokenType: matchTokenType, + requireTokenType: requireTokenType, + consumeToken: consumeToken, + matchToken: matchToken, + requireToken: requireToken, + list: tokens, + consumed: consumed, + source: source, + hasMore: hasMore, + currentToken: currentToken, + lastMatch: lastMatch, + token: token, + consumeUntil: consumeUntil, + consumeUntilWhitespace: consumeUntilWhitespace, + lastWhitespace: lastWhitespace, + sourceFor: sourceFor, + lineFor: lineFor, + }; + } + + /** + * @param {Token[]} tokens + * @returns {boolean} + */ + function isValidSingleQuoteStringStart(tokens) { + if (tokens.length > 0) { + var previousToken = tokens[tokens.length - 1]; + if ( + previousToken.type === "IDENTIFIER" || + previousToken.type === "CLASS_REF" || + previousToken.type === "ID_REF" + ) { + return false; + } + if (previousToken.op && (previousToken.value === ">" || previousToken.value === ")")) { + return false; + } + } + return true; + } + + /** + * @param {string} string + * @param {boolean} [template] + * @returns {TokensObject} + */ + function tokenize(string, template) { + var tokens = /** @type {Token[]}*/ []; + var source = string; + var position = 0; + var column = 0; + var line = 1; + var lastToken = ""; + var templateBraceCount = 0; + + function inTemplate() { + return template && templateBraceCount === 0; + } + + while (position < source.length) { + if (currentChar() === "-" && nextChar() === "-") { + consumeComment(); + } else { + if (isWhitespace(currentChar())) { + tokens.push(consumeWhitespace()); + } else if ( + !possiblePrecedingSymbol() && + currentChar() === "." && + (isAlpha(nextChar()) || nextChar() === "{") + ) { + tokens.push(consumeClassReference()); + } else if ( + !possiblePrecedingSymbol() && + currentChar() === "#" && + (isAlpha(nextChar()) || nextChar() === "{") + ) { + tokens.push(consumeIdReference()); + } else if (currentChar() === "[" && nextChar() === "@") { + tokens.push(consumeAttributeReference()); + } else if (currentChar() === "@") { + tokens.push(consumeShortAttributeReference()); + } else if (isAlpha(currentChar()) || (!inTemplate() && isIdentifierChar(currentChar()))) { + tokens.push(consumeIdentifier()); + } else if (isNumeric(currentChar())) { + tokens.push(consumeNumber()); + } else if (!inTemplate() && (currentChar() === '"' || currentChar() === "`")) { + tokens.push(consumeString()); + } else if (!inTemplate() && currentChar() === "'") { + if (isValidSingleQuoteStringStart(tokens)) { + tokens.push(consumeString()); + } else { + tokens.push(consumeOp()); + } + } else if (OP_TABLE[currentChar()]) { + if (lastToken === "$" && currentChar() === "{") { + templateBraceCount++; + } + if (currentChar() === "}") { + templateBraceCount--; + } + tokens.push(consumeOp()); + } else if (inTemplate() || isReservedChar(currentChar())) { + tokens.push(makeToken("RESERVED", consumeChar())); + } else { + if (position < source.length) { + throw Error("Unknown token: " + currentChar() + " "); + } + } + } + } + + return makeTokensObject(tokens, [], source); + + /** + * @param {string} [type] + * @param {string} [value] + * @returns {Token} + */ + function makeOpToken(type, value) { + var token = makeToken(type, value); + token.op = true; + return token; + } + + /** + * @param {string} [type] + * @param {string} [value] + * @returns {Token} + */ + function makeToken(type, value) { + return { + type: type, + value: value, + start: position, + end: position + 1, + column: column, + line: line, + }; + } + + function consumeComment() { + while (currentChar() && !isNewline(currentChar())) { + consumeChar(); + } + consumeChar(); + } + + /** + * @returns Token + */ + function consumeClassReference() { + var classRef = makeToken("CLASS_REF"); + var value = consumeChar(); + if (currentChar() === "{") { + classRef.template = true; + value += consumeChar(); + while (currentChar() && currentChar() !== "}") { + value += consumeChar(); + } + if (currentChar() !== "}") { + throw Error("Unterminated class reference"); + } else { + value += consumeChar(); // consume final curly + } + } else { + while (isValidCSSClassChar(currentChar())) { + value += consumeChar(); + } + } + classRef.value = value; + classRef.end = position; + return classRef; + } + + /** + * @returns Token + */ + function consumeAttributeReference() { + var attributeRef = makeToken("ATTRIBUTE_REF"); + var value = consumeChar(); + while (position < source.length && currentChar() !== "]") { + value += consumeChar(); + } + if (currentChar() === "]") { + value += consumeChar(); + } + attributeRef.value = value; + attributeRef.end = position; + return attributeRef; + } + + function consumeShortAttributeReference() { + var attributeRef = makeToken("ATTRIBUTE_REF"); + var value = consumeChar(); + while (isValidCSSIDChar(currentChar())) { + value += consumeChar(); + } + attributeRef.value = value; + attributeRef.end = position; + return attributeRef; + } + + /** + * @returns Token + */ + function consumeIdReference() { + var idRef = makeToken("ID_REF"); + var value = consumeChar(); + if (currentChar() === "{") { + idRef.template = true; + value += consumeChar(); + while (currentChar() && currentChar() !== "}") { + value += consumeChar(); + } + if (currentChar() !== "}") { + throw Error("Unterminated id reference"); + } else { + consumeChar(); // consume final quote + } + } else { + while (isValidCSSIDChar(currentChar())) { + value += consumeChar(); + } + } + idRef.value = value; + idRef.end = position; + return idRef; + } + + /** + * @returns Token + */ + function consumeIdentifier() { + var identifier = makeToken("IDENTIFIER"); + var value = consumeChar(); + while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) { + value += consumeChar(); + } + identifier.value = value; + identifier.end = position; + return identifier; + } + + /** + * @returns Token + */ + function consumeNumber() { + var number = makeToken("NUMBER"); + var value = consumeChar(); + while (isNumeric(currentChar())) { + value += consumeChar(); + } + if (currentChar() === "." && isNumeric(nextChar())) { + value += consumeChar(); + } + while (isNumeric(currentChar())) { + value += consumeChar(); + } + number.value = value; + number.end = position; + return number; + } + + /** + * @returns Token + */ + function consumeOp() { + var op = makeOpToken(); + var value = consumeChar(); // consume leading char + while (currentChar() && OP_TABLE[value + currentChar()]) { + value += consumeChar(); + } + op.type = OP_TABLE[value]; + op.value = value; + op.end = position; + return op; + } + + /** + * @returns Token + */ + function consumeString() { + var string = makeToken("STRING"); + var startChar = consumeChar(); // consume leading quote + var value = ""; + while (currentChar() && currentChar() !== startChar) { + if (currentChar() === "\\") { + consumeChar(); // consume escape char and move on + } + value += consumeChar(); + } + if (currentChar() !== startChar) { + throw Error("Unterminated string at " + positionString(string)); + } else { + consumeChar(); // consume final quote + } + string.value = value; + string.end = position; + string.template = startChar === "`"; + return string; + } + + /** + * @returns string + */ + function currentChar() { + return source.charAt(position); + } + + /** + * @returns string + */ + function nextChar() { + return source.charAt(position + 1); + } + + /** + * @returns string + */ + function consumeChar() { + lastToken = currentChar(); + position++; + column++; + return lastToken; + } + + /** + * @returns boolean + */ + function possiblePrecedingSymbol() { + return ( + isAlpha(lastToken) || + isNumeric(lastToken) || + lastToken === ")" || + lastToken === "}" || + lastToken === "]" + ); + } + + /** + * @returns Token + */ + function consumeWhitespace() { + var whitespace = makeToken("WHITESPACE"); + var value = ""; + while (currentChar() && isWhitespace(currentChar())) { + if (isNewline(currentChar())) { + column = 0; + line++; + } + value += consumeChar(); + } + whitespace.value = value; + whitespace.end = position; + return whitespace; + } + } + + return { + tokenize: tokenize, + makeTokensObject: makeTokensObject, + }; + })(); + + //==================================================================== + // Parser + //==================================================================== + + /** @type ParserObject */ + var _parser = (function () { + /** @type {Object} */ + var GRAMMAR = {}; + + /** @type {Object} */ + var COMMANDS = {}; + + /** @type {Object} */ + var FEATURES = {}; + + var LEAF_EXPRESSIONS = []; + var INDIRECT_EXPRESSIONS = []; + + /** + * @param {*} parseElement + * @param {*} start + * @param {TokensObject} tokens + */ + function initElt(parseElement, start, tokens) { + parseElement.startToken = start; + parseElement.sourceFor = tokens.sourceFor; + parseElement.lineFor = tokens.lineFor; + parseElement.programSource = tokens.source; + } + + /** + * @param {string} type + * @param {TokensObject} tokens + * @param {*} root + * @returns GrammarElement + */ + function parseElement(type, tokens, root) { + var elementDefinition = GRAMMAR[type]; + if (elementDefinition) { + var start = tokens.currentToken(); + var parseElement = elementDefinition(_parser, _runtime, tokens, root); + if (parseElement) { + initElt(parseElement, start, tokens); + parseElement.endToken = parseElement.endToken || tokens.lastMatch(); + var root = parseElement.root; + while (root != null) { + initElt(root, start, tokens); + root = root.root; + } + } + return parseElement; + } + } + + /** + * @param {string} type + * @param {TokensObject} tokens + * @param {string} [message] + * @param {*} [root] + * @returns {GrammarElement} + */ + function requireElement(type, tokens, message, root) { + var result = parseElement(type, tokens, root); + return result || raiseParseError(tokens, message || "Expected " + type); + } + + /** + * @param {string[]} types + * @param {TokensObject} tokens + * @returns {GrammarElement} + */ + function parseAnyOf(types, tokens) { + for (var i = 0; i < types.length; i++) { + var type = types[i]; + var expression = parseElement(type, tokens); + if (expression) { + return expression; + } + } + } + + /** + * @param {string} name + * @param {GrammarDefinition} definition + */ + function addGrammarElement(name, definition) { + GRAMMAR[name] = definition; + } + + /** + * @param {string} keyword + * @param {GrammarDefinition} definition + */ + function addCommand(keyword, definition) { + var commandGrammarType = keyword + "Command"; + var commandDefinitionWrapper = function (parser, runtime, tokens) { + var commandElement = definition(parser, runtime, tokens); + if (commandElement) { + commandElement.type = commandGrammarType; + commandElement.execute = function (context) { + context.meta.command = commandElement; + return runtime.unifiedExec(this, context); + }; + return commandElement; + } + }; + GRAMMAR[commandGrammarType] = commandDefinitionWrapper; + COMMANDS[keyword] = commandDefinitionWrapper; + } + + /** + * @param {string} keyword + * @param {GrammarDefinition} definition + */ + function addFeature(keyword, definition) { + var featureGrammarType = keyword + "Feature"; + + /** @type {GrammarDefinition} */ + var featureDefinitionWrapper = function (parser, runtime, tokens) { + var featureElement = definition(parser, runtime, tokens); + if (featureElement) { + featureElement.keyword = keyword; + featureElement.type = featureGrammarType; + return featureElement; + } + }; + GRAMMAR[featureGrammarType] = featureDefinitionWrapper; + FEATURES[keyword] = featureDefinitionWrapper; + } + + /** + * @param {string} name + * @param {GrammarDefinition} definition + */ + function addLeafExpression(name, definition) { + LEAF_EXPRESSIONS.push(name); + addGrammarElement(name, definition); + } + + /** + * @param {string} name + * @param {GrammarDefinition} definition + */ + function addIndirectExpression(name, definition) { + INDIRECT_EXPRESSIONS.push(name); + addGrammarElement(name, definition); + } + + /* ============================================================================================ */ + /* Core hyperscript Grammar Elements */ + /* ============================================================================================ */ + addGrammarElement("feature", function (parser, runtime, tokens) { + if (tokens.matchOpToken("(")) { + var featureElement = parser.requireElement("feature", tokens); + tokens.requireOpToken(")"); + return featureElement; + } + + var featureDefinition = FEATURES[tokens.currentToken().value]; + if (featureDefinition) { + return featureDefinition(parser, runtime, tokens); + } + }); + + addGrammarElement("command", function (parser, runtime, tokens) { + if (tokens.matchOpToken("(")) { + var commandElement = parser.requireElement("command", tokens); + tokens.requireOpToken(")"); + return commandElement; + } + + var commandDefinition = COMMANDS[tokens.currentToken().value]; + if (commandDefinition) { + var commandElement = commandDefinition(parser, runtime, tokens); + } else if (tokens.currentToken().type === "IDENTIFIER" && tokens.token(1).value === "(") { + var commandElement = parser.requireElement("pseudoCommand", tokens); + } + if (commandElement) { + return parser.parseElement("indirectStatement", tokens, commandElement); + } + + return commandElement; + }); + + addGrammarElement("commandList", function (parser, runtime, tokens) { + var cmd = parser.parseElement("command", tokens); + if (cmd) { + tokens.matchToken("then"); + cmd.next = parser.parseElement("commandList", tokens); + return cmd; + } + }); + + addGrammarElement("leaf", function (parser, runtime, tokens) { + var result = parseAnyOf(LEAF_EXPRESSIONS, tokens); + // symbol is last so it doesn't consume any constants + if (result == null) { + return parseElement("symbol", tokens); + } + + return result; + }); + + addGrammarElement("indirectExpression", function (parser, runtime, tokens, root) { + for (var i = 0; i < INDIRECT_EXPRESSIONS.length; i++) { + var indirect = INDIRECT_EXPRESSIONS[i]; + root.endToken = tokens.lastMatch(); + var result = parser.parseElement(indirect, tokens, root); + if (result) { + return result; + } + } + return root; + }); + + addGrammarElement("indirectStatement", function (parser, runtime, tokens, root) { + if (tokens.matchToken("unless")) { + root.endToken = tokens.lastMatch(); + var conditional = parser.requireElement("expression", tokens); + var unless = { + type: "unlessStatementModifier", + args: [conditional], + op: function (context, conditional) { + if (conditional) { + return this.next; + } else { + return root; + } + }, + execute: function (context) { + return runtime.unifiedExec(this, context); + }, + }; + root.parent = unless; + return unless; + } + return root; + }); + + addGrammarElement("primaryExpression", function (parser, runtime, tokens) { + var leaf = parser.parseElement("leaf", tokens); + if (leaf) { + return parser.parseElement("indirectExpression", tokens, leaf); + } + parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); + }); + + /* ============================================================================================ */ + /* END Core hyperscript Grammar Elements */ + + /* ============================================================================================ */ + + /** + * + * @param {TokensObject} tokens + * @returns string + */ + function createParserContext(tokens) { + var currentToken = tokens.currentToken(); + var source = tokens.source; + var lines = source.split("\n"); + var line = currentToken && currentToken.line ? currentToken.line - 1 : lines.length - 1; + var contextLine = lines[line]; + var offset = currentToken && currentToken.line ? currentToken.column : contextLine.length - 1; + return contextLine + "\n" + " ".repeat(offset) + "^^\n\n"; + } + + /** + * @param {TokensObject} tokens + * @param {string} message + */ + function raiseParseError(tokens, message) { + message = + (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" + createParserContext(tokens); + var error = new Error(message); + error["tokens"] = tokens; + throw error; + } + + /** + * @param {TokensObject} tokens + * @returns {GrammarElement} + */ + function parseHyperScript(tokens) { + var result = parseElement("hyperscript", tokens); + if (tokens.hasMore()) raiseParseError(tokens); + return result; + } + + /** + * @param {GrammarElement} elt + * @param {GrammarElement} parent + */ + function setParent(elt, parent) { + if (elt) { + elt.parent = parent; + setParent(elt.next, parent); + } + } + + /** + * @param {Token} token + * @returns {GrammarDefinition} + */ + function commandStart(token) { + return COMMANDS[token.value]; + } + + /** + * @param {Token} token + * @returns {GrammarDefinition} + */ + function featureStart(token) { + return FEATURES[token.value]; + } + + /** + * @param {Token} token + * @returns {true | void} + */ + function commandBoundary(token) { + if ( + token.value == "end" || + token.value == "then" || + token.value == "else" || + token.value == ")" || + commandStart(token) || + featureStart(token) || + token.type == "EOF" + ) { + return true; + } + } + + /** + * @param {TokensObject} tokens + * @returns {(string | Token)[]} + */ + function parseStringTemplate(tokens) { + /** @type (string | Token)[] */ + var returnArr = [""]; + do { + returnArr.push(tokens.lastWhitespace()); + if (tokens.currentToken().value === "$") { + tokens.consumeToken(); + var startingBrace = tokens.matchOpToken("{"); + returnArr.push(requireElement("expression", tokens)); + if (startingBrace) { + tokens.requireOpToken("}"); + } + returnArr.push(""); + } else if (tokens.currentToken().value === "\\") { + tokens.consumeToken(); // skip next + tokens.consumeToken(); + } else { + var token = tokens.consumeToken(); + returnArr[returnArr.length - 1] += token ? token.value : ""; + } + } while (tokens.hasMore()); + returnArr.push(tokens.lastWhitespace()); + return returnArr; + } + + // parser API + return { + setParent: setParent, + requireElement: requireElement, + parseElement: parseElement, + featureStart: featureStart, + commandStart: commandStart, + commandBoundary: commandBoundary, + parseAnyOf: parseAnyOf, + parseHyperScript: parseHyperScript, + raiseParseError: raiseParseError, + addGrammarElement: addGrammarElement, + addCommand: addCommand, + addFeature: addFeature, + addLeafExpression: addLeafExpression, + addIndirectExpression: addIndirectExpression, + parseStringTemplate: parseStringTemplate, + }; + })(); + + //==================================================================== + // Runtime + //==================================================================== + + /** @type ConversionMap */ + var CONVERSIONS = { + dynamicResolvers: /** @type DynamicConversionFunction[] */ [], + String: function (val) { + if (val.toString) { + return val.toString(); + } else { + return "" + val; + } + }, + Int: function (val) { + return parseInt(val); + }, + Float: function (val) { + return parseFloat(val); + }, + Number: function (val) { + console.log(val); + return Number(val); + }, + Date: function (val) { + return Date(val); + }, + Array: function (val) { + return Array.from(val); + }, + JSON: function (val) { + return JSON.stringify(val); + }, + Object: function (val) { + if (val instanceof String) { + val = val.toString(); + } + if (typeof val === "string") { + return JSON.parse(val); + } else { + return mergeObjects({}, val); + } + }, + }; + + /******************************************** + * RUNTIME OBJECT + ********************************************/ + + /** @type {RuntimeObject} */ + var _runtime = (function () { + /** + * @param {HTMLElement} elt + * @param {string} selector + * @returns boolean + */ + function matchesSelector(elt, selector) { + // noinspection JSUnresolvedVariable + var matchesFunction = + elt.matches || + elt.matchesSelector || + elt.msMatchesSelector || + elt.mozMatchesSelector || + elt.webkitMatchesSelector || + elt.oMatchesSelector; + return matchesFunction && matchesFunction.call(elt, selector); + } + + /** + * @param {string} eventName + * @param {Object} [detail] + * @returns {Event} + */ + function makeEvent(eventName, detail) { + var evt; + if (window.CustomEvent && typeof window.CustomEvent === "function") { + evt = new CustomEvent(eventName, { + bubbles: true, + cancelable: true, + detail: detail, + }); + } else { + evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(eventName, true, true, detail); + } + return evt; + } + + /** + * @param {HTMLElement} elt + * @param {string} eventName + * @param {Object} [detail] + * @returns {boolean} + */ + function triggerEvent(elt, eventName, detail) { + detail = detail || {}; + detail["sentBy"] = elt; + var event = makeEvent(eventName, detail); + var eventResult = elt.dispatchEvent(event); + return eventResult; + } + + /** + * isArrayLike returns `true` if the provided value is an array or + * a NodeList (which is close enough to being an array for our purposes). + * + * @param {any} value + * @returns {value is Array | NodeList} + */ + function isArrayLike(value) { + return Array.isArray(value) || value instanceof NodeList; + } + + /** + * forEach executes the provided `func` on every item in the `value` array. + * if `value` is a single item (and not an array) then `func` is simply called + * once. If `value` is null, then no further actions are taken. + * + * @template T + * @param {NodeList | T | T[]} value + * @param {(item:Node | T) => void} func + */ + function forEach(value, func) { + if (value == null) { + // do nothing + } else if (isArrayLike(value)) { + for (var i = 0; i < value.length; i++) { + func(value[i]); + } + } else { + func(value); + } + } + + var ARRAY_SENTINEL = { array_sentinel: true }; + + function linearize(args) { + var arr = []; + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (Array.isArray(arg)) { + arr.push(ARRAY_SENTINEL); + for (var j = 0; j < arg.length; j++) { + arr.push(arg[j]); + } + arr.push(ARRAY_SENTINEL); + } else { + arr.push(arg); + } + } + return arr; + } + + function delinearize(values) { + var arr = []; + for (var i = 0; i < values.length; i++) { + var value = values[i]; + if (value === ARRAY_SENTINEL) { + value = values[++i]; + var valueArray = []; + arr.push(valueArray); + while (value !== ARRAY_SENTINEL) { + valueArray.push(value); + value = values[++i]; + } + } else { + arr.push(value); + } + } + return arr; + } + + function unwrapAsyncs(values) { + for (var i = 0; i < values.length; i++) { + var value = values[i]; + if (value.asyncWrapper) { + values[i] = value.value; + } + if (Array.isArray(value)) { + for (var j = 0; j < value.length; j++) { + var valueElement = value[j]; + if (valueElement.asyncWrapper) { + value[j] = valueElement.value; + } + } + } + } + } + + var HALT = { halt_flag: true }; + + /** + * @param {GrammarDefinition} command + * @param {Context} ctx + */ + function unifiedExec(command, ctx) { + while (true) { + try { + var next = unifiedEval(command, ctx); + } catch (e) { + _runtime.registerHyperTrace(ctx, e); + if (ctx.meta.errorHandler && !ctx.meta.handlingError) { + ctx.meta.handlingError = true; + ctx[ctx.meta.errorSymmbol] = e; + command = ctx.meta.errorHandler; + continue; + } else if (ctx.meta.reject) { + ctx.meta.reject(e); + next = HALT; + } else { + throw e; + } + } + if (next == null) { + console.error(command, " did not return a next element to execute! context: ", ctx); + return; + } else if (next.then) { + next.then(function (resolvedNext) { + unifiedExec(resolvedNext, ctx); + }).catch(function (reason) { + _runtime.registerHyperTrace(ctx, reason); + if (ctx.meta.errorHandler && !ctx.meta.handlingError) { + ctx.meta.handlingError = true; + ctx[ctx.meta.errorSymmbol] = reason; + unifiedExec(ctx.meta.errorHandler, ctx); + } else if (ctx.meta.reject) { + ctx.meta.reject(reason); + } else { + throw reason; + } + }); + return; + } else if (next === HALT) { + // done + return; + } else { + command = next; // move to the next command + } + } + } + + /** + * @param {*} parseElement + * @param {Context} ctx + * @returns {*} + */ + function unifiedEval(parseElement, ctx) { + /** @type any[] */ + var args = [ctx]; + var async = false; + var wrappedAsyncs = false; + + if (parseElement.args) { + for (var i = 0; i < parseElement.args.length; i++) { + var argument = parseElement.args[i]; + if (argument == null) { + args.push(null); + } else if (Array.isArray(argument)) { + var arr = []; + for (var j = 0; j < argument.length; j++) { + var element = argument[j]; + var value = element ? element.evaluate(ctx) : null; // OK + if (value) { + if (value.then) { + async = true; + } else if (value.asyncWrapper) { + wrappedAsyncs = true; + } + } + arr.push(value); + } + args.push(arr); + } else if (argument.evaluate) { + var value = argument.evaluate(ctx); // OK + if (value) { + if (value.then) { + async = true; + } else if (value.asyncWrapper) { + wrappedAsyncs = true; + } + } + args.push(value); + } else { + args.push(argument); + } + } + } + if (async) { + return new Promise(function (resolve, reject) { + var linearized = linearize(args); + Promise.all(linearized) + .then(function (values) { + values = delinearize(values); + if (wrappedAsyncs) { + unwrapAsyncs(values); + } + try { + var apply = parseElement.op.apply(parseElement, values); + resolve(apply); + } catch (e) { + reject(e); + } + }) + .catch(function (reason) { + if (ctx.meta.errorHandler && !ctx.meta.handlingError) { + ctx.meta.handlingError = true; + ctx[ctx.meta.errorSymmbol] = reason; + unifiedExec(ctx.meta.errorHandler, ctx); + } else if (ctx.meta.reject) { + ctx.meta.reject(reason); + } else { + // TODO: no meta context to reject with, trigger event? + } + }); + }); + } else { + if (wrappedAsyncs) { + unwrapAsyncs(args); + } + return parseElement.op.apply(parseElement, args); + } + } + + var _scriptAttrs = null; + + /** + * getAttributes returns the attribute name(s) to use when + * locating hyperscript scripts in a DOM element. If no value + * has been configured, it defaults to _hyperscript.config.attributes + * @returns string[] + */ + function getScriptAttributes() { + if (_scriptAttrs == null) { + _scriptAttrs = _hyperscript.config.attributes.replace(/ /g, "").split(","); + } + return _scriptAttrs; + } + + /** + * @param {HTMLElement} elt + * @returns {string | null} + */ + function getScript(elt) { + for (var i = 0; i < getScriptAttributes().length; i++) { + var scriptAttribute = getScriptAttributes()[i]; + if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) { + return elt.getAttribute(scriptAttribute); + } + } + if (elt["type"] === "text/hyperscript") { + return elt.innerText; + } + return null; + } + + /** + * @param {Object} owner + * @param {Context} ctx + */ + function addFeatures(owner, ctx) { + if (owner) { + if (owner.hyperscriptFeatures) { + mergeObjects(ctx, owner.hyperscriptFeatures); + } + addFeatures(owner.parentElement, ctx); + } + } + + /** + * @param {*} owner + * @param {*} feature + * @param {*} hyperscriptTarget + * @param {*} event + * @returns {Context} + */ + function makeContext(owner, feature, hyperscriptTarget, event) { + /** @type {Context} */ + var ctx = { + meta: { + parser: _parser, + lexer: _lexer, + runtime: _runtime, + owner: owner, + feature: feature, + iterators: {}, + }, + me: hyperscriptTarget, + event: event, + target: event ? event.target : null, + detail: event ? event.detail : null, + body: "document" in globalScope ? document.body : null, + }; + ctx.meta.ctx = ctx; + addFeatures(owner, ctx); + return ctx; + } + + /** + * @returns string + */ + function getScriptSelector() { + return getScriptAttributes() + .map(function (attribute) { + return "[" + attribute + "]"; + }) + .join(", "); + } + + /** + * @param {any} value + * @param {string} type + * @returns {any} + */ + function convertValue(value, type) { + var dynamicResolvers = CONVERSIONS.dynamicResolvers; + for (var i = 0; i < dynamicResolvers.length; i++) { + var dynamicResolver = dynamicResolvers[i]; + var converted = dynamicResolver(type, value); + if (converted !== undefined) { + return converted; + } + } + + if (value == null) { + return null; + } + var converter = CONVERSIONS[type]; + if (converter) { + return converter(value); + } + + throw "Unknown conversion : " + type; + } + + // TODO: There do not seem to be any references to this function. + // Is it still in use, or can it be removed? + function isType(o, type) { + return Object.prototype.toString.call(o) === "[object " + type + "]"; + } + + /** + * @param {string} src + * @returns {GrammarElement} + */ + function parse(src) { + var tokens = _lexer.tokenize(src); + if (_parser.commandStart(tokens.currentToken())) { + var commandList = _parser.parseElement("commandList", tokens); + var last = commandList; + while (last.next) { + last = last.next; + } + last.next = { + op: function () { + return HALT; + }, + }; + return commandList; + } else if (_parser.featureStart(tokens.currentToken())) { + var hyperscript = _parser.parseElement("hyperscript", tokens); + return hyperscript; + } else { + var expression = _parser.parseElement("expression", tokens); + return expression; + } + } + + /** + * @param {string} src + * @param {Context} ctx + * @returns {any} + */ + function evaluate(src, ctx) { + ctx = mergeObjects(makeContext(document.body, null, document.body, null), ctx || {}); + var element = parse(src); + if (element.execute) { + return element.execute(ctx); + } else if (element.apply) { + element.apply(document.body, null); + } else { + return element.evaluate(ctx); + } + } + + /** + * @param {HTMLElement} elt + */ + function processNode(elt) { + var selector = _runtime.getScriptSelector(); + if (matchesSelector(elt, selector)) { + initElement(elt, elt); + } + if (elt["type"] === "text/hyperscript") { + initElement(elt, document.body); + } + if (elt.querySelectorAll) { + forEach(elt.querySelectorAll(selector + ", [type='text/hyperscript']"), function (elt) { + initElement(elt, elt.type === "text/hyperscript" ? document.body : elt); + }); + } + } + + /** + * @param {HTMLElement} elt + * @param {HTMLElement} [target] + */ + function initElement(elt, target) { + if (elt.closest && elt.closest(_hyperscript.config.disableSelector)) { + return; + } + var internalData = getInternalData(elt); + if (!internalData.initialized) { + var src = getScript(elt); + if (src) { + try { + internalData.initialized = true; + internalData.script = src; + var tokens = _lexer.tokenize(src); + var hyperScript = _parser.parseHyperScript(tokens); + hyperScript.apply(target || elt, elt); + setTimeout(function () { + triggerEvent(target || elt, "load", { + hyperscript: true, + }); + }, 1); + } catch (e) { + _runtime.triggerEvent(elt, "exception", { + error: e, + }); + console.error( + "hyperscript errors were found on the following element:", + elt, + "\n\n", + e.message, + e.stack + ); + } + } + } + } + + /** + * @param {HTMLElement} elt + * @returns {Object} + */ + function getInternalData(elt) { + var dataProp = "hyperscript-internal-data"; + var data = elt[dataProp]; + if (!data) { + data = elt[dataProp] = {}; + } + return data; + } + + /** + * @param {any} value + * @param {string} typeString + * @param {boolean} [nullOk] + * @returns {boolean} + */ + function typeCheck(value, typeString, nullOk) { + if (value == null && nullOk) { + return true; + } + var typeName = Object.prototype.toString.call(value).slice(8, -1); + return typeName === typeString; + } + + function getElementScope(context) { + var elt = context.meta.owner; + if (elt) { + var internalData = getInternalData(elt); + var scopeName = "elementScope"; + if (context.meta.feature && context.meta.feature.behavior) { + scopeName = context.meta.feature.behavior + "Scope"; + } + var elementScope = getOrInitObject(internalData, scopeName); + return elementScope; + } else { + return {}; // no element, return empty scope + } + } + + /** + * @param {string} str + * @param {Context} context + * @returns {any} + */ + function resolveSymbol(str, context, type) { + if (str === "me" || str === "my" || str === "I") { + return context["me"]; + } + if (str === "it" || str === "its") { + return context["result"]; + } + if (str === "you" || str === "your" || str === "yourself") { + return context["beingTold"]; + } else { + if (type === "global") { + return globalScope[str]; + } else if (type === "element") { + var elementScope = getElementScope(context); + return elementScope[str]; + } else if (type === "local") { + return context[str]; + } else { + // meta scope (used for event conditionals) + if (context.meta && context.meta.context) { + var fromMetaContext = context.meta.context[str]; + if (typeof fromMetaContext !== "undefined") { + return fromMetaContext; + } + } + // local scope + var fromContext = context[str]; + if (typeof fromContext !== "undefined") { + return fromContext; + } else { + // element scope + var elementScope = getElementScope(context); + fromContext = elementScope[str]; + if (typeof fromContext !== "undefined") { + return fromContext; + } else { + // global scope + return globalScope[str]; + } + } + } + } + } + + function setSymbol(str, context, type, value) { + if (type === "global") { + globalScope[str] = value; + } else if (type === "element") { + var elementScope = getElementScope(context); + elementScope[str] = value; + } else if (type === "local") { + context[str] = value; + } else { + // local scope + var fromContext = context[str]; + if (typeof fromContext !== "undefined") { + context[str] = value; + } else { + // element scope + var elementScope = getElementScope(context); + fromContext = elementScope[str]; + if (typeof fromContext !== "undefined") { + elementScope[str] = value; + } else { + // global scope + fromContext = globalScope[str]; + if (typeof fromContext !== "undefined") { + globalScope[str] = value; + } else { + context[str] = value; + } + } + } + } + } + + /** + * @param {GrammarElement} command + * @param {Context} context + * @returns {undefined | GrammarElement} + */ + function findNext(command, context) { + if (command) { + if (command.resolveNext) { + return command.resolveNext(context); + } else if (command.next) { + return command.next; + } else { + return findNext(command.parent, context); + } + } + } + + /** + * @param {Object} root + * @param {string} property + * @param {boolean} attribute + * @returns {any} + */ + function resolveProperty(root, property, attribute) { + if (root != null) { + var val = attribute && root.getAttribute ? root.getAttribute(property) : root[property]; + if (typeof val !== "undefined") { + return val; + } + + if (isArrayLike(root)) { + // flat map + var result = []; + for (var i = 0; i < root.length; i++) { + var component = root[i]; + var componentValue = attribute ? component.getAttribute(property) : component[property]; + if (componentValue) { + result.push(componentValue); + } + } + return result; + } + } + } + + /** + * @param {Element} elt + * @param {string[]} nameSpace + * @param {string} name + * @param {any} value + */ + function assignToNamespace(elt, nameSpace, name, value) { + if (typeof document === "undefined" || elt === document.body) { + var root = globalScope; + } else { + var root = elt["hyperscriptFeatures"]; + if (root == null) { + root = {}; + elt["hyperscriptFeatures"] = root; + } + } + while (nameSpace.length > 0) { + var propertyName = nameSpace.shift(); + var newRoot = root[propertyName]; + if (newRoot == null) { + newRoot = {}; + root[propertyName] = newRoot; + } + root = newRoot; + } + + root[name] = value; + } + + function getHyperTrace(ctx, thrown) { + var trace = []; + var root = ctx; + while (root.meta.caller) { + root = root.meta.caller; + } + if (root.meta.traceMap) { + return root.meta.traceMap.get(thrown, trace); + } + } + + function registerHyperTrace(ctx, thrown) { + var trace = []; + var root = null; + while (ctx != null) { + trace.push(ctx); + root = ctx; + ctx = ctx.meta.caller; + } + if (root.meta.traceMap == null) { + root.meta.traceMap = new Map(); // TODO - WeakMap? + } + if (!root.meta.traceMap.get(thrown)) { + var traceEntry = { + trace: trace, + print: function (logger) { + logger = logger || console.error; + logger("hypertrace /// "); + var maxLen = 0; + for (var i = 0; i < trace.length; i++) { + maxLen = Math.max(maxLen, trace[i].meta.feature.displayName.length); + } + for (var i = 0; i < trace.length; i++) { + var traceElt = trace[i]; + logger( + " ->", + traceElt.meta.feature.displayName.padEnd(maxLen + 2), + "-", + traceElt.meta.owner + ); + } + }, + }; + root.meta.traceMap.set(thrown, traceEntry); + } + } + + /** + * @param {string} str + * @returns {string} + */ + function escapeSelector(str) { + return str.replace(/:/g, function (str) { + return "\\" + str; + }); + } + + /** + * @param {any} value + * @param {*} elt + */ + function nullCheck(value, elt) { + if (value == null) { + throw new Error(elt.sourceFor() + " is null"); + } + } + + /** + * @param {any} value + * @returns {boolean} + */ + function isEmpty(value) { + return value == undefined || value.length === 0; + } + + /** @type string | null */ + var hyperscriptUrl = "document" in globalScope ? document.currentScript.src : null; + + /** @type {RuntimeObject} */ + return { + typeCheck: typeCheck, + forEach: forEach, + triggerEvent: triggerEvent, + matchesSelector: matchesSelector, + getScript: getScript, + processNode: processNode, + evaluate: evaluate, + parse: parse, + getScriptSelector: getScriptSelector, + resolveSymbol: resolveSymbol, + setSymbol: setSymbol, + makeContext: makeContext, + findNext: findNext, + unifiedEval: unifiedEval, + convertValue: convertValue, + unifiedExec: unifiedExec, + resolveProperty: resolveProperty, + assignToNamespace: assignToNamespace, + registerHyperTrace: registerHyperTrace, + getHyperTrace: getHyperTrace, + getInternalData: getInternalData, + escapeSelector: escapeSelector, + nullCheck: nullCheck, + isEmpty: isEmpty, + hyperscriptUrl: hyperscriptUrl, + HALT: HALT, + }; + })(); + + //==================================================================== + // Grammar + //==================================================================== + { + _parser.addLeafExpression("parenthesized", function (parser, _runtime, tokens) { + if (tokens.matchOpToken("(")) { + var follows = tokens.clearFollow(); + try { + var expr = parser.requireElement("expression", tokens); + } finally { + tokens.restoreFollow(follows); + } + tokens.requireOpToken(")"); + return expr; + } + }); + + _parser.addLeafExpression("string", function (parser, runtime, tokens) { + var stringToken = tokens.matchTokenType("STRING"); + if (!stringToken) return; + var rawValue = stringToken.value; + if (stringToken.template) { + var innerTokens = _lexer.tokenize(rawValue, true); + var args = parser.parseStringTemplate(innerTokens); + } else { + var args = []; + } + return { + type: "string", + token: stringToken, + args: args, + op: function (context) { + var returnStr = ""; + for (var i = 1; i < arguments.length; i++) { + var val = arguments[i]; + if (val !== undefined) { + returnStr += val; + } + } + return returnStr; + }, + evaluate: function (context) { + if (args.length === 0) { + return rawValue; + } else { + return runtime.unifiedEval(this, context); + } + }, + }; + }); + + _parser.addGrammarElement("nakedString", function (parser, runtime, tokens) { + if (tokens.hasMore()) { + var tokenArr = tokens.consumeUntilWhitespace(); + tokens.matchTokenType("WHITESPACE"); + return { + type: "nakedString", + tokens: tokenArr, + evaluate: function (context) { + return tokenArr + .map(function (t) { + return t.value; + }) + .join(""); + }, + }; + } + }); + + _parser.addLeafExpression("number", function (parser, runtime, tokens) { + var number = tokens.matchTokenType("NUMBER"); + if (!number) return; + var numberToken = number; + var value = parseFloat(number.value); + return { + type: "number", + value: value, + numberToken: numberToken, + evaluate: function () { + return value; + }, + }; + }); + + _parser.addLeafExpression("idRef", function (parser, runtime, tokens) { + var elementId = tokens.matchTokenType("ID_REF"); + if (!elementId) return; + // TODO - unify these two expression types + if (elementId.template) { + var templateValue = elementId.value.substr(2, elementId.value.length - 2); + var innerTokens = _lexer.tokenize(templateValue); + var innerExpression = parser.requireElement("expression", innerTokens); + return { + type: "idRefTemplate", + args: [innerExpression], + op: function (context, arg) { + return context.me.getRootNode().getElementById(arg) || document.getElementById(arg); + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } else { + return { + type: "idRef", + css: elementId.value, + value: elementId.value.substr(1), + evaluate: function (context) { + return ( + context.me.getRootNode().getElementById(this.value) || document.getElementById(this.value) + ); + }, + }; + } + }); + + _parser.addLeafExpression("classRef", function (parser, runtime, tokens) { + var classRef = tokens.matchTokenType("CLASS_REF"); + + if (!classRef) return; + + // TODO - unify these two expression types + if (classRef.template) { + var templateValue = classRef.value.substr(2, classRef.value.length - 2); + var innerTokens = _lexer.tokenize(templateValue); + var innerExpression = parser.requireElement("expression", innerTokens); + return { + type: "classRefTemplate", + args: [innerExpression], + op: function (context, arg) { + return document.querySelectorAll(runtime.escapeSelector("." + arg)); + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } else { + return { + type: "classRef", + css: classRef.value, + className: function () { + return this.css.substr(1); + }, + evaluate: function (context) { + return context.me.getRootNode().querySelectorAll(runtime.escapeSelector(this.css)); + }, + }; + } + }); + + _parser.addLeafExpression("queryRef", function (parser, runtime, tokens) { + var queryStart = tokens.matchOpToken("<"); + if (!queryStart) return; + var queryTokens = tokens.consumeUntil("/"); + tokens.requireOpToken("/"); + tokens.requireOpToken(">"); + var queryValue = queryTokens + .map(function (t) { + if (t.type === "STRING") { + return '"' + t.value + '"'; + } else { + return t.value; + } + }) + .join(""); + + if (queryValue.indexOf("$") >= 0) { + var template = true; + var innerTokens = _lexer.tokenize(queryValue, true); + var args = parser.parseStringTemplate(innerTokens); + } + + return { + type: "queryRef", + css: queryValue, + args: args, + op: function (context, args) { + var query = queryValue; + var elements = []; + if (template) { + query = ""; + for (var i = 1; i < arguments.length; i++) { + var val = arguments[i]; + if (val) { + if (val instanceof Element) { + val.dataset.hsQueryId = elements.length; + query += "[data-hs-query-id='" + elements.length + "']"; + elements.push(val); + } else query += val; + } + } + } + var result = context.me.getRootNode().querySelectorAll(query); + runtime.forEach(elements, function (el) { el.removeAttribute("data-hs-query-id") }); + return result; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addLeafExpression("attributeRef", function (parser, runtime, tokens) { + var attributeRef = tokens.matchTokenType("ATTRIBUTE_REF"); + if (!attributeRef) return; + var outerVal = attributeRef.value; + if (outerVal.indexOf("[") === 0) { + var innerValue = outerVal.substring(2, outerVal.length - 1); + } else { + var innerValue = outerVal.substring(1); + } + var css = "[" + innerValue + "]"; + var split = innerValue.split("="); + var name = split[0]; + var value = split[1]; + if (value) { + // strip quotes + if (value.indexOf('"') === 0) { + value = value.substring(1, value.length - 1); + } + } + return { + type: "attributeRef", + name: name, + css: css, + value: value, + op: function (context) { + var target = context.beingTold || context.me; + if (target) { + return target.getAttribute(name); + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("objectKey", function (parser, runtime, tokens) { + var token; + if ((token = tokens.matchTokenType("STRING"))) { + return { + type: "objectKey", + key: token.value, + evaluate: function () { + return this.key; + }, + }; + } else if (tokens.matchOpToken("[")) { + var expr = parser.parseElement("expression", tokens); + tokens.requireOpToken("]"); + return { + type: "objectKey", + expr: expr, + args: [expr], + op: function (ctx, expr) { + return expr; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } else { + var key = ""; + do { + token = tokens.matchTokenType("IDENTIFIER") || tokens.matchOpToken("-"); + if (token) key += token.value; + } while (token); + return { + type: "objectKey", + key: key, + evaluate: function () { + return this.key; + }, + }; + } + }); + + _parser.addLeafExpression("objectLiteral", function (parser, runtime, tokens) { + if (!tokens.matchOpToken("{")) return; + var keyExpressions = []; + var valueExpressions = []; + if (!tokens.matchOpToken("}")) { + do { + var name = parser.requireElement("objectKey", tokens); + tokens.requireOpToken(":"); + var value = parser.requireElement("expression", tokens); + valueExpressions.push(value); + keyExpressions.push(name); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken("}"); + } + return { + type: "objectLiteral", + args: [keyExpressions, valueExpressions], + op: function (context, keys, values) { + var returnVal = {}; + for (var i = 0; i < keys.length; i++) { + returnVal[keys[i]] = values[i]; + } + return returnVal; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("namedArgumentList", function (parser, runtime, tokens) { + if (!tokens.matchOpToken("(")) return; + var fields = []; + var valueExpressions = []; + if (!tokens.matchOpToken(")")) { + do { + var name = tokens.requireTokenType("IDENTIFIER"); + tokens.requireOpToken(":"); + var value = parser.requireElement("expression", tokens); + valueExpressions.push(value); + fields.push({ name: name, value: value }); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken(")"); + } + return { + type: "namedArgumentList", + fields: fields, + args: [valueExpressions], + op: function (context, values) { + var returnVal = { _namedArgList_: true }; + for (var i = 0; i < values.length; i++) { + var field = fields[i]; + returnVal[field.name.value] = values[i]; + } + return returnVal; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("symbol", function (parser, runtime, tokens) { + var type = "default"; + if (tokens.matchToken("global")) { + type = "global"; + } else if (tokens.matchToken("element")) { + type = "element"; + // optional possessive + if (tokens.matchOpToken("'")) { + tokens.requireToken("s"); + } + } else if (tokens.matchToken("local")) { + type = "local"; + } + var identifier = tokens.matchTokenType("IDENTIFIER"); + if (identifier) { + return { + type: "symbol", + symbolType: type, + token: identifier, + name: identifier.value, + evaluate: function (context) { + return runtime.resolveSymbol(identifier.value, context, type); + }, + }; + } + }); + + _parser.addGrammarElement("implicitMeTarget", function (parser, runtime, tokens) { + return { + type: "implicitMeTarget", + evaluate: function (context) { + return context.beingTold || context.me; + }, + }; + }); + + _parser.addLeafExpression("boolean", function (parser, runtime, tokens) { + var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false"); + if (!booleanLiteral) return; + return { + type: "boolean", + evaluate: function (context) { + return booleanLiteral.value === "true"; + }, + }; + }); + + _parser.addLeafExpression("null", function (parser, runtime, tokens) { + if (tokens.matchToken("null")) { + return { + type: "null", + evaluate: function (context) { + return null; + }, + }; + } + }); + + _parser.addLeafExpression("arrayLiteral", function (parser, runtime, tokens) { + if (!tokens.matchOpToken("[")) return; + var values = []; + if (!tokens.matchOpToken("]")) { + do { + var expr = parser.requireElement("expression", tokens); + values.push(expr); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken("]"); + } + return { + type: "arrayLiteral", + values: values, + args: [values], + op: function (context, values) { + return values; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addLeafExpression("blockLiteral", function (parser, runtime, tokens) { + if (!tokens.matchOpToken("\\")) return; + var args = []; + var arg1 = tokens.matchTokenType("IDENTIFIER"); + if (arg1) { + args.push(arg1); + while (tokens.matchOpToken(",")) { + args.push(tokens.requireTokenType("IDENTIFIER")); + } + } + // TODO compound op token + tokens.requireOpToken("-"); + tokens.requireOpToken(">"); + var expr = parser.requireElement("expression", tokens); + return { + type: "blockLiteral", + args: args, + expr: expr, + evaluate: function (ctx) { + var returnFunc = function () { + //TODO - push scope + for (var i = 0; i < args.length; i++) { + ctx[args[i].value] = arguments[i]; + } + return expr.evaluate(ctx); //OK + }; + return returnFunc; + }, + }; + }); + + _parser.addGrammarElement("timeExpression", function (parser, runtime, tokens) { + var time = parser.requireElement("expression", tokens); + var factor = 1; + if (tokens.matchToken("s") || tokens.matchToken("seconds")) { + factor = 1000; + } else if (tokens.matchToken("ms") || tokens.matchToken("milliseconds")) { + // do nothing + } + return { + type: "timeExpression", + time: time, + factor: factor, + args: [time], + op: function (_context, val) { + return val * this.factor; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addIndirectExpression("propertyAccess", function (parser, runtime, tokens, root) { + if (!tokens.matchOpToken(".")) return; + var prop = tokens.requireTokenType("IDENTIFIER"); + var propertyAccess = { + type: "propertyAccess", + root: root, + prop: prop, + args: [root], + op: function (_context, rootVal) { + var value = runtime.resolveProperty(rootVal, prop.value); + return value; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + return parser.parseElement("indirectExpression", tokens, propertyAccess); + }); + + _parser.addIndirectExpression("of", function (parser, runtime, tokens, root) { + if (!tokens.matchToken("of")) return; + var newRoot = parser.requireElement("expression", tokens); + // find the urroot + var childOfUrRoot = null; + var urRoot = root; + while (urRoot.root) { + childOfUrRoot = urRoot; + urRoot = urRoot.root; + } + if (urRoot.type !== "symbol" && urRoot.type !== "attributeRef") { + parser.raiseParseError(tokens, "Cannot take a property of a non-symbol: " + urRoot.type); + } + var attribute = urRoot.type === "attributeRef"; + var prop = urRoot.name; + var propertyAccess = { + type: "ofExpression", + prop: urRoot.token, + root: newRoot, + attribute: attribute, + expression: root, + args: [newRoot], + op: function (context, rootVal) { + return runtime.resolveProperty(rootVal, prop, attribute); + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + + if (urRoot.type === "attributeRef") { + propertyAccess.attribute = urRoot; + } + if (childOfUrRoot) { + childOfUrRoot.root = propertyAccess; + childOfUrRoot.args = [propertyAccess]; + } else { + root = propertyAccess; + } + + return parser.parseElement("indirectExpression", tokens, root); + }); + + _parser.addIndirectExpression("possessive", function (parser, runtime, tokens, root) { + if (parser.possessivesDisabled) { + return; + } + var apostrophe = tokens.matchOpToken("'"); + if ( + apostrophe || + (root.type === "symbol" && + (root.name === "my" || root.name === "its" || root.name === "your") && + tokens.currentToken().type === "IDENTIFIER") + ) { + if (apostrophe) { + tokens.requireToken("s"); + } + var attribute = parser.parseElement("attributeRef", tokens); + if (attribute == null) { + var prop = tokens.requireTokenType("IDENTIFIER"); + } + var propertyAccess = { + type: "possessive", + root: root, + attribute: attribute, + prop: prop, + args: [root], + op: function (context, rootVal) { + if (attribute) { + var value = runtime.resolveProperty(rootVal, attribute.name, true); + } else { + var value = runtime.resolveProperty(rootVal, prop.value, false); + } + return value; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + return parser.parseElement("indirectExpression", tokens, propertyAccess); + } + }); + + _parser.addIndirectExpression("inExpression", function (parser, runtime, tokens, root) { + if (!tokens.matchToken("in")) return; + if ((root.type !== "idRef" && root.type === "queryRef") || root.type === "classRef") { + var query = true; + } + var target = parser.requireElement("expression", tokens); + var propertyAccess = { + type: "inExpression", + root: root, + args: [query ? null : root, target], + op: function (context, rootVal, target) { + var returnArr = []; + if (query) { + runtime.forEach(target, function (targetElt) { + var results = targetElt.querySelectorAll(root.css); + for (var i = 0; i < results.length; i++) { + returnArr.push(results[i]); + } + }); + } else { + runtime.forEach(rootVal, function (rootElt) { + runtime.forEach(target, function (targetElt) { + if (rootElt === targetElt) { + returnArr.push(rootElt); + } + }); + }); + } + if (returnArr.length > 0) { + return returnArr; + } else { + return null; + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + return parser.parseElement("indirectExpression", tokens, propertyAccess); + }); + + _parser.addIndirectExpression("asExpression", function (parser, runtime, tokens, root) { + if (!tokens.matchToken("as")) return; + var conversion = parser.requireElement("dotOrColonPath", tokens).evaluate(); // OK No promise + var propertyAccess = { + type: "asExpression", + root: root, + args: [root], + op: function (context, rootVal) { + return runtime.convertValue(rootVal, conversion); + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + return parser.parseElement("indirectExpression", tokens, propertyAccess); + }); + + _parser.addIndirectExpression("functionCall", function (parser, runtime, tokens, root) { + if (!tokens.matchOpToken("(")) return; + var args = []; + if (!tokens.matchOpToken(")")) { + do { + args.push(parser.requireElement("expression", tokens)); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken(")"); + } + + if (root.root) { + var functionCall = { + type: "functionCall", + root: root, + argExressions: args, + args: [root.root, args], + op: function (context, rootRoot, args) { + runtime.nullCheck(rootRoot, root.root); + var func = rootRoot[root.prop.value]; + runtime.nullCheck(func, root); + if (func.hyperfunc) { + args.push(context); + } + return func.apply(rootRoot, args); + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } else { + var functionCall = { + type: "functionCall", + root: root, + argExressions: args, + args: [root, args], + op: function (context, func, argVals) { + runtime.nullCheck(func, root); + if (func.hyperfunc) { + argVals.push(context); + } + var apply = func.apply(null, argVals); + return apply; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } + return parser.parseElement("indirectExpression", tokens, functionCall); + }); + + _parser.addIndirectExpression("attributeRefAccess", function (parser, runtime, tokens, root) { + var attribute = parser.parseElement("attributeRef", tokens); + if (!attribute) return; + var attributeAccess = { + type: "attributeRefAccess", + root: root, + attribute: attribute, + args: [root], + op: function (_ctx, rootVal) { + var value = runtime.resolveProperty(rootVal, attribute.name, true); + return value; + }, + evaluate: function (context) { + return _runtime.unifiedEval(this, context); + }, + }; + return attributeAccess; + }); + + _parser.addIndirectExpression("arrayIndex", function (parser, runtime, tokens, root) { + if (!tokens.matchOpToken("[")) return; + var andBefore = false; + var andAfter = false; + var firstIndex = null; + var secondIndex = null; + + if (tokens.matchOpToken("..")) { + andBefore = true; + firstIndex = parser.requireElement("expression", tokens); + } else { + firstIndex = parser.requireElement("expression", tokens); + + if (tokens.matchOpToken("..")) { + andAfter = true; + var current = tokens.currentToken(); + if (current.type !== "R_BRACKET") { + secondIndex = parser.parseElement("expression", tokens); + } + } + } + tokens.requireOpToken("]"); + + var arrayIndex = { + type: "arrayIndex", + root: root, + firstIndex: firstIndex, + secondIndex: secondIndex, + args: [root, firstIndex, secondIndex], + op: function (_ctx, root, firstIndex, secondIndex) { + if (andBefore) { + return root.slice(0, firstIndex + 1); // returns all items from beginning to firstIndex (inclusive) + } else if (andAfter) { + if (secondIndex != null) { + return root.slice(firstIndex, secondIndex + 1); // returns all items from firstIndex to secondIndex (inclusive) + } else { + return root.slice(firstIndex); // returns from firstIndex to end of array + } + } else { + return root[firstIndex]; + } + }, + evaluate: function (context) { + return _runtime.unifiedEval(this, context); + }, + }; + + return _parser.parseElement("indirectExpression", tokens, arrayIndex); + }); + + _parser.addGrammarElement("postfixExpression", function (parser, runtime, tokens) { + var root = parser.parseElement("primaryExpression", tokens); + if (tokens.matchOpToken(":")) { + var typeName = tokens.requireTokenType("IDENTIFIER"); + var nullOk = !tokens.matchOpToken("!"); + return { + type: "typeCheck", + typeName: typeName, + root: root, + nullOk: nullOk, + args: [root], + op: function (context, val) { + var passed = runtime.typeCheck(val, this.typeName.value, this.nullOk); + if (passed) { + return val; + } else { + throw new Error("Typecheck failed! Expected: " + this.typeName.value); + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } else { + return root; + } + }); + + _parser.addGrammarElement("logicalNot", function (parser, runtime, tokens) { + if (!tokens.matchToken("not")) return; + var root = parser.requireElement("unaryExpression", tokens); + return { + type: "logicalNot", + root: root, + args: [root], + op: function (context, val) { + return !val; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("noExpression", function (parser, runtime, tokens) { + if (!tokens.matchToken("no")) return; + var root = parser.requireElement("unaryExpression", tokens); + return { + type: "noExpression", + root: root, + args: [root], + op: function (_context, val) { + return runtime.isEmpty(val); + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("negativeNumber", function (parser, runtime, tokens) { + if (!tokens.matchOpToken("-")) return; + var root = parser.requireElement("unaryExpression", tokens); + return { + type: "negativeNumber", + root: root, + args: [root], + op: function (context, value) { + return -1 * value; + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("unaryExpression", function (parser, runtime, tokens) { + return parser.parseAnyOf( + ["logicalNot", "positionalExpression", "noExpression", "negativeNumber", "postfixExpression"], + tokens + ); + }); + + _parser.addGrammarElement("positionalExpression", function (parser, runtime, tokens) { + var op = tokens.matchAnyToken("first", "last", "random"); + if (!op) return; + tokens.matchAnyToken("in", "from", "of"); + var rhs = parser.requireElement("unaryExpression", tokens); + return { + type: "positionalExpression", + rhs: rhs, + operator: op.value, + args: [rhs], + op: function (context, rhsVal) { + if (rhsVal && !Array.isArray(rhsVal)) { + if (rhsVal.children) { + rhsVal = rhsVal.children; + } else { + rhsVal = Array.from(rhsVal); + } + } + if (rhsVal) { + if (this.operator === "first") { + return rhsVal[0]; + } else if (this.operator === "last") { + return rhsVal[rhsVal.length - 1]; + } else if (this.operator === "random") { + return rhsVal[Math.floor(Math.random() * rhsVal.length)]; + } + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + }); + + _parser.addGrammarElement("mathOperator", function (parser, runtime, tokens) { + var expr = parser.parseElement("unaryExpression", tokens); + var mathOp, + initialMathOp = null; + mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%"); + while (mathOp) { + initialMathOp = initialMathOp || mathOp; + var operator = mathOp.value; + if (initialMathOp.value !== operator) { + parser.raiseParseError(tokens, "You must parenthesize math operations with different operators"); + } + var rhs = parser.parseElement("unaryExpression", tokens); + expr = { + type: "mathOperator", + lhs: expr, + rhs: rhs, + operator: operator, + args: [expr, rhs], + op: function (context, lhsVal, rhsVal) { + if (this.operator === "+") { + return lhsVal + rhsVal; + } else if (this.operator === "-") { + return lhsVal - rhsVal; + } else if (this.operator === "*") { + return lhsVal * rhsVal; + } else if (this.operator === "/") { + return lhsVal / rhsVal; + } else if (this.operator === "%") { + return lhsVal % rhsVal; + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%"); + } + return expr; + }); + + _parser.addGrammarElement("mathExpression", function (parser, runtime, tokens) { + return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens); + }); + + _parser.addGrammarElement("comparisonOperator", function (parser, runtime, tokens) { + var expr = parser.parseElement("mathExpression", tokens); + var comparisonToken = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!=="); + var comparisonStr = comparisonToken ? comparisonToken.value : null; + var hasRightValue = true; // By default, most comparisons require two values, but there are some exceptions. + var typeCheck = false; + + if (comparisonStr == null) { + if (tokens.matchToken("is") || tokens.matchToken("am")) { + if (tokens.matchToken("not")) { + if (tokens.matchToken("in")) { + comparisonStr = "not in"; + } else if (tokens.matchToken("a")) { + comparisonStr = "not a"; + typeCheck = true; + } else if (tokens.matchToken("empty")) { + comparisonStr = "not empty"; + hasRightValue = false; + } else { + comparisonStr = "!="; + } + } else if (tokens.matchToken("in")) { + comparisonStr = "in"; + } else if (tokens.matchToken("a")) { + comparisonStr = "a"; + typeCheck = true; + } else if (tokens.matchToken("empty")) { + comparisonStr = "empty"; + hasRightValue = false; + } else { + comparisonStr = "=="; + } + } else if (tokens.matchToken("matches") || tokens.matchToken("match")) { + comparisonStr = "match"; + } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) { + comparisonStr = "contain"; + } else if (tokens.matchToken("do") || tokens.matchToken("does")) { + tokens.requireToken("not"); + if (tokens.matchToken("matches") || tokens.matchToken("match")) { + comparisonStr = "not match"; + } else if (tokens.matchToken("contains") || tokens.matchToken("contain")) { + comparisonStr = "not contain"; + } else { + parser.raiseParseError(tokens, "Expected matches or contains"); + } + } + } + + if (comparisonStr) { + // Do not allow chained comparisons, which is dumb + if (typeCheck) { + var typeName = tokens.requireTokenType("IDENTIFIER"); + var nullOk = !tokens.matchOpToken("!"); + } else if (hasRightValue) { + var rhs = parser.requireElement("mathExpression", tokens); + if (comparisonStr === "match" || comparisonStr === "not match") { + rhs = rhs.css ? rhs.css : rhs; + } + } + expr = { + type: "comparisonOperator", + operator: comparisonStr, + typeName: typeName, + nullOk: nullOk, + lhs: expr, + rhs: rhs, + args: [expr, rhs], + op: function (context, lhsVal, rhsVal) { + if (this.operator === "==") { + return lhsVal == rhsVal; + } else if (this.operator === "!=") { + return lhsVal != rhsVal; + } + if (this.operator === "in") { + return rhsVal != null && Array.from(rhsVal).indexOf(lhsVal) >= 0; + } + if (this.operator === "not in") { + return rhsVal == null || Array.from(rhsVal).indexOf(lhsVal) < 0; + } + if (this.operator === "match") { + return lhsVal != null && lhsVal.matches(rhsVal); + } + if (this.operator === "not match") { + return lhsVal == null || !lhsVal.matches(rhsVal); + } + if (this.operator === "contain") { + return lhsVal != null && lhsVal.contains(rhsVal); + } + if (this.operator === "not contain") { + return lhsVal == null || !lhsVal.contains(rhsVal); + } + if (this.operator === "===") { + return lhsVal === rhsVal; + } else if (this.operator === "!==") { + return lhsVal !== rhsVal; + } else if (this.operator === "<") { + return lhsVal < rhsVal; + } else if (this.operator === ">") { + return lhsVal > rhsVal; + } else if (this.operator === "<=") { + return lhsVal <= rhsVal; + } else if (this.operator === ">=") { + return lhsVal >= rhsVal; + } else if (this.operator === "empty") { + return runtime.isEmpty(lhsVal); + } else if (this.operator === "not empty") { + return !runtime.isEmpty(lhsVal); + } else if (this.operator === "a") { + return runtime.typeCheck(lhsVal, this.typeName.value, this.nullOk); + } else if (this.operator === "not a") { + return !runtime.typeCheck(lhsVal, this.typeName.value, this.nullOk); + } else { + throw "Unknown comparison : " + this.operator; + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + } + return expr; + }); + + _parser.addGrammarElement("comparisonExpression", function (parser, runtime, tokens) { + return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens); + }); + + _parser.addGrammarElement("logicalOperator", function (parser, runtime, tokens) { + var expr = parser.parseElement("comparisonExpression", tokens); + var logicalOp, + initialLogicalOp = null; + logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); + while (logicalOp) { + initialLogicalOp = initialLogicalOp || logicalOp; + if (initialLogicalOp.value !== logicalOp.value) { + parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators"); + } + var rhs = parser.requireElement("comparisonExpression", tokens); + expr = { + type: "logicalOperator", + operator: logicalOp.value, + lhs: expr, + rhs: rhs, + args: [expr, rhs], + op: function (context, lhsVal, rhsVal) { + if (this.operator === "and") { + return lhsVal && rhsVal; + } else { + return lhsVal || rhsVal; + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); + } + return expr; + }); + + _parser.addGrammarElement("logicalExpression", function (parser, runtime, tokens) { + return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens); + }); + + _parser.addGrammarElement("asyncExpression", function (parser, runtime, tokens) { + if (tokens.matchToken("async")) { + var value = parser.requireElement("logicalExpression", tokens); + var expr = { + type: "asyncExpression", + value: value, + evaluate: function (context) { + return { + asyncWrapper: true, + value: this.value.evaluate(context), //OK + }; + }, + }; + return expr; + } else { + return parser.parseElement("logicalExpression", tokens); + } + }); + + _parser.addGrammarElement("expression", function (parser, runtime, tokens) { + tokens.matchToken("the"); // optional the + return parser.parseElement("asyncExpression", tokens); + }); + + _parser.addGrammarElement("assignableExpression", function (parser, runtime, tokens) { + tokens.matchToken("the"); // optional the + + // TODO obviously we need to generalize this as a left hand side / targetable concept + var expr = parser.parseElement("primaryExpression", tokens); + if ( + expr.type === "symbol" || + expr.type === "ofExpression" || + expr.type === "propertyAccess" || + expr.type === "attributeRefAccess" || + expr.type === "attributeRef" || + expr.type === "possessive" + ) { + return expr; + } else { + _parser.raiseParseError( + tokens, + "A target expression must be writable. The expression type '" + expr.type + "' is not." + ); + } + return expr; + }); + + _parser.addGrammarElement("hyperscript", function (parser, runtime, tokens) { + var features = []; + + if (tokens.hasMore()) { + while (parser.featureStart(tokens.currentToken()) || tokens.currentToken().value === "(") { + var feature = parser.requireElement("feature", tokens); + features.push(feature); + tokens.matchToken("end"); // optional end + } + } + return { + type: "hyperscript", + features: features, + apply: function (target, source, args) { + // no op + _runtime.forEach(features, function (feature) { + feature.install(target, source, args); + }); + }, + }; + }); + + var parseEventArgs = function (tokens) { + var args = []; + // handle argument list (look ahead 3) + if ( + tokens.token(0).value === "(" && + (tokens.token(1).value === ")" || tokens.token(2).value === "," || tokens.token(2).value === ")") + ) { + tokens.matchOpToken("("); + do { + args.push(tokens.requireTokenType("IDENTIFIER")); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken(")"); + } + return args; + }; + + _parser.addFeature("on", function (parser, runtime, tokens) { + if (!tokens.matchToken("on")) return; + var every = false; + if (tokens.matchToken("every")) { + every = true; + } + var events = []; + var displayName = null; + do { + var on = parser.requireElement("eventName", tokens, "Expected event name"); + + var eventName = on.evaluate(); // OK No Promise + + if (displayName) { + displayName = displayName + " or " + eventName; + } else { + displayName = "on " + eventName; + } + var args = parseEventArgs(tokens); + + var filter = null; + if (tokens.matchOpToken("[")) { + filter = parser.requireElement("expression", tokens); + tokens.requireOpToken("]"); + } + + if (tokens.currentToken().type === "NUMBER") { + var startCountToken = tokens.consumeToken(); + var startCount = parseInt(startCountToken.value); + if (tokens.matchToken("to")) { + var endCountToken = tokens.consumeToken(); + var endCount = parseInt(endCountToken.value); + } else if (tokens.matchToken("and")) { + var unbounded = true; + tokens.requireToken("on"); + } + } + + if (eventName === "intersection") { + var intersectionSpec = {}; + if (tokens.matchToken("with")) { + intersectionSpec["with"] = parser.parseElement("expression", tokens).evaluate(); + } + if (tokens.matchToken("having")) { + do { + if (tokens.matchToken("margin")) { + intersectionSpec["rootMargin"] = parser.parseElement("stringLike", tokens).evaluate(); + } else if (tokens.matchToken("threshold")) { + intersectionSpec["threshold"] = parser.parseElement("expression", tokens).evaluate(); + } else { + parser.raiseParseError(tokens, "Unknown intersection config specification"); + } + } while (tokens.matchToken("and")); + } + } else if (eventName === "mutation") { + var mutationSpec = {}; + if (tokens.matchToken("of")) { + do { + if (tokens.matchToken("anything")) { + mutationSpec["attributes"] = true; + mutationSpec["subtree"] = true; + mutationSpec["characterData"] = true; + mutationSpec["childList"] = true; + } else if (tokens.matchToken("childList")) { + mutationSpec["childList"] = true; + } else if (tokens.matchToken("attributes")) { + mutationSpec["attributes"] = true; + mutationSpec["attributeOldValue"] = true; + } else if (tokens.matchToken("subtree")) { + mutationSpec["subtree"] = true; + } else if (tokens.matchToken("characterData")) { + mutationSpec["characterData"] = true; + mutationSpec["characterDataOldValue"] = true; + } else if (tokens.currentToken().type === "ATTRIBUTE_REF") { + var attribute = tokens.consumeToken(); + if (mutationSpec["attributeFilter"] == null) { + mutationSpec["attributeFilter"] = []; + } + if (attribute.value.indexOf("@") == 0) { + mutationSpec["attributeFilter"].push(attribute.value.substring(1)); + } else { + parser.raiseParseError( + tokens, + "Only shorthand attribute references are allowed here" + ); + } + } else { + parser.raiseParseError(tokens, "Unknown mutation config specification"); + } + } while (tokens.matchToken("or")); + } else { + mutationSpec["attributes"] = true; + mutationSpec["characterData"] = true; + mutationSpec["childList"] = true; + } + } + + var from = null; + var elsewhere = false; + if (tokens.matchToken("from")) { + if (tokens.matchToken("elsewhere")) { + elsewhere = true; + } else { + from = parser.parseElement("expression", tokens); + if (!from) { + parser.raiseParseError('Expected either target value or "elsewhere".', tokens); + } + } + } + // support both "elsewhere" and "from elsewhere" + if (from === null && elsewhere === false && tokens.matchToken("elsewhere")) { + elsewhere = true; + } + + if (tokens.matchToken("in")) { + var inExpr = parser.parseAnyOf(["idRef", "queryRef", "classRef"], tokens); + } + + if (tokens.matchToken("debounced")) { + tokens.requireToken("at"); + var timeExpr = parser.requireElement("timeExpression", tokens); + var debounceTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr + } else if (tokens.matchToken("throttled")) { + tokens.requireToken("at"); + var timeExpr = parser.requireElement("timeExpression", tokens); + var throttleTime = timeExpr.evaluate({}); // OK No promise TODO make a literal time expr + } + + events.push({ + execCount: 0, + every: every, + on: eventName, + args: args, + filter: filter, + from: from, + inExpr: inExpr, + elsewhere: elsewhere, + startCount: startCount, + endCount: endCount, + unbounded: unbounded, + debounceTime: debounceTime, + throttleTime: throttleTime, + mutationSpec: mutationSpec, + intersectionSpec: intersectionSpec, + }); + } while (tokens.matchToken("or")); + + var queue = []; + var queueLast = true; + if (!every) { + if (tokens.matchToken("queue")) { + if (tokens.matchToken("all")) { + var queueAll = true; + var queueLast = false; + } else if (tokens.matchToken("first")) { + var queueFirst = true; + } else if (tokens.matchToken("none")) { + var queueNone = true; + } else { + tokens.requireToken("last"); + } + } + } + + var start = parser.requireElement("commandList", tokens); + + var implicitReturn = { + type: "implicitReturn", + op: function (context) { + // automatically resolve at the end of an event handler if nothing else does + context.meta.resolve(); + return runtime.HALT; + }, + execute: function (ctx) { + // do nothing + }, + }; + if (start) { + var end = start; + while (end.next) { + end = end.next; + } + end.next = implicitReturn; + } else { + start = implicitReturn; + } + + var onFeature = { + displayName: displayName, + events: events, + start: start, + every: every, + executing: false, + execCount: 0, + queue: queue, + execute: function (/** @type {Context} */ ctx) { + if (this.executing && this.every === false) { + if (queueNone || (queueFirst && queue.length > 0)) { + return; + } + if (queueLast) { + onFeature.queue.length = 0; + } + onFeature.queue.push(ctx); + return; + } + this.execCount++; + this.executing = true; + ctx.meta.resolve = function () { + onFeature.executing = false; + var queued = onFeature.queue.shift(); + if (queued) { + setTimeout(function () { + onFeature.execute(queued); + }, 1); + } + }; + ctx.meta.reject = function (err) { + console.error(err.message ? err.message : err); + var hypertrace = runtime.getHyperTrace(ctx, err); + if (hypertrace) { + hypertrace.print(); + } + runtime.triggerEvent(ctx.me, "exception", { + error: err, + }); + onFeature.executing = false; + var queued = onFeature.queue.shift(); + if (queued) { + setTimeout(function () { + onFeature.execute(queued); + }, 1); + } + }; + start.execute(ctx); + }, + install: function (elt, source) { + runtime.forEach(onFeature.events, function (eventSpec) { + var targets; + if (eventSpec.elsewhere) { + targets = [document]; + } else if (eventSpec.from) { + targets = eventSpec.from.evaluate({ + me: elt, + }); + } else { + targets = [elt]; + } + runtime.forEach(targets, function (target) { + // OK NO PROMISE + + var eventName = eventSpec.on; + if (eventSpec.mutationSpec) { + eventName = "hyperscript:mutation"; + var observer = new MutationObserver(function (mutationList, observer) { + console.log(target, mutationList); + if (!onFeature.executing) { + _runtime.triggerEvent(target, eventName, { + mutationList: mutationList, + observer: observer, + }); + } + }); + observer.observe(target, eventSpec.mutationSpec); + } + + if (eventSpec.intersectionSpec) { + eventName = "hyperscript:insersection"; + var observer = new IntersectionObserver(function (entries) { + _runtime.forEach(entries, function (entry) { + var detail = { + observer: observer, + }; + detail = mergeObjects(detail, entry); + detail["intersecting"] = entry.isIntersecting; + _runtime.triggerEvent(target, eventName, detail); + }); + }, eventSpec.intersectionSpec); + observer.observe(target); + } + + target.addEventListener(eventName, function listener(evt) { + // OK NO PROMISE + if (elt instanceof Node && target !== elt && elt.getRootNode() === null) { + target.removeEventListener(eventName, listener); + return; + } + + var ctx = runtime.makeContext(elt, onFeature, elt, evt); + if (eventSpec.elsewhere && elt.contains(evt.target)) { + return; + } + if (eventSpec.from) { + ctx.result = target; + } + + // establish context + runtime.forEach(eventSpec.args, function (arg) { + ctx[arg.value] = + ctx.event[arg.value] || (ctx.event.detail ? ctx.event.detail[arg.value] : null); + }); + + // apply filter + if (eventSpec.filter) { + var initialCtx = ctx.meta.context; + ctx.meta.context = ctx.event; + try { + var value = eventSpec.filter.evaluate(ctx); //OK NO PROMISE + if (value) { + // match the javascript semantics for if statements + } else { + return; + } + } finally { + ctx.meta.context = initialCtx; + } + } + + if (eventSpec.inExpr) { + var inElement = evt.target; + while (true) { + if (inElement.matches && inElement.matches(eventSpec.inExpr.css)) { + ctx.result = inElement; + break; + } else { + inElement = inElement.parentElement; + if (inElement == null) { + return; // no match found + } + } + } + } + + // verify counts + eventSpec.execCount++; + if (eventSpec.startCount) { + if (eventSpec.endCount) { + if ( + eventSpec.execCount < eventSpec.startCount || + eventSpec.execCount > eventSpec.endCount + ) { + return; + } + } else if (eventSpec.unbounded) { + if (eventSpec.execCount < eventSpec.startCount) { + return; + } + } else if (eventSpec.execCount !== eventSpec.startCount) { + return; + } + } + + //debounce + if (eventSpec.debounceTime) { + if (eventSpec.debounced) { + clearTimeout(eventSpec.debounced); + } + eventSpec.debounced = setTimeout(function () { + onFeature.execute(ctx); + }, eventSpec.debounceTime); + return; + } + + // throttle + if (eventSpec.throttleTime) { + if ( + eventSpec.lastExec && + Date.now() < eventSpec.lastExec + eventSpec.throttleTime + ) { + return; + } else { + eventSpec.lastExec = Date.now(); + } + } + + // apply execute + onFeature.execute(ctx); + }); + }); + }); + }, + }; + parser.setParent(start, onFeature); + return onFeature; + }); + + _parser.addFeature("def", function (parser, runtime, tokens) { + if (!tokens.matchToken("def")) return; + var functionName = parser.requireElement("dotOrColonPath", tokens); + var nameVal = functionName.evaluate(); // OK + var nameSpace = nameVal.split("."); + var funcName = nameSpace.pop(); + + var args = []; + if (tokens.matchOpToken("(")) { + if (tokens.matchOpToken(")")) { + // emtpy args list + } else { + do { + args.push(tokens.requireTokenType("IDENTIFIER")); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken(")"); + } + } + + var start = parser.parseElement("commandList", tokens); + if (tokens.matchToken("catch")) { + var errorSymbol = tokens.requireTokenType("IDENTIFIER").value; + var errorHandler = parser.parseElement("commandList", tokens); + } + var functionFeature = { + displayName: + funcName + + "(" + + args + .map(function (arg) { + return arg.value; + }) + .join(", ") + + ")", + name: funcName, + args: args, + start: start, + errorHandler: errorHandler, + errorSymbol: errorSymbol, + install: function (target, source) { + var func = function () { + // null, worker + var ctx = runtime.makeContext(source, functionFeature, target, null); + + // install error handler if any + ctx.meta.errorHandler = errorHandler; + ctx.meta.errorSymmbol = errorSymbol; + + for (var i = 0; i < args.length; i++) { + var name = args[i]; + var argumentVal = arguments[i]; + if (name) { + ctx[name.value] = argumentVal; + } + } + ctx.meta.caller = arguments[args.length]; + if (ctx.meta.caller) { + ctx.meta.callingCommand = ctx.meta.caller.meta.command; + } + var resolve, + reject = null; + var promise = new Promise(function (theResolve, theReject) { + resolve = theResolve; + reject = theReject; + }); + start.execute(ctx); + if (ctx.meta.returned) { + return ctx.meta.returnValue; + } else { + ctx.meta.resolve = resolve; + ctx.meta.reject = reject; + return promise; + } + }; + func.hyperfunc = true; + func.hypername = nameVal; + runtime.assignToNamespace(target, nameSpace, funcName, func); + }, + }; + + var implicitReturn = { + type: "implicitReturn", + op: function (context) { + // automatically return at the end of the function if nothing else does + context.meta.returned = true; + if (context.meta.resolve) { + context.meta.resolve(); + } + return runtime.HALT; + }, + execute: function (context) { + // do nothing + }, + }; + // terminate body + if (start) { + var end = start; + while (end.next) { + end = end.next; + } + end.next = implicitReturn; + } else { + functionFeature.start = implicitReturn; + } + + // terminate error handler + if (errorHandler) { + var end = errorHandler; + while (end.next) { + end = end.next; + } + end.next = implicitReturn; + } + + parser.setParent(start, functionFeature); + return functionFeature; + }); + + _parser.addFeature("init", function (parser, runtime, tokens) { + if (!tokens.matchToken("init")) return; + + var start = parser.parseElement("commandList", tokens); + var initFeature = { + start: start, + install: function (target, source) { + setTimeout(function () { + start.execute(runtime.makeContext(target, this, target, null)); + }, 0); + }, + }; + + var implicitReturn = { + type: "implicitReturn", + op: function (context) { + return runtime.HALT; + }, + execute: function (context) { + // do nothing + }, + }; + // terminate body + if (start) { + var end = start; + while (end.next) { + end = end.next; + } + end.next = implicitReturn; + } else { + initFeature.start = implicitReturn; + } + parser.setParent(start, initFeature); + return initFeature; + }); + + _parser.addFeature("worker", function (parser, runtime, tokens) { + if (tokens.matchToken("worker")) { + parser.raiseParseError( + tokens, + "In order to use the 'worker' feature, include " + + "the _hyperscript worker plugin. See " + + "https://hyperscript.org/features/worker/ for " + + "more info." + ); + } + }); + + _parser.addFeature("behavior", function (parser, runtime, tokens) { + if (!tokens.matchToken("behavior")) return; + var path = parser.parseElement("dotOrColonPath", tokens).evaluate(); + var nameSpace = path.split("."); + var name = nameSpace.pop(); + + var formalParams = []; + if (tokens.matchOpToken("(") && !tokens.matchOpToken(")")) { + do { + formalParams.push(tokens.requireTokenType("IDENTIFIER").value); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken(")"); + } + var hs = parser.parseElement("hyperscript", tokens); + for (var i = 0; i < hs.features.length; i++) { + var feature = hs.features[i]; + feature.behavior = path; + } + + return { + install: function (target, source) { + runtime.assignToNamespace( + globalScope.document && globalScope.document.body, + nameSpace, + name, + function (target, source, innerArgs) { + var internalData = runtime.getInternalData(target); + var elementScope = getOrInitObject(internalData, path + "Scope"); + for (var i = 0; i < formalParams.length; i++) { + elementScope[formalParams[i]] = innerArgs[formalParams[i]]; + } + hs.apply(target, source); + } + ); + }, + }; + }); + + _parser.addFeature("install", function (parser, runtime, tokens) { + if (!tokens.matchToken("install")) return; + var behaviorPath = parser.requireElement("dotOrColonPath", tokens).evaluate(); + var behaviorNamespace = behaviorPath.split("."); + var args = parser.parseElement("namedArgumentList", tokens); + + var installFeature; + return (installFeature = { + install: function (target, source) { + runtime.unifiedEval( + { + args: [args], + op: function (ctx, args) { + var behavior = globalScope; + for (var i = 0; i < behaviorNamespace.length; i++) { + behavior = behavior[behaviorNamespace[i]]; + if (typeof behavior !== "object" && typeof behavior !== "function") + throw new Error("No such behavior defined as " + behaviorPath); + } + + if (!(behavior instanceof Function)) + throw new Error(behaviorPath + " is not a behavior"); + + behavior(target, source, args); + }, + }, + runtime.makeContext(target, installFeature, target) + ); + }, + }); + }); + + _parser.addGrammarElement("jsBody", function (parser, runtime, tokens) { + var jsSourceStart = tokens.currentToken().start; + var jsLastToken = tokens.currentToken(); + + var funcNames = []; + var funcName = ""; + var expectFunctionDeclaration = false; + while (tokens.hasMore()) { + jsLastToken = tokens.consumeToken(); + var peek = tokens.currentToken(true); + if (peek.type === "IDENTIFIER" && peek.value === "end") { + break; + } + if (expectFunctionDeclaration) { + if (jsLastToken.type === "IDENTIFIER" || jsLastToken.type === "NUMBER") { + funcName += jsLastToken.value; + } else { + if (funcName !== "") funcNames.push(funcName); + funcName = ""; + expectFunctionDeclaration = false; + } + } else if (jsLastToken.type === "IDENTIFIER" && jsLastToken.value === "function") { + expectFunctionDeclaration = true; + } + } + var jsSourceEnd = jsLastToken.end + 1; + + return { + type: "jsBody", + exposedFunctionNames: funcNames, + jsSource: tokens.source.substring(jsSourceStart, jsSourceEnd), + }; + }); + + _parser.addFeature("js", function (parser, runtime, tokens) { + if (!tokens.matchToken("js")) return; + var jsBody = parser.parseElement("jsBody", tokens); + + var jsSource = + jsBody.jsSource + + "\nreturn { " + + jsBody.exposedFunctionNames + .map(function (name) { + return name + ":" + name; + }) + .join(",") + + " } "; + var func = new Function(jsSource); + + return { + jsSource: jsSource, + function: func, + exposedFunctionNames: jsBody.exposedFunctionNames, + install: function () { + mergeObjects(globalScope, func()); + }, + }; + }); + + _parser.addCommand("js", function (parser, runtime, tokens) { + if (!tokens.matchToken("js")) return; + // Parse inputs + var inputs = []; + if (tokens.matchOpToken("(")) { + if (tokens.matchOpToken(")")) { + // empty input list + } else { + do { + var inp = tokens.requireTokenType("IDENTIFIER"); + inputs.push(inp.value); + } while (tokens.matchOpToken(",")); + tokens.requireOpToken(")"); + } + } + + var jsBody = parser.parseElement("jsBody", tokens); + tokens.matchToken("end"); + + var func = varargConstructor(Function, inputs.concat([jsBody.jsSource])); + + return { + jsSource: jsBody.jsSource, + function: func, + inputs: inputs, + op: function (context) { + var args = []; + inputs.forEach(function (input) { + args.push(runtime.resolveSymbol(input, context)); + }); + var result = func.apply(globalScope, args); + if (result && typeof result.then === "function") { + return Promise(function (resolve) { + result.then(function (actualResult) { + context.result = actualResult; + resolve(runtime.findNext(this, context)); + }); + }); + } else { + context.result = result; + return runtime.findNext(this, context); + } + }, + }; + }); + + _parser.addCommand("async", function (parser, runtime, tokens) { + if (!tokens.matchToken("async")) return; + if (tokens.matchToken("do")) { + var body = parser.requireElement("commandList", tokens); + tokens.requireToken("end"); + } else { + var body = parser.requireElement("command", tokens); + } + return { + body: body, + op: function (context) { + setTimeout(function () { + body.execute(context); + }); + return runtime.findNext(this, context); + }, + }; + }); + + _parser.addCommand("tell", function (parser, runtime, tokens) { + var startToken = tokens.currentToken(); + if (!tokens.matchToken("tell")) return; + var value = parser.requireElement("expression", tokens); + var body = parser.requireElement("commandList", tokens); + if (tokens.hasMore()) { + tokens.requireToken("end"); + } + var slot = "tell_" + startToken.start; + var tellCmd = { + value: value, + body: body, + args: [value], + resolveNext: function (context) { + var iterator = context.meta.iterators[slot]; + if (iterator.index < iterator.value.length) { + context.beingTold = iterator.value[iterator.index++]; + return body; + } else { + // restore original me + context.beingTold = iterator.originalBeingTold; + if (this.next) { + return this.next; + } else { + return runtime.findNext(this.parent, context); + } + } + }, + op: function (context, value) { + if (value == null) { + value = []; + } else if (!(Array.isArray(value) || value instanceof NodeList)) { + value = [value]; + } + context.meta.iterators[slot] = { + originalBeingTold: context.beingTold, + index: 0, + value: value, + }; + return this.resolveNext(context); + }, + }; + parser.setParent(body, tellCmd); + return tellCmd; + }); + + _parser.addCommand("wait", function (parser, runtime, tokens) { + if (!tokens.matchToken("wait")) return; + // wait on event + if (tokens.matchToken("for")) { + tokens.matchToken("a"); // optional "a" + var events = []; + do { + events.push({ + name: _parser.requireElement("dotOrColonPath", tokens, "Expected event name").evaluate(), + args: parseEventArgs(tokens), + }); + } while (tokens.matchToken("or")); + + if (tokens.matchToken("from")) { + var on = parser.requireElement("expression", tokens); + } + + // wait on event + var waitCmd = { + event: events, + on: on, + args: [on], + op: function (context, on) { + var target = on ? on : context.me; + if (!(target instanceof EventTarget)) + throw new Error("Not a valid event target: " + this.on.sourceFor()); + return new Promise(function (resolve) { + var resolved = false; + runtime.forEach(events, function (eventInfo) { + var listener = function (event) { + context.result = event; + runtime.forEach(eventInfo.args, function (arg) { + context[arg.value] = + event[arg.value] || (event.detail ? event.detail[arg.value] : null); + }); + if (!resolved) { + resolved = true; + resolve(runtime.findNext(waitCmd, context)); + } + }; + target.addEventListener(eventInfo.name, listener, { once: true }); + }); + }); + }, + }; + } else { + if (tokens.matchToken("a")) { + tokens.requireToken("tick"); + time = 0; + } else { + var time = _parser.requireElement("timeExpression", tokens); + } + + var waitCmd = { + type: "waitCmd", + time: time, + args: [time], + op: function (context, timeValue) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(runtime.findNext(waitCmd, context)); + }, timeValue); + }); + }, + execute: function (context) { + return runtime.unifiedExec(this, context); + }, + }; + } + return waitCmd; + }); + + // TODO - colon path needs to eventually become part of ruby-style symbols + _parser.addGrammarElement("dotOrColonPath", function (parser, runtime, tokens) { + var root = tokens.matchTokenType("IDENTIFIER"); + if (root) { + var path = [root.value]; + + var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":"); + if (separator) { + do { + path.push(tokens.requireTokenType("IDENTIFIER").value); + } while (tokens.matchOpToken(separator.value)); + } + + return { + type: "dotOrColonPath", + path: path, + evaluate: function () { + return path.join(separator ? separator.value : ""); + }, + }; + } + }); + + _parser.addGrammarElement("eventName", function (parser, runtime, tokens) { + var token; + if ((token = tokens.matchTokenType("STRING"))) { + return { + evaluate: function() { + return token.value; + }, + }; + } + + return parser.parseElement("dotOrColonPath", tokens); + }); + + _parser.addCommand("send", function (parser, runtime, tokens) { + if (!tokens.matchToken("send")) return; + var eventName = parser.requireElement("eventName", tokens); + + var details = parser.parseElement("namedArgumentList", tokens); + if (tokens.matchToken("to")) { + var to = parser.requireElement("expression", tokens); + } else { + var to = parser.requireElement("implicitMeTarget", tokens); + } + + var sendCmd = { + eventName: eventName, + details: details, + to: to, + args: [to, eventName, details], + op: function (context, to, eventName, details) { + runtime.forEach(to, function (target) { + runtime.triggerEvent(target, eventName, details ? details : {}); + }); + return runtime.findNext(sendCmd, context); + }, + }; + return sendCmd; + }); + + var parseReturnFunction = function (parser, runtime, tokens, returnAValue) { + if (returnAValue) { + var value = parser.requireElement("expression", tokens); + } + + var returnCmd = { + value: value, + args: [value], + op: function (context, value) { + var resolve = context.meta.resolve; + context.meta.returned = true; + if (resolve) { + if (value) { + resolve(value); + } else { + resolve(); + } + } else { + context.meta.returned = true; + context.meta.returnValue = value; + } + return runtime.HALT; + }, + }; + return returnCmd; + }; + + _parser.addCommand("return", function (parser, runtime, tokens) { + if (tokens.matchToken("return")) { + return parseReturnFunction(parser, runtime, tokens, true); + } + }); + + _parser.addCommand("exit", function (parser, runtime, tokens) { + if (tokens.matchToken("exit")) { + return parseReturnFunction(parser, runtime, tokens, false); + } + }); + + _parser.addCommand("halt", function (parser, runtime, tokens) { + if (tokens.matchToken("halt")) { + if (tokens.matchToken("the")) { + tokens.requireToken("event"); + // optional possessive + if (tokens.matchOpToken("'")) { + tokens.requireToken("s"); + } + var keepExecuting = true; + } + if (tokens.matchToken("bubbling")) { + var bubbling = true; + } else if (tokens.matchToken("default")) { + var haltDefault = true; + } + var exit = parseReturnFunction(parser, runtime, tokens, false); + + var haltCmd = { + keepExecuting: true, + bubbling: bubbling, + haltDefault: haltDefault, + exit: exit, + op: function (ctx) { + if (ctx.event) { + if (bubbling) { + ctx.event.stopPropagation(); + } else if (haltDefault) { + ctx.event.preventDefault(); + } else { + ctx.event.stopPropagation(); + ctx.event.preventDefault(); + } + if (keepExecuting) { + return runtime.findNext(this, ctx); + } else { + return exit; + } + } + }, + }; + return haltCmd; + } + }); + + _parser.addCommand("log", function (parser, runtime, tokens) { + if (!tokens.matchToken("log")) return; + var exprs = [parser.parseElement("expression", tokens)]; + while (tokens.matchOpToken(",")) { + exprs.push(parser.requireElement("expression", tokens)); + } + if (tokens.matchToken("with")) { + var withExpr = parser.requireElement("expression", tokens); + } + var logCmd = { + exprs: exprs, + withExpr: withExpr, + args: [withExpr, exprs], + op: function (ctx, withExpr, values) { + if (withExpr) { + withExpr.apply(null, values); + } else { + console.log.apply(null, values); + } + return runtime.findNext(this, ctx); + }, + }; + return logCmd; + }); + + _parser.addCommand("throw", function (parser, runtime, tokens) { + if (!tokens.matchToken("throw")) return; + var expr = parser.requireElement("expression", tokens); + var throwCmd = { + expr: expr, + args: [expr], + op: function (ctx, expr) { + runtime.registerHyperTrace(ctx, expr); + var reject = ctx.meta && ctx.meta.reject; + if (reject) { + reject(expr); + return runtime.HALT; + } else { + throw expr; + } + }, + }; + return throwCmd; + }); + + var parseCallOrGet = function (parser, runtime, tokens) { + var expr = parser.requireElement("expression", tokens); + var callCmd = { + expr: expr, + args: [expr], + op: function (context, result) { + context.result = result; + return runtime.findNext(callCmd, context); + }, + }; + return callCmd; + }; + _parser.addCommand("call", function (parser, runtime, tokens) { + if (!tokens.matchToken("call")) return; + var call = parseCallOrGet(parser, runtime, tokens); + if (call.expr && call.expr.type !== "functionCall") { + parser.raiseParseError(tokens, "Must be a function invocation"); + } + return call; + }); + _parser.addCommand("get", function (parser, runtime, tokens) { + if (tokens.matchToken("get")) { + return parseCallOrGet(parser, runtime, tokens); + } + }); + + _parser.addCommand("make", function (parser, runtime, tokens) { + if (!tokens.matchToken("make")) return; + tokens.matchToken("a") || tokens.matchToken("an"); + + var expr = parser.requireElement("expression", tokens); + + var args = []; + if (expr.type !== "queryRef" && tokens.matchToken("from")) { + do { + args.push(parser.requireElement("expression", tokens)); + } while (tokens.matchOpToken(",")); + } + + if (tokens.matchToken("called")) { + var name = tokens.requireTokenType("IDENTIFIER").value; + } + + if (expr.type === "queryRef") + return { + op: function (ctx) { + var match, + tagname = "div", + id, + classes = []; + var re = /(?:(^|#|\.)([^#\. ]+))/g; + while ((match = re.exec(expr.css))) { + if (match[1] === "") tagname = match[2].trim(); + else if (match[1] === "#") id = match[2].trim(); + else classes.push(match[2].trim()); + } + + var result = document.createElement(tagname); + if (id !== undefined) result.id = id; + for (var i = 0; i < classes.length; i++) { + var cls = classes[i]; + result.classList.add(cls) + } + + ctx.result = result; + if (name) ctx[name] = result; + + return runtime.findNext(this, ctx); + }, + }; + else + return { + args: [expr, args], + op: function (ctx, expr, args) { + ctx.result = varargConstructor(expr, args); + if (name) ctx[name] = ctx.result; + + return runtime.findNext(this, ctx); + }, + }; + }); + + _parser.addGrammarElement("pseudoCommand", function (parser, runtime, tokens) { + var expr = parser.requireElement("primaryExpression", tokens); + if (expr.type !== "functionCall" && expr.root.type !== "symbol") { + parser.raiseParseError("Implicit function calls must start with a simple function", tokens); + } + // optional "on", "with", or "to" + if (!tokens.matchAnyToken("to", "on", "with") && parser.commandBoundary(tokens.currentToken())) { + var target = parser.requireElement("implicitMeTarget", tokens); + } else { + var target = parser.requireElement("expression", tokens); + } + var functionName = expr.root.name; + var functionArgs = expr.argExressions; + + /** @type {GrammarElement} */ + var pseudoCommand = { + type: "pseudoCommand", + expr: expr, + args: [target, functionArgs], + op: function (context, target, args) { + var func = target[functionName]; + if (func.hyperfunc) { + args.push(context); + } + var result = func.apply(target, args); + context.result = result; + return runtime.findNext(pseudoCommand, context); + }, + execute: function (context) { + return runtime.unifiedExec(this, context); + }, + }; + + return pseudoCommand; + }); + + /** + * @param {ParserObject} parser + * @param {RuntimeObject} runtime + * @param {TokensObject} tokens + * @param {*} target + * @param {*} value + * @returns + */ + var makeSetter = function (parser, runtime, tokens, target, value) { + var symbolWrite = target.type === "symbol"; + var attributeWrite = target.type === "attributeRef"; + if (!attributeWrite && !symbolWrite && target.root == null) { + parser.raiseParseError(tokens, "Can only put directly into symbols, not references"); + } + + var root = null; + var prop = null; + if (symbolWrite) { + // root is null + } else if (attributeWrite) { + root = parser.requireElement("implicitMeTarget", tokens); + var attribute = target; + } else { + prop = target.prop ? target.prop.value : null; + var attribute = target.attribute; + root = target.root; + } + + /** @type {GrammarElement} */ + var setCmd = { + target: target, + symbolWrite: symbolWrite, + value: value, + args: [root, value], + op: function (context, root, valueToSet) { + if (symbolWrite) { + runtime.setSymbol(target.name, context, target.symbolType, valueToSet); + } else { + runtime.forEach(root, function (elt) { + if (attribute) { + if (valueToSet == null) { + elt.removeAttribute(attribute.name); + } else { + elt.setAttribute(attribute.name, valueToSet); + } + } else { + elt[prop] = valueToSet; + } + }); + } + return runtime.findNext(this, context); + }, + }; + return setCmd; + }; + + _parser.addCommand("default", function (parser, runtime, tokens) { + if (!tokens.matchToken("default")) return; + var target = parser.requireElement("assignableExpression", tokens); + tokens.requireToken("to"); + + var value = parser.requireElement("expression", tokens); + + /** @type {GrammarElement} */ + var setter = makeSetter(parser, runtime, tokens, target, value); + var defaultCmd = { + target: target, + value: value, + setter: setter, + args: [target], + op: function (context, target) { + if (target) { + return runtime.findNext(this, context); + } else { + return setter; + } + }, + }; + setter.parent = defaultCmd; + return defaultCmd; + }); + + _parser.addCommand("set", function (parser, runtime, tokens) { + if (!tokens.matchToken("set")) return; + if (tokens.currentToken().type === "L_BRACE") { + var obj = parser.requireElement("objectLiteral", tokens); + tokens.requireToken("on"); + var target = parser.requireElement("expression", tokens); + + return { + objectLiteral: obj, + target: target, + args: [obj, target], + op: function (ctx, obj, target) { + mergeObjects(target, obj); + return runtime.findNext(this, ctx); + }, + }; + } + + try { + tokens.pushFollow("to"); + var target = parser.requireElement("assignableExpression", tokens); + } finally { + tokens.popFollow(); + } + tokens.requireToken("to"); + var value = parser.requireElement("expression", tokens); + return makeSetter(parser, runtime, tokens, target, value); + }); + + _parser.addCommand("if", function (parser, runtime, tokens) { + if (!tokens.matchToken("if")) return; + var expr = parser.requireElement("expression", tokens); + tokens.matchToken("then"); // optional 'then' + var trueBranch = parser.parseElement("commandList", tokens); + if (tokens.matchToken("else")) { + var falseBranch = parser.parseElement("commandList", tokens); + } + if (tokens.hasMore()) { + tokens.requireToken("end"); + } + + /** @type {GrammarElement} */ + var ifCmd = { + expr: expr, + trueBranch: trueBranch, + falseBranch: falseBranch, + args: [expr], + op: function (context, exprValue) { + if (exprValue) { + return trueBranch; + } else if (falseBranch) { + return falseBranch; + } else { + return runtime.findNext(this, context); + } + }, + }; + parser.setParent(trueBranch, ifCmd); + parser.setParent(falseBranch, ifCmd); + return ifCmd; + }); + + var parseRepeatExpression = function (parser, tokens, runtime, startedWithForToken) { + var innerStartToken = tokens.currentToken(); + if (tokens.matchToken("for") || startedWithForToken) { + var identifierToken = tokens.requireTokenType("IDENTIFIER"); + var identifier = identifierToken.value; + tokens.requireToken("in"); + var expression = parser.requireElement("expression", tokens); + } else if (tokens.matchToken("in")) { + var identifier = "it"; + var expression = parser.requireElement("expression", tokens); + } else if (tokens.matchToken("while")) { + var whileExpr = parser.requireElement("expression", tokens); + } else if (tokens.matchToken("until")) { + var isUntil = true; + if (tokens.matchToken("event")) { + var evt = _parser.requireElement("dotOrColonPath", tokens, "Expected event name"); + if (tokens.matchToken("from")) { + var on = parser.requireElement("expression", tokens); + } + } else { + var whileExpr = parser.requireElement("expression", tokens); + } + } else if (tokens.matchTokenType("NUMBER")) { + var times = parseFloat(innerStartToken.value); + tokens.requireToken("times"); + } else { + tokens.matchToken("forever"); // consume optional forever + var forever = true; + } + + if (tokens.matchToken("index")) { + var identifierToken = tokens.requireTokenType("IDENTIFIER"); + var indexIdentifier = identifierToken.value; + } + + var loop = parser.parseElement("commandList", tokens); + if (loop && evt) { + // if this is an event based loop, wait a tick at the end of the loop so that + // events have a chance to trigger in the loop condition o_O))) + var last = loop; + while (last.next) { + last = last.next; + } + var waitATick = { + type: "waitATick", + op: function () { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(runtime.findNext(waitATick)); + }, 0); + }); + }, + }; + last.next = waitATick; + } + if (tokens.hasMore()) { + tokens.requireToken("end"); + } + + if (identifier == null) { + identifier = "_implicit_repeat_" + innerStartToken.start; + var slot = identifier; + } else { + var slot = identifier + "_" + innerStartToken.start; + } + + var repeatCmd = { + identifier: identifier, + indexIdentifier: indexIdentifier, + slot: slot, + expression: expression, + forever: forever, + times: times, + until: isUntil, + event: evt, + on: on, + whileExpr: whileExpr, + resolveNext: function () { + return this; + }, + loop: loop, + args: [whileExpr], + op: function (context, whileValue) { + var iterator = context.meta.iterators[slot]; + var keepLooping = false; + if (this.forever) { + keepLooping = true; + } else if (this.until) { + if (evt) { + keepLooping = context.meta.iterators[slot].eventFired === false; + } else { + keepLooping = whileValue !== true; + } + } else if (whileValue) { + keepLooping = true; + } else if (times) { + keepLooping = iterator.index < this.times; + } else { + keepLooping = iterator.value !== null && iterator.index < iterator.value.length; + } + + if (keepLooping) { + if (iterator.value) { + context[identifier] = iterator.value[iterator.index]; + context.result = iterator.value[iterator.index]; + } else { + context.result = iterator.index; + } + if (indexIdentifier) { + context[indexIdentifier] = iterator.index; + } + iterator.index++; + return loop; + } else { + context.meta.iterators[slot] = null; + return runtime.findNext(this.parent, context); + } + }, + }; + parser.setParent(loop, repeatCmd); + var repeatInit = { + name: "repeatInit", + args: [expression, evt, on], + op: function (context, value, event, on) { + context.meta.iterators[slot] = { + index: 0, + value: value, + eventFired: false, + }; + if (evt) { + var target = on || context.me; + target.addEventListener( + event, + function (e) { + context.meta.iterators[slot].eventFired = true; + }, + { once: true } + ); + } + return repeatCmd; // continue to loop + }, + execute: function (context) { + return runtime.unifiedExec(this, context); + }, + }; + parser.setParent(repeatCmd, repeatInit); + return repeatInit; + }; + + _parser.addCommand("repeat", function (parser, runtime, tokens) { + if (tokens.matchToken("repeat")) { + return parseRepeatExpression(parser, tokens, runtime, false); + } + }); + + _parser.addCommand("for", function (parser, runtime, tokens) { + if (tokens.matchToken("for")) { + return parseRepeatExpression(parser, tokens, runtime, true); + } + }); + + _parser.addGrammarElement("stringLike", function (parser, runtime, tokens) { + return _parser.parseAnyOf(["string", "nakedString"], tokens); + }); + + _parser.addCommand("append", function (parser, runtime, tokens) { + if (!tokens.matchToken("append")) return; + var target = null; + var prop = null; + + var value = parser.requireElement("expression", tokens); + + if (tokens.matchToken("to")) { + target = parser.requireElement("expression", tokens); + } + + if (target == null) { + prop = "result"; + } else if (target.type === "symbol") { + prop = target.name; + } else if (target.type === "propertyAccess") { + prop = target.prop.value; + } else { + throw "Unable to append to " + target.type; + } + + return { + value: value, + target: target, + args: [value], + op: function (context, value) { + if (Array.isArray(context[prop])) { + context[prop].push(value); + } else if (context[prop] instanceof Element) { + if (typeof value == "string") { + context[prop].innerHTML += value; + } else { + throw "Don't know how to append non-strings to an HTML Element yet."; + } + } else { + context[prop] += value; + } + + return runtime.findNext(this, context); + }, + execute: function (context) { + return runtime.unifiedExec(this, context, value, target); + }, + }; + }); + + _parser.addCommand("increment", function (parser, runtime, tokens) { + if (!tokens.matchToken("increment")) return; + var amount; + + // This is optional. Defaults to "result" + var target = parser.parseElement("assignableExpression", tokens); + + // This is optional. Defaults to 1. + if (tokens.matchToken("by")) { + amount = parser.requireElement("expression", tokens); + } + + return { + target: target, + args: [target, amount], + op: function (context, targetValue, amount) { + targetValue = targetValue ? parseFloat(targetValue) : 0; + amount = amount ? parseFloat(amount) : 1; + var newValue = targetValue + amount; + var setter = makeSetter(parser, runtime, tokens, target, newValue); + context.result = newValue; + setter.parent = this; + return setter; + }, + execute: function (context) { + return runtime.unifiedExec(this, context, target, amount); + }, + }; + }); + + _parser.addCommand("decrement", function (parser, runtime, tokens) { + if (!tokens.matchToken("decrement")) return; + var amount; + + // This is optional. Defaults to "result" + var target = parser.parseElement("assignableExpression", tokens); + + // This is optional. Defaults to 1. + if (tokens.matchToken("by")) { + amount = parser.requireElement("expression", tokens); + } + + return { + target: target, + args: [target, amount], + op: function (context, targetValue, amount) { + targetValue = targetValue ? parseFloat(targetValue) : 0; + amount = amount ? parseFloat(amount) : 1; + var newValue = targetValue - amount; + var setter = makeSetter(parser, runtime, tokens, target, newValue); + context.result = newValue; + setter.parent = this; + return setter; + }, + execute: function (context) { + return runtime.unifiedExec(this, context, target, amount); + }, + }; + }); + + _parser.addCommand("fetch", function (parser, runtime, tokens) { + if (!tokens.matchToken("fetch")) return; + var url = parser.requireElement("stringLike", tokens); + var args = parser.parseElement("objectLiteral", tokens); + + var type = "text"; + var conversion; + if (tokens.matchToken("as")) { + if (tokens.matchToken("json")) { + type = "json"; + } else if (tokens.matchToken("response")) { + type = "response"; + } else if (tokens.matchToken("html")) { + type = "html"; + } else if (tokens.matchToken("text")) { + // default, ignore + } else { + conversion = parser.requireElement("dotOrColonPath", tokens).evaluate(); + } + } + + /** @type {GrammarElement} */ + var fetchCmd = { + url: url, + argExpressions: args, + args: [url, args], + op: function (context, url, args) { + return fetch(url, args) + .then(function (resp) { + if (type === "response") { + context.result = resp; + return runtime.findNext(fetchCmd, context); + } + if (type === "json") { + return resp.json().then(function (result) { + context.result = result; + return runtime.findNext(fetchCmd, context); + }); + } + return resp.text().then(function (result) { + if (conversion) result = runtime.convertValue(result, conversion); + + if (type === "html") result = runtime.convertValue(result, "Fragment"); + + context.result = result; + return runtime.findNext(fetchCmd, context); + }); + }) + .catch(function (reason) { + runtime.triggerEvent(context.me, "fetch:error", { + reason: reason, + }); + throw reason; + }); + }, + }; + return fetchCmd; + }); + } + + //==================================================================== + // Initialization + //==================================================================== + function ready(fn) { + if (document.readyState !== "loading") { + setTimeout(fn); + } else { + document.addEventListener("DOMContentLoaded", fn); + } + } + + function getMetaConfig() { + var element = document.querySelector('meta[name="htmx-config"]'); + if (element) { + return parseJSON(element.content); + } else { + return null; + } + } + + function mergeMetaConfig() { + var metaConfig = getMetaConfig(); + if (metaConfig) { + _hyperscript.config = mergeObjects(_hyperscript.config, metaConfig); + } + } + + if ("document" in globalScope) { + Promise.all( + Array.from(document.querySelectorAll("script[type='text/hyperscript'][src]")).map(function (script) { + return fetch(script.src) + .then(function (res) { + return res.text(); + }) + .then(function (code) { + return _runtime.evaluate(code); + }); + }) + ).then(function () { + ready(function () { + mergeMetaConfig(); + _runtime.processNode(document.documentElement); + document.addEventListener("htmx:load", function (evt) { + _runtime.processNode(evt.detail.elt); + }); + }); + }); + } + + //==================================================================== + // API + //==================================================================== + return mergeObjects( + function (str, ctx) { + return _runtime.evaluate(str, ctx); //OK + }, + { + internals: { + lexer: _lexer, + parser: _parser, + runtime: _runtime, + }, + addFeature: function (keyword, definition) { + _parser.addFeature(keyword, definition); + }, + addCommand: function (keyword, definition) { + _parser.addCommand(keyword, definition); + }, + addLeafExpression: function (name, definition) { + _parser.addLeafExpression(name, definition); + }, + addIndirectExpression: function (name, definition) { + _parser.addIndirectExpression(name, definition); + }, + evaluate: function (str, ctx) { + //OK + return _runtime.evaluate(str, ctx); //OK + }, + parse: function (str) { + //OK + return _runtime.parse(str); //OK + }, + processNode: function (elt) { + _runtime.processNode(elt); + }, + config: { + attributes: "_, script, data-script", + defaultTransition: "all 500ms ease-in", + disableSelector: "[disable-scripting], [data-disable-scripting]", + conversions: CONVERSIONS, + }, + } + ); +}); + +///========================================================================= +/// This module provides the core web functionality for hyperscript +///========================================================================= +(function () { + function mergeObjects(obj1, obj2) { + for (var key in obj2) { + if (obj2.hasOwnProperty(key)) { + obj1[key] = obj2[key]; + } + } + return obj1; + } + + _hyperscript.addCommand("settle", function (parser, runtime, tokens) { + if (tokens.matchToken("settle")) { + if (!parser.commandBoundary(tokens.currentToken())) { + var on = parser.requireElement("expression", tokens); + } else { + var on = parser.requireElement("implicitMeTarget", tokens); + } + + var settleCommand = { + type: "settleCmd", + args: [on], + op: function (context, on) { + var resolve = null; + var resolved = false; + var transitionStarted = false; + + var promise = new Promise(function (r) { + resolve = r; + }); + + // listen for a transition begin + on.addEventListener( + "transitionstart", + function () { + transitionStarted = true; + }, + { once: true } + ); + + // if no transition begins in 500ms, cancel + setTimeout(function () { + if (!transitionStarted && !resolved) { + resolve(runtime.findNext(settleCommand, context)); + } + }, 500); + + // continue on a transition emd + on.addEventListener( + "transitionend", + function () { + if (!resolved) { + resolve(runtime.findNext(settleCommand, context)); + } + }, + { once: true } + ); + return promise; + }, + execute: function (context) { + return runtime.unifiedExec(this, context); + }, + }; + return settleCommand; + } + }); + + _hyperscript.addCommand("add", function (parser, runtime, tokens) { + if (tokens.matchToken("add")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + var cssDeclaration = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + cssDeclaration = parser.parseElement("objectLiteral", tokens); + if (cssDeclaration == null) { + parser.raiseParseError(tokens, "Expected either a class reference or attribute expression"); + } + } + } else { + var classRefs = [classRef]; + while ((classRef = parser.parseElement("classRef", tokens))) { + classRefs.push(classRef); + } + } + + if (tokens.matchToken("to")) { + var to = parser.requireElement("expression", tokens); + } else { + var to = parser.parseElement("implicitMeTarget", tokens); + } + + if (classRefs) { + var addCmd = { + classRefs: classRefs, + to: to, + args: [to], + op: function (context, to) { + runtime.forEach(classRefs, function (classRef) { + runtime.forEach(to, function (target) { + if (target instanceof Element) target.classList.add(classRef.className()); + }); + }); + return runtime.findNext(this, context); + }, + }; + } else if (attributeRef) { + var addCmd = { + type: "addCmd", + attributeRef: attributeRef, + to: to, + args: [to], + op: function (context, to, attrRef) { + runtime.forEach(to, function (target) { + target.setAttribute(attributeRef.name, attributeRef.value); + }); + return runtime.findNext(addCmd, context); + }, + execute: function (ctx) { + return runtime.unifiedExec(this, ctx); + }, + }; + } else { + var addCmd = { + type: "addCmd", + cssDeclaration: cssDeclaration, + to: to, + args: [to, cssDeclaration], + op: function (context, to, css) { + runtime.forEach(to, function (target) { + for (var key in css) { + if (css.hasOwnProperty(key)) { + target.style.setProperty(key, css[key]); + } + } + }); + return runtime.findNext(addCmd, context); + }, + execute: function (ctx) { + return runtime.unifiedExec(this, ctx); + }, + }; + } + return addCmd; + } + }); + + _hyperscript.addCommand("remove", function (parser, runtime, tokens) { + if (tokens.matchToken("remove")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + var elementExpr = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + elementExpr = parser.parseElement("expression", tokens); + if (elementExpr == null) { + parser.raiseParseError( + tokens, + "Expected either a class reference, attribute expression or value expression" + ); + } + } + } else { + var classRefs = [classRef]; + while ((classRef = parser.parseElement("classRef", tokens))) { + classRefs.push(classRef); + } + } + + if (tokens.matchToken("from")) { + var from = parser.requireElement("expression", tokens); + } else { + var from = parser.requireElement("implicitMeTarget", tokens); + } + + if (elementExpr) { + var removeCmd = { + elementExpr: elementExpr, + from: from, + args: [elementExpr], + op: function (context, element) { + runtime.forEach(element, function (target) { + if (target.parentElement) { + target.parentElement.removeChild(target); + } + }); + return runtime.findNext(this, context); + }, + }; + } else { + var removeCmd = { + classRefs: classRefs, + attributeRef: attributeRef, + elementExpr: elementExpr, + from: from, + args: [from], + op: function (context, from) { + if (this.classRefs) { + runtime.forEach(classRefs, function (classRef) { + runtime.forEach(from, function (target) { + target.classList.remove(classRef.className()); + }); + }); + } else { + runtime.forEach(from, function (target) { + target.removeAttribute(attributeRef.name); + }); + } + return runtime.findNext(this, context); + }, + }; + } + return removeCmd; + } + }); + + _hyperscript.addCommand("toggle", function (parser, runtime, tokens) { + if (tokens.matchToken("toggle")) { + if (tokens.matchToken("between")) { + var between = true; + var classRef = parser.parseElement("classRef", tokens); + tokens.requireToken("and"); + var classRef2 = parser.requireElement("classRef", tokens); + } else { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + parser.raiseParseError(tokens, "Expected either a class reference or attribute expression"); + } + } else { + var classRefs = [classRef]; + while ((classRef = parser.parseElement("classRef", tokens))) { + classRefs.push(classRef); + } + } + } + + if (tokens.matchToken("on")) { + var on = parser.requireElement("expression", tokens); + } else { + var on = parser.requireElement("implicitMeTarget", tokens); + } + + if (tokens.matchToken("for")) { + var time = parser.requireElement("timeExpression", tokens); + } else if (tokens.matchToken("until")) { + var evt = parser.requireElement("dotOrColonPath", tokens, "Expected event name"); + if (tokens.matchToken("from")) { + var from = parser.requireElement("expression", tokens); + } + } + + var toggleCmd = { + classRef: classRef, + classRef2: classRef2, + classRefs: classRefs, + attributeRef: attributeRef, + on: on, + time: time, + evt: evt, + from: from, + toggle: function (on) { + if (between) { + runtime.forEach(on, function (target) { + if (target.classList.contains(classRef.className())) { + target.classList.remove(classRef.className()); + target.classList.add(classRef2.className()); + } else { + target.classList.add(classRef.className()); + target.classList.remove(classRef2.className()); + } + }); + } else if (this.classRefs) { + runtime.forEach(this.classRefs, function (classRef) { + runtime.forEach(on, function (target) { + target.classList.toggle(classRef.className()); + }); + }); + } else { + runtime.forEach(on, function (target) { + if (target.hasAttribute(attributeRef.name)) { + target.removeAttribute(attributeRef.name); + } else { + target.setAttribute(attributeRef.name, attributeRef.value); + } + }); + } + }, + args: [on, time, evt, from], + op: function (context, on, time, evt, from) { + if (time) { + return new Promise(function (resolve) { + toggleCmd.toggle(on); + setTimeout(function () { + toggleCmd.toggle(on); + resolve(runtime.findNext(toggleCmd, context)); + }, time); + }); + } else if (evt) { + return new Promise(function (resolve) { + var target = from || context.me; + target.addEventListener( + evt, + function () { + toggleCmd.toggle(on); + resolve(runtime.findNext(toggleCmd, context)); + }, + { once: true } + ); + toggleCmd.toggle(on); + }); + } else { + this.toggle(on); + return runtime.findNext(toggleCmd, context); + } + }, + }; + return toggleCmd; + } + }); + + var HIDE_SHOW_STRATEGIES = { + display: function (op, element, arg) { + if (arg) { + element.style.display = arg; + } else if (op === "hide") { + element.style.display = "none"; + } else { + element.style.display = "block"; + } + }, + visibility: function (op, element, arg) { + if (arg) { + element.style.visibility = arg; + } else if (op === "hide") { + element.style.visibility = "hidden"; + } else { + element.style.visibility = "visible"; + } + }, + opacity: function (op, element, arg) { + if (arg) { + element.style.opacity = arg; + } else if (op === "hide") { + element.style.opacity = "0"; + } else { + element.style.opacity = "1"; + } + }, + }; + + var parseShowHideTarget = function (parser, runtime, tokens) { + var target; + var currentTokenValue = tokens.currentToken(); + if (currentTokenValue.value === "with" || parser.commandBoundary(currentTokenValue)) { + target = parser.parseElement("implicitMeTarget", tokens); + } else { + target = parser.parseElement("expression", tokens); + } + return target; + }; + + var resolveStrategy = function (parser, tokens, name) { + var configDefault = _hyperscript.config.defaultHideShowStrategy; + var strategies = HIDE_SHOW_STRATEGIES; + if (_hyperscript.config.hideShowStrategies) { + strategies = mergeObjects(strategies, _hyperscript.config.hideShowStrategies); // merge in user provided strategies + } + name = name || configDefault || "display"; + var value = strategies[name]; + if (value == null) { + parser.raiseParseError(tokens, "Unknown show/hide strategy : " + name); + } + return value; + }; + + _hyperscript.addCommand("hide", function (parser, runtime, tokens) { + if (tokens.matchToken("hide")) { + var target = parseShowHideTarget(parser, runtime, tokens); + + var name = null; + if (tokens.matchToken("with")) { + name = tokens.requireTokenType("IDENTIFIER").value; + } + var hideShowStrategy = resolveStrategy(parser, tokens, name); + + return { + target: target, + args: [target], + op: function (ctx, target) { + runtime.forEach(target, function (elt) { + hideShowStrategy("hide", elt); + }); + return runtime.findNext(this, ctx); + }, + }; + } + }); + + _hyperscript.addCommand("show", function (parser, runtime, tokens) { + if (tokens.matchToken("show")) { + var target = parseShowHideTarget(parser, runtime, tokens); + + var name = null; + if (tokens.matchToken("with")) { + name = tokens.requireTokenType("IDENTIFIER").value; + } + var arg = null; + if (tokens.matchOpToken(":")) { + var tokenArr = tokens.consumeUntilWhitespace(); + tokens.matchTokenType("WHITESPACE"); + arg = tokenArr + .map(function (t) { + return t.value; + }) + .join(""); + } + var hideShowStrategy = resolveStrategy(parser, tokens, name); + + return { + target: target, + args: [target], + op: function (ctx, target) { + runtime.forEach(target, function (elt) { + hideShowStrategy("show", elt, arg); + }); + return runtime.findNext(this, ctx); + }, + }; + } + }); + + _hyperscript.addCommand("trigger", function (parser, runtime, tokens) { + if (tokens.matchToken("trigger")) { + var eventName = parser.requireElement("eventName", tokens); + var details = parser.parseElement("namedArgumentList", tokens); + + var triggerCmd = { + eventName: eventName, + details: details, + args: [eventName, details], + op: function (context, eventNameStr, details) { + runtime.triggerEvent(context.me, eventNameStr, details ? details : {}); + return runtime.findNext(triggerCmd, context); + }, + }; + return triggerCmd; + } + }); + + _hyperscript.addCommand("take", function (parser, runtime, tokens) { + if (tokens.matchToken("take")) { + var classRef = parser.parseElement("classRef", tokens); + + if (tokens.matchToken("from")) { + var from = parser.requireElement("expression", tokens); + } else { + var from = classRef; + } + + if (tokens.matchToken("for")) { + var forElt = parser.requireElement("expression", tokens); + } else { + var forElt = parser.requireElement("implicitMeTarget", tokens); + } + + var takeCmd = { + classRef: classRef, + from: from, + forElt: forElt, + args: [from, forElt], + op: function (context, from, forElt) { + var clazz = this.classRef.css.substr(1); + runtime.forEach(from, function (target) { + target.classList.remove(clazz); + }); + runtime.forEach(forElt, function (target) { + target.classList.add(clazz); + }); + return runtime.findNext(this, context); + }, + }; + return takeCmd; + } + }); + + function putInto(context, prop, valueToPut) { + if (prop) { + var value = context[prop]; + } else { + var value = context; + } + if (value instanceof Element || value instanceof HTMLDocument) { + while (value.firstChild) value.removeChild(value.firstChild); + value.append(_hyperscript.internals.runtime.convertValue(valueToPut, "Fragment")); + } else { + if (prop) { + context[prop] = valueToPut; + } else { + throw "Don't know how to put a value into " + typeof context; + } + } + } + + _hyperscript.addCommand("put", function (parser, runtime, tokens) { + if (tokens.matchToken("put")) { + var value = parser.requireElement("expression", tokens); + + var operationToken = tokens.matchAnyToken("into", "before", "after"); + + if (operationToken == null && tokens.matchToken("at")) { + operationToken = tokens.matchAnyToken("start", "end"); + tokens.requireToken("of"); + } + + if (operationToken == null) { + parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'"); + } + var target = parser.requireElement("expression", tokens); + + var operation = operationToken.value; + + var symbolWrite = false; + var root = null; + var prop = null; + if (target.type === "propertyAccess" && operation === "into") { + prop = target.prop.value; + root = target.root; + } else if (target.type === "symbol" && operation === "into") { + symbolWrite = true; + prop = target.name; + } else if (target.type === "attributeRef" && operation === "into") { + var attributeWrite = true; + prop = target.name; + root = parser.requireElement("implicitMeTarget", tokens); + } else if (target.type === "attributeRefAccess" && operation === "into") { + var attributeWrite = true; + prop = target.attribute.name; + root = target.root; + } else { + root = target; + } + + var putCmd = { + target: target, + operation: operation, + symbolWrite: symbolWrite, + value: value, + args: [root, value], + op: function (context, root, valueToPut) { + if (symbolWrite) { + putInto(context, prop, valueToPut); + } else { + if (operation === "into") { + if (attributeWrite) { + runtime.forEach(root, function (elt) { + elt.setAttribute(prop, valueToPut); + }); + } else { + runtime.forEach(root, function (elt) { + putInto(elt, prop, valueToPut); + }); + } + } else { + var op = + operation === "before" + ? Element.prototype.before + : operation === "after" + ? Element.prototype.after + : operation === "start" + ? Element.prototype.prepend + : operation === "end" + ? Element.prototype.append + : "unreachable"; + + runtime.forEach(root, function (elt) { + op.call( + elt, + valueToPut instanceof Node + ? valueToPut + : runtime.convertValue(valueToPut, "Fragment") + ); + }); + } + } + return runtime.findNext(this, context); + }, + }; + return putCmd; + } + }); + + function parsePseudopossessiveTarget(parser, runtime, tokens) { + if ( + tokens.matchToken("the") || + tokens.matchToken("element") || + tokens.matchToken("elements") || + tokens.currentToken().type === "CLASS_REF" || + tokens.currentToken().type === "ID_REF" || + (tokens.currentToken().op && tokens.currentToken().value === "<") + ) { + parser.possessivesDisabled = true; + try { + var targets = parser.parseElement("expression", tokens); + } finally { + delete parser.possessivesDisabled; + } + // optional possessive + if (tokens.matchOpToken("'")) { + tokens.requireToken("s"); + } + } else if (tokens.currentToken().type === "IDENTIFIER" && tokens.currentToken().value === "its") { + var identifier = tokens.matchToken("its"); + var targets = { + type: "pseudopossessiveIts", + token: identifier, + name: identifier.value, + evaluate: function (context) { + return runtime.resolveSymbol("it", context); + }, + }; + } else { + tokens.matchToken("my") || tokens.matchToken("me"); // consume optional 'my' + var targets = parser.parseElement("implicitMeTarget", tokens); + } + return targets; + } + + _hyperscript.addCommand("transition", function (parser, runtime, tokens) { + if (tokens.matchToken("transition")) { + var targets = parsePseudopossessiveTarget(parser, runtime, tokens); + + var properties = []; + var from = []; + var to = []; + var currentToken = tokens.currentToken(); + while ( + !parser.commandBoundary(currentToken) && + currentToken.value !== "over" && + currentToken.value !== "using" + ) { + properties.push(parser.requireElement("stringLike", tokens)); + + if (tokens.matchToken("from")) { + from.push(parser.requireElement("stringLike", tokens)); + } else { + from.push(null); + } + tokens.requireToken("to"); + to.push(parser.requireElement("stringLike", tokens)); + currentToken = tokens.currentToken(); + } + if (tokens.matchToken("over")) { + var over = parser.requireElement("timeExpression", tokens); + } else if (tokens.matchToken("using")) { + var using = parser.requireElement("expression", tokens); + } + + var transition = { + to: to, + args: [targets, properties, from, to, using, over], + op: function (context, targets, properties, from, to, using, over) { + var promises = []; + runtime.forEach(targets, function (target) { + var promise = new Promise(function (resolve, reject) { + var initialTransition = target.style.transition; + if (over) { + target.style.transition = "all " + over + "ms ease-in"; + } else if (using) { + target.style.transition = using; + } else { + target.style.transition = _hyperscript.config.defaultTransition; + } + var internalData = runtime.getInternalData(target); + var computedStyles = getComputedStyle(target); + + var initialStyles = {}; + for (var i = 0; i < computedStyles.length; i++) { + var name = computedStyles[i]; + var initialValue = computedStyles[name]; + initialStyles[name] = initialValue; + } + + // store intitial values + if (!internalData.initalStyles) { + internalData.initalStyles = initialStyles; + } + + for (var i = 0; i < properties.length; i++) { + var property = properties[i]; + var fromVal = from[i]; + if (fromVal == "computed" || fromVal == null) { + target.style[property] = initialStyles[property]; + } else { + target.style[property] = fromVal; + } + } + // console.log("transition started", transition); + setTimeout(function () { + var autoProps = []; + for (var i = 0; i < properties.length; i++) { + var property = properties[i]; + var toVal = to[i]; + if (toVal == "initial") { + var propertyValue = internalData.initalStyles[property]; + target.style[property] = propertyValue; + } else { + target.style[property] = toVal; + } + // console.log("set", property, "to", target.style[property], "on", target, "value passed in : ", toVal); + } + target.addEventListener( + "transitionend", + function () { + // console.log("transition ended", transition); + target.style.transition = initialTransition; + resolve(); + }, + { once: true } + ); + }, 5); + }); + promises.push(promise); + }); + return Promise.all(promises).then(function () { + return runtime.findNext(transition, context); + }); + }, + }; + return transition; + } + }); + + _hyperscript.addCommand("measure", function (parser, runtime, tokens) { + if (!tokens.matchToken("measure")) return; + + var target = parsePseudopossessiveTarget(parser, runtime, tokens); + + var propsToMeasure = []; + if (!parser.commandBoundary(tokens.currentToken())) + do { + propsToMeasure.push(tokens.matchTokenType("IDENTIFIER").value); + } while (tokens.matchOpToken(",")); + + return { + properties: propsToMeasure, + args: [target], + op: function (ctx, target) { + if (0 in target) target = target[0]; // not measuring multiple elts + var rect = target.getBoundingClientRect(); + var scroll = { + top: target.scrollTop, + left: target.scrollLeft, + topMax: target.scrollTopMax, + leftMax: target.scrollLeftMax, + height: target.scrollHeight, + width: target.scrollWidth, + }; + + ctx.result = { + x: rect.x, + y: rect.y, + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom, + width: rect.width, + height: rect.height, + bounds: rect, + + scrollLeft: scroll.left, + scrollTop: scroll.top, + scrollLeftMax: scroll.leftMax, + scrollTopMax: scroll.topMax, + scrollWidth: scroll.width, + scrollHeight: scroll.height, + scroll: scroll, + }; + + runtime.forEach(propsToMeasure, function (prop) { + if (prop in ctx.result) ctx[prop] = ctx.result[prop]; + else throw "No such measurement as " + prop; + }); + + return runtime.findNext(this, ctx); + }, + }; + }); + + _hyperscript.addLeafExpression("closestExpr", function (parser, runtime, tokens) { + if (tokens.matchToken("closest")) { + if (tokens.matchToken("parent")) { + var parentSearch = true; + } + + var css = null; + if (tokens.currentToken().type === "ATTRIBUTE_REF") { + var attributeRef = parser.parseElement("attributeRefAccess", tokens, null); + css = "[" + attributeRef.attribute.name + "]"; + } + + if (css == null) { + var expr = parser.parseElement("expression", tokens); + if (expr.css == null) { + parser.raiseParseError(tokens, "Expected a CSS expression"); + } else { + css = expr.css; + } + } + + if (tokens.matchToken("to")) { + var to = parser.parseElement("expression", tokens); + } else { + var to = parser.parseElement("implicitMeTarget", tokens); + } + + var closestExpr = { + type: "closestExpr", + parentSearch: parentSearch, + expr: expr, + css: css, + to: to, + args: [to], + op: function (ctx, to) { + if (to == null || !(to instanceof Element)) { + return null; + } else { + if (parentSearch) { + var node = to.parentElement ? to.parentElement.closest(css) : null; + } else { + var node = to.closest(css); + } + return node; + } + }, + evaluate: function (context) { + return runtime.unifiedEval(this, context); + }, + }; + + if (attributeRef) { + attributeRef.root = closestExpr; + attributeRef.args = [closestExpr]; + return attributeRef; + } else { + return closestExpr; + } + } + }); + + _hyperscript.addCommand("go", function (parser, runtime, tokens) { + if (tokens.matchToken("go")) { + if (tokens.matchToken("back")) { + var back = true; + } else { + tokens.matchToken("to"); + if (tokens.matchToken("url")) { + var target = parser.requireElement("stringLike", tokens); + var url = true; + if (tokens.matchToken("in")) { + tokens.requireToken("new"); + tokens.requireToken("window"); + var newWindow = true; + } + } else { + tokens.matchToken("the"); // optional the + var verticalPosition = tokens.matchAnyToken("top", "bottom", "middle"); + var horizontalPosition = tokens.matchAnyToken("left", "center", "right"); + if (verticalPosition || horizontalPosition) { + tokens.requireToken("of"); + } + var target = parser.requireElement("expression", tokens); + var smoothness = tokens.matchAnyToken("smoothly", "instantly"); + + var scrollOptions = {}; + if (verticalPosition) { + if (verticalPosition.value === "top") { + scrollOptions.block = "start"; + } else if (verticalPosition.value === "bottom") { + scrollOptions.block = "end"; + } else if (verticalPosition.value === "middle") { + scrollOptions.block = "center"; + } + } + + if (horizontalPosition) { + if (horizontalPosition.value === "left") { + scrollOptions.inline = "start"; + } else if (horizontalPosition.value === "center") { + scrollOptions.inline = "center"; + } else if (horizontalPosition.value === "right") { + scrollOptions.inline = "end"; + } + } + + if (smoothness) { + if (smoothness.value === "smoothly") { + scrollOptions.behavior = "smooth"; + } else if (smoothness.value === "instantly") { + scrollOptions.behavior = "instant"; + } + } + } + } + + var goCmd = { + target: target, + args: [target], + op: function (ctx, to) { + if (back) { + window.history.back(); + } else if (url) { + if (to) { + if (to.indexOf("#") === 0 && !newWindow) { + window.location.href = to; + } else { + window.open(to, newWindow ? "_blank" : null); + } + } + } else { + runtime.forEach(to, function (target) { + target.scrollIntoView(scrollOptions); + }); + } + return runtime.findNext(goCmd); + }, + }; + return goCmd; + } + }); + + _hyperscript.config.conversions["Values"] = function (/** @type {Node | NodeList} */ node) { + /** @type Object */ + var result = {}; + + var forEach = _hyperscript.internals.runtime.forEach; + + forEach(node, function (/** @type HTMLInputElement */ node) { + // Try to get a value directly from this node + var input = getInputInfo(node); + + if (input !== undefined) { + result[input.name] = input.value; + return; + } + + // Otherwise, try to query all child elements of this node that *should* contain values. + if (node.querySelectorAll != undefined) { + var children = node.querySelectorAll("input,select,textarea"); + forEach(children, appendValue); + } + }); + + return result; + + /** + * @param {HTMLInputElement} node + */ + function appendValue(node) { + var info = getInputInfo(node); + + if (info == undefined) { + return; + } + + // If there is no value already stored in this space. + if (result[info.name] == undefined) { + result[info.name] = info.value; + return; + } + + if (Array.isArray(result[info.name]) && Array.isArray(info.value)) { + result[info.name] = [].concat(result[info.name], info.value); + return; + } + } + + /** + * @param {HTMLInputElement} node + * @returns {{name:string, value:string | string[]} | undefined} + */ + function getInputInfo(node) { + try { + /** @type {{name: string, value: string | string[]}}*/ + var result = { + name: node.name, + value: node.value, + }; + + if (result.name == undefined || result.value == undefined) { + return undefined; + } + + if (node.type == "radio" && node.checked == false) { + return undefined; + } + + if (node.type == "checkbox") { + if (node.checked == false) { + result.value = undefined; + } else if (typeof result.value === "string") { + result.value = [result.value]; + } + } + + if (node.type == "select-multiple") { + /** @type {NodeListOf} */ + var selected = node.querySelectorAll("option[selected]"); + + result.value = []; + for (var index = 0; index < selected.length; index++) { + result.value.push(selected[index].value); + } + } + return result; + } catch (e) { + return undefined; + } + } + }; + + _hyperscript.config.conversions["HTML"] = function (value) { + var toHTML = /** @returns {string}*/ function (/** @type any*/ value) { + if (value instanceof Array) { + return value + .map(function (item) { + return toHTML(item); + }) + .join(""); + } + + if (value instanceof HTMLElement) { + return value.outerHTML; + } + + if (value instanceof NodeList) { + var result = ""; + for (var i = 0; i < value.length; i++) { + var node = value[i]; + if (node instanceof HTMLElement) { + result += node.outerHTML; + } + } + return result; + } + + if (value.toString) { + return value.toString(); + } + + return ""; + }; + + return toHTML(value); + }; + + _hyperscript.config.conversions["Fragment"] = function (val) { + var frag = document.createDocumentFragment(); + _hyperscript.internals.runtime.forEach(val, function (val) { + if (val instanceof Node) frag.append(val); + else { + var temp = document.createElement("template"); + temp.innerHTML = val; + frag.append(temp.content); + } + }); + return frag; + }; +})(); diff --git a/www/test/1.8.0/test/lib/handlebars-v4.7.6.js b/www/test/1.8.0/test/lib/handlebars-v4.7.6.js new file mode 100644 index 000000000..05d9f6141 --- /dev/null +++ b/www/test/1.8.0/test/lib/handlebars-v4.7.6.js @@ -0,0 +1,5210 @@ +/**! + + @license + handlebars v4.7.6 + +Copyright (C) 2011-2019 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["Handlebars"] = factory(); + else + root["Handlebars"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _handlebarsRuntime = __webpack_require__(2); + + var _handlebarsRuntime2 = _interopRequireDefault(_handlebarsRuntime); + + // Compiler imports + + var _handlebarsCompilerAst = __webpack_require__(45); + + var _handlebarsCompilerAst2 = _interopRequireDefault(_handlebarsCompilerAst); + + var _handlebarsCompilerBase = __webpack_require__(46); + + var _handlebarsCompilerCompiler = __webpack_require__(51); + + var _handlebarsCompilerJavascriptCompiler = __webpack_require__(52); + + var _handlebarsCompilerJavascriptCompiler2 = _interopRequireDefault(_handlebarsCompilerJavascriptCompiler); + + var _handlebarsCompilerVisitor = __webpack_require__(49); + + var _handlebarsCompilerVisitor2 = _interopRequireDefault(_handlebarsCompilerVisitor); + + var _handlebarsNoConflict = __webpack_require__(44); + + var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + var _create = _handlebarsRuntime2['default'].create; + function create() { + var hb = _create(); + + hb.compile = function (input, options) { + return _handlebarsCompilerCompiler.compile(input, options, hb); + }; + hb.precompile = function (input, options) { + return _handlebarsCompilerCompiler.precompile(input, options, hb); + }; + + hb.AST = _handlebarsCompilerAst2['default']; + hb.Compiler = _handlebarsCompilerCompiler.Compiler; + hb.JavaScriptCompiler = _handlebarsCompilerJavascriptCompiler2['default']; + hb.Parser = _handlebarsCompilerBase.parser; + hb.parse = _handlebarsCompilerBase.parse; + hb.parseWithoutProcessing = _handlebarsCompilerBase.parseWithoutProcessing; + + return hb; + } + + var inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst.Visitor = _handlebarsCompilerVisitor2['default']; + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + return obj && obj.__esModule ? obj : { + "default": obj + }; + }; + + exports.__esModule = true; + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _handlebarsBase = __webpack_require__(4); + + var base = _interopRequireWildcard(_handlebarsBase); + + // Each of these augment the Handlebars object. No need to setup here. + // (This is done to easily share code between commonjs and browse envs) + + var _handlebarsSafeString = __webpack_require__(37); + + var _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString); + + var _handlebarsException = __webpack_require__(6); + + var _handlebarsException2 = _interopRequireDefault(_handlebarsException); + + var _handlebarsUtils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_handlebarsUtils); + + var _handlebarsRuntime = __webpack_require__(38); + + var runtime = _interopRequireWildcard(_handlebarsRuntime); + + var _handlebarsNoConflict = __webpack_require__(44); + + var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + // For compatibility and usage outside of module systems, make the Handlebars object a namespace + function create() { + var hb = new base.HandlebarsEnvironment(); + + Utils.extend(hb, base); + hb.SafeString = _handlebarsSafeString2['default']; + hb.Exception = _handlebarsException2['default']; + hb.Utils = Utils; + hb.escapeExpression = Utils.escapeExpression; + + hb.VM = runtime; + hb.template = function (spec) { + return runtime.template(spec, hb); + }; + + return hb; + } + + var inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; + } + } + + newObj["default"] = obj; + return newObj; + } + }; + + exports.__esModule = true; + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.HandlebarsEnvironment = HandlebarsEnvironment; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _helpers = __webpack_require__(10); + + var _decorators = __webpack_require__(30); + + var _logger = __webpack_require__(32); + + var _logger2 = _interopRequireDefault(_logger); + + var _internalProtoAccess = __webpack_require__(33); + + var VERSION = '4.7.6'; + exports.VERSION = VERSION; + var COMPILER_REVISION = 8; + exports.COMPILER_REVISION = COMPILER_REVISION; + var LAST_COMPATIBLE_COMPILER_REVISION = 7; + + exports.LAST_COMPATIBLE_COMPILER_REVISION = LAST_COMPATIBLE_COMPILER_REVISION; + var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '== 1.x.x', + 5: '== 2.0.0-alpha.x', + 6: '>= 2.0.0-beta.1', + 7: '>= 4.0.0 <4.3.0', + 8: '>= 4.3.0' + }; + + exports.REVISION_CHANGES = REVISION_CHANGES; + var objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials, decorators) { + this.helpers = helpers || {}; + this.partials = partials || {}; + this.decorators = decorators || {}; + + _helpers.registerDefaultHelpers(this); + _decorators.registerDefaultDecorators(this); + } + + HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: _logger2['default'], + log: _logger2['default'].log, + + registerHelper: function registerHelper(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple helpers'); + } + _utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function unregisterHelper(name) { + delete this.helpers[name]; + }, + + registerPartial: function registerPartial(name, partial) { + if (_utils.toString.call(name) === objectType) { + _utils.extend(this.partials, name); + } else { + if (typeof partial === 'undefined') { + throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined'); + } + this.partials[name] = partial; + } + }, + unregisterPartial: function unregisterPartial(name) { + delete this.partials[name]; + }, + + registerDecorator: function registerDecorator(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple decorators'); + } + _utils.extend(this.decorators, name); + } else { + this.decorators[name] = fn; + } + }, + unregisterDecorator: function unregisterDecorator(name) { + delete this.decorators[name]; + }, + /** + * Reset the memory of illegal property accesses that have already been logged. + * @deprecated should only be used in handlebars test-cases + */ + resetLoggedPropertyAccesses: function resetLoggedPropertyAccesses() { + _internalProtoAccess.resetLoggedProperties(); + } + }; + + var log = _logger2['default'].log; + + exports.log = log; + exports.createFrame = _utils.createFrame; + exports.logger = _logger2['default']; + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.extend = extend; + exports.indexOf = indexOf; + exports.escapeExpression = escapeExpression; + exports.isEmpty = isEmpty; + exports.createFrame = createFrame; + exports.blockParams = blockParams; + exports.appendContextPath = appendContextPath; + var escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`', + '=': '=' + }; + + var badChars = /[&<>"'`=]/g, + possible = /[&<>"'`=]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + var toString = Object.prototype.toString; + + exports.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + /* eslint-disable func-style */ + var isFunction = function isFunction(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + exports.isFunction = isFunction = function (value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + exports.isFunction = isFunction; + + /* eslint-enable func-style */ + + /* istanbul ignore next */ + var isArray = Array.isArray || function (value) { + return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; + }; + + exports.isArray = isArray; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. + + function indexOf(array, value) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + return -1; + } + + function escapeExpression(string) { + if (typeof string !== 'string') { + // don't escape SafeStrings, since they're already safe + if (string && string.toHTML) { + return string.toHTML(); + } else if (string == null) { + return ''; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = '' + string; + } + + if (!possible.test(string)) { + return string; + } + return string.replace(badChars, escapeChar); + } + + function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + function createFrame(object) { + var frame = extend({}, object); + frame._parent = object; + return frame; + } + + function blockParams(params, ids) { + params.path = ids; + return params; + } + + function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$defineProperty = __webpack_require__(7)['default']; + + exports.__esModule = true; + var errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var loc = node && node.loc, + line = undefined, + endLineNumber = undefined, + column = undefined, + endColumn = undefined; + + if (loc) { + line = loc.start.line; + endLineNumber = loc.end.line; + column = loc.start.column; + endColumn = loc.end.column; + + message += ' - ' + line + ':' + column; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + /* istanbul ignore else */ + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Exception); + } + + try { + if (loc) { + this.lineNumber = line; + this.endLineNumber = endLineNumber; + + // Work around issue under safari where we can't directly set the column value + /* istanbul ignore next */ + if (_Object$defineProperty) { + Object.defineProperty(this, 'column', { + value: column, + enumerable: true + }); + Object.defineProperty(this, 'endColumn', { + value: endColumn, + enumerable: true + }); + } else { + this.column = column; + this.endColumn = endColumn; + } + } + } catch (nop) { + /* Ignore if the browser is very particular */ + } + } + + Exception.prototype = new Error(); + + exports['default'] = Exception; + module.exports = exports['default']; + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(8), __esModule: true }; + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + + var $ = __webpack_require__(9); + module.exports = function defineProperty(it, key, desc){ + return $.setDesc(it, key, desc); + }; + +/***/ }), +/* 9 */ +/***/ (function(module, exports) { + + var $Object = Object; + module.exports = { + create: $Object.create, + getProto: $Object.getPrototypeOf, + isEnum: {}.propertyIsEnumerable, + getDesc: $Object.getOwnPropertyDescriptor, + setDesc: $Object.defineProperty, + setDescs: $Object.defineProperties, + getKeys: $Object.keys, + getNames: $Object.getOwnPropertyNames, + getSymbols: $Object.getOwnPropertySymbols, + each: [].forEach + }; + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultHelpers = registerDefaultHelpers; + exports.moveHelperToHooks = moveHelperToHooks; + + var _helpersBlockHelperMissing = __webpack_require__(11); + + var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); + + var _helpersEach = __webpack_require__(12); + + var _helpersEach2 = _interopRequireDefault(_helpersEach); + + var _helpersHelperMissing = __webpack_require__(25); + + var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); + + var _helpersIf = __webpack_require__(26); + + var _helpersIf2 = _interopRequireDefault(_helpersIf); + + var _helpersLog = __webpack_require__(27); + + var _helpersLog2 = _interopRequireDefault(_helpersLog); + + var _helpersLookup = __webpack_require__(28); + + var _helpersLookup2 = _interopRequireDefault(_helpersLookup); + + var _helpersWith = __webpack_require__(29); + + var _helpersWith2 = _interopRequireDefault(_helpersWith); + + function registerDefaultHelpers(instance) { + _helpersBlockHelperMissing2['default'](instance); + _helpersEach2['default'](instance); + _helpersHelperMissing2['default'](instance); + _helpersIf2['default'](instance); + _helpersLog2['default'](instance); + _helpersLookup2['default'](instance); + _helpersWith2['default'](instance); + } + + function moveHelperToHooks(instance, helperName, keepHelper) { + if (instance.helpers[helperName]) { + instance.hooks[helperName] = instance.helpers[helperName]; + if (!keepHelper) { + delete instance.helpers[helperName]; + } + } + } + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('blockHelperMissing', function (context, options) { + var inverse = options.inverse, + fn = options.fn; + + if (context === true) { + return fn(this); + } else if (context === false || context == null) { + return inverse(this); + } else if (_utils.isArray(context)) { + if (context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + var data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); + options = { data: data }; + } + + return fn(context, options); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + + var _Object$keys = __webpack_require__(13)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('each', function (context, options) { + if (!options) { + throw new _exception2['default']('Must pass iterator to #each'); + } + + var fn = options.fn, + inverse = options.inverse, + i = 0, + ret = '', + data = undefined, + contextPath = undefined; + + if (options.data && options.ids) { + contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (_utils.isFunction(context)) { + context = context.call(this); + } + + if (options.data) { + data = _utils.createFrame(options.data); + } + + function execIteration(field, index, last) { + if (data) { + data.key = field; + data.index = index; + data.first = index === 0; + data.last = !!last; + + if (contextPath) { + data.contextPath = contextPath + field; + } + } + + ret = ret + fn(context[field], { + data: data, + blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) + }); + } + + if (context && typeof context === 'object') { + if (_utils.isArray(context)) { + for (var j = context.length; i < j; i++) { + if (i in context) { + execIteration(i, i, i === context.length - 1); + } + } + } else if (global.Symbol && context[global.Symbol.iterator]) { + var newContext = []; + var iterator = context[global.Symbol.iterator](); + for (var it = iterator.next(); !it.done; it = iterator.next()) { + newContext.push(it.value); + } + context = newContext; + for (var j = context.length; i < j; i++) { + execIteration(i, i, i === context.length - 1); + } + } else { + (function () { + var priorKey = undefined; + + _Object$keys(context).forEach(function (key) { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); + } + priorKey = key; + i++; + }); + if (priorKey !== undefined) { + execIteration(priorKey, i - 1, true); + } + })(); + } + } + + if (i === 0) { + ret = inverse(this); + } + + return ret; + }); + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(14), __esModule: true }; + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + + __webpack_require__(15); + module.exports = __webpack_require__(21).Object.keys; + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + + // 19.1.2.14 Object.keys(O) + var toObject = __webpack_require__(16); + + __webpack_require__(18)('keys', function($keys){ + return function keys(it){ + return $keys(toObject(it)); + }; + }); + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + + // 7.1.13 ToObject(argument) + var defined = __webpack_require__(17); + module.exports = function(it){ + return Object(defined(it)); + }; + +/***/ }), +/* 17 */ +/***/ (function(module, exports) { + + // 7.2.1 RequireObjectCoercible(argument) + module.exports = function(it){ + if(it == undefined)throw TypeError("Can't call method on " + it); + return it; + }; + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + + // most Object methods by ES6 should accept primitives + var $export = __webpack_require__(19) + , core = __webpack_require__(21) + , fails = __webpack_require__(24); + module.exports = function(KEY, exec){ + var fn = (core.Object || {})[KEY] || Object[KEY] + , exp = {}; + exp[KEY] = exec(fn); + $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp); + }; + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + + var global = __webpack_require__(20) + , core = __webpack_require__(21) + , ctx = __webpack_require__(22) + , PROTOTYPE = 'prototype'; + + var $export = function(type, name, source){ + var IS_FORCED = type & $export.F + , IS_GLOBAL = type & $export.G + , IS_STATIC = type & $export.S + , IS_PROTO = type & $export.P + , IS_BIND = type & $export.B + , IS_WRAP = type & $export.W + , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) + , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] + , key, own, out; + if(IS_GLOBAL)source = name; + for(key in source){ + // contains in native + own = !IS_FORCED && target && key in target; + if(own && key in exports)continue; + // export native or passed + out = own ? target[key] : source[key]; + // prevent global pollution for namespaces + exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] + // bind timers to global for call from export context + : IS_BIND && own ? ctx(out, global) + // wrap global constructors for prevent change them in library + : IS_WRAP && target[key] == out ? (function(C){ + var F = function(param){ + return this instanceof C ? new C(param) : C(param); + }; + F[PROTOTYPE] = C[PROTOTYPE]; + return F; + // make static versions for prototype methods + })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; + if(IS_PROTO)(exports[PROTOTYPE] || (exports[PROTOTYPE] = {}))[key] = out; + } + }; + // type bitmap + $export.F = 1; // forced + $export.G = 2; // global + $export.S = 4; // static + $export.P = 8; // proto + $export.B = 16; // bind + $export.W = 32; // wrap + module.exports = $export; + +/***/ }), +/* 20 */ +/***/ (function(module, exports) { + + // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 + var global = module.exports = typeof window != 'undefined' && window.Math == Math + ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')(); + if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef + +/***/ }), +/* 21 */ +/***/ (function(module, exports) { + + var core = module.exports = {version: '1.2.6'}; + if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + + // optional / simple context binding + var aFunction = __webpack_require__(23); + module.exports = function(fn, that, length){ + aFunction(fn); + if(that === undefined)return fn; + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + case 2: return function(a, b){ + return fn.call(that, a, b); + }; + case 3: return function(a, b, c){ + return fn.call(that, a, b, c); + }; + } + return function(/* ...args */){ + return fn.apply(that, arguments); + }; + }; + +/***/ }), +/* 23 */ +/***/ (function(module, exports) { + + module.exports = function(it){ + if(typeof it != 'function')throw TypeError(it + ' is not a function!'); + return it; + }; + +/***/ }), +/* 24 */ +/***/ (function(module, exports) { + + module.exports = function(exec){ + try { + return !!exec(); + } catch(e){ + return true; + } + }; + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('helperMissing', function () /* [args, ]options */{ + if (arguments.length === 1) { + // A missing field in a {{foo}} construct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('if', function (conditional, options) { + if (arguments.length != 2) { + throw new _exception2['default']('#if requires exactly one argument'); + } + if (_utils.isFunction(conditional)) { + conditional = conditional.call(this); + } + + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { + return options.inverse(this); + } else { + return options.fn(this); + } + }); + + instance.registerHelper('unless', function (conditional, options) { + if (arguments.length != 2) { + throw new _exception2['default']('#unless requires exactly one argument'); + } + return instance.helpers['if'].call(this, conditional, { + fn: options.inverse, + inverse: options.fn, + hash: options.hash + }); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 27 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('log', function () /* message, options */{ + var args = [undefined], + options = arguments[arguments.length - 1]; + for (var i = 0; i < arguments.length - 1; i++) { + args.push(arguments[i]); + } + + var level = 1; + if (options.hash.level != null) { + level = options.hash.level; + } else if (options.data && options.data.level != null) { + level = options.data.level; + } + args[0] = level; + + instance.log.apply(instance, args); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 28 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('lookup', function (obj, field, options) { + if (!obj) { + // Note for 5.0: Change to "obj == null" in 5.0 + return obj; + } + return options.lookupProperty(obj, field); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('with', function (context, options) { + if (arguments.length != 2) { + throw new _exception2['default']('#with requires exactly one argument'); + } + if (_utils.isFunction(context)) { + context = context.call(this); + } + + var fn = options.fn; + + if (!_utils.isEmpty(context)) { + var data = options.data; + if (options.data && options.ids) { + data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); + } + + return fn(context, { + data: data, + blockParams: _utils.blockParams([context], [data && data.contextPath]) + }); + } else { + return options.inverse(this); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultDecorators = registerDefaultDecorators; + + var _decoratorsInline = __webpack_require__(31); + + var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); + + function registerDefaultDecorators(instance) { + _decoratorsInline2['default'](instance); + } + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerDecorator('inline', function (fn, props, container, options) { + var ret = fn; + if (!props.partials) { + props.partials = {}; + ret = function (context, options) { + // Create a new partials stack frame prior to exec. + var original = container.partials; + container.partials = _utils.extend({}, original, props.partials); + var ret = fn(context, options); + container.partials = original; + return ret; + }; + } + + props.partials[options.args[0]] = options.fn; + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var logger = { + methodMap: ['debug', 'info', 'warn', 'error'], + level: 'info', + + // Maps a given level value to the `methodMap` indexes above. + lookupLevel: function lookupLevel(level) { + if (typeof level === 'string') { + var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); + if (levelMap >= 0) { + level = levelMap; + } else { + level = parseInt(level, 10); + } + } + + return level; + }, + + // Can be overridden in the host environment + log: function log(level) { + level = logger.lookupLevel(level); + + if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { + var method = logger.methodMap[level]; + // eslint-disable-next-line no-console + if (!console[method]) { + method = 'log'; + } + + for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + message[_key - 1] = arguments[_key]; + } + + console[method].apply(console, message); // eslint-disable-line no-console + } + } + }; + + exports['default'] = logger; + module.exports = exports['default']; + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$create = __webpack_require__(34)['default']; + + var _Object$keys = __webpack_require__(13)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + exports.__esModule = true; + exports.createProtoAccessControl = createProtoAccessControl; + exports.resultIsAllowed = resultIsAllowed; + exports.resetLoggedProperties = resetLoggedProperties; + + var _createNewLookupObject = __webpack_require__(36); + + var _logger = __webpack_require__(32); + + var logger = _interopRequireWildcard(_logger); + + var loggedProperties = _Object$create(null); + + function createProtoAccessControl(runtimeOptions) { + var defaultMethodWhiteList = _Object$create(null); + defaultMethodWhiteList['constructor'] = false; + defaultMethodWhiteList['__defineGetter__'] = false; + defaultMethodWhiteList['__defineSetter__'] = false; + defaultMethodWhiteList['__lookupGetter__'] = false; + + var defaultPropertyWhiteList = _Object$create(null); + // eslint-disable-next-line no-proto + defaultPropertyWhiteList['__proto__'] = false; + + return { + properties: { + whitelist: _createNewLookupObject.createNewLookupObject(defaultPropertyWhiteList, runtimeOptions.allowedProtoProperties), + defaultValue: runtimeOptions.allowProtoPropertiesByDefault + }, + methods: { + whitelist: _createNewLookupObject.createNewLookupObject(defaultMethodWhiteList, runtimeOptions.allowedProtoMethods), + defaultValue: runtimeOptions.allowProtoMethodsByDefault + } + }; + } + + function resultIsAllowed(result, protoAccessControl, propertyName) { + if (typeof result === 'function') { + return checkWhiteList(protoAccessControl.methods, propertyName); + } else { + return checkWhiteList(protoAccessControl.properties, propertyName); + } + } + + function checkWhiteList(protoAccessControlForType, propertyName) { + if (protoAccessControlForType.whitelist[propertyName] !== undefined) { + return protoAccessControlForType.whitelist[propertyName] === true; + } + if (protoAccessControlForType.defaultValue !== undefined) { + return protoAccessControlForType.defaultValue; + } + logUnexpecedPropertyAccessOnce(propertyName); + return false; + } + + function logUnexpecedPropertyAccessOnce(propertyName) { + if (loggedProperties[propertyName] !== true) { + loggedProperties[propertyName] = true; + logger.log('error', 'Handlebars: Access has been denied to resolve the property "' + propertyName + '" because it is not an "own property" of its parent.\n' + 'You can add a runtime option to disable the check or this warning:\n' + 'See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details'); + } + } + + function resetLoggedProperties() { + _Object$keys(loggedProperties).forEach(function (propertyName) { + delete loggedProperties[propertyName]; + }); + } + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(35), __esModule: true }; + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + + var $ = __webpack_require__(9); + module.exports = function create(P, D){ + return $.create(P, D); + }; + +/***/ }), +/* 36 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$create = __webpack_require__(34)['default']; + + exports.__esModule = true; + exports.createNewLookupObject = createNewLookupObject; + + var _utils = __webpack_require__(5); + + /** + * Create a new object with "null"-prototype to avoid truthy results on prototype properties. + * The resulting object can be used with "object[property]" to check if a property exists + * @param {...object} sources a varargs parameter of source objects that will be merged + * @returns {object} + */ + + function createNewLookupObject() { + for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { + sources[_key] = arguments[_key]; + } + + return _utils.extend.apply(undefined, [_Object$create(null)].concat(sources)); + } + +/***/ }), +/* 37 */ +/***/ (function(module, exports) { + + // Build out our basic SafeString type + 'use strict'; + + exports.__esModule = true; + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = SafeString.prototype.toHTML = function () { + return '' + this.string; + }; + + exports['default'] = SafeString; + module.exports = exports['default']; + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$seal = __webpack_require__(39)['default']; + + var _Object$keys = __webpack_require__(13)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.checkRevision = checkRevision; + exports.template = template; + exports.wrapProgram = wrapProgram; + exports.resolvePartial = resolvePartial; + exports.invokePartial = invokePartial; + exports.noop = noop; + + var _utils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_utils); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _base = __webpack_require__(4); + + var _helpers = __webpack_require__(10); + + var _internalWrapHelper = __webpack_require__(43); + + var _internalProtoAccess = __webpack_require__(33); + + function checkRevision(compilerInfo) { + var compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = _base.COMPILER_REVISION; + + if (compilerRevision >= _base.LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= _base.COMPILER_REVISION) { + return; + } + + if (compilerRevision < _base.LAST_COMPATIBLE_COMPILER_REVISION) { + var runtimeVersions = _base.REVISION_CHANGES[currentRevision], + compilerVersions = _base.REVISION_CHANGES[compilerRevision]; + throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); + } + } + + function template(templateSpec, env) { + /* istanbul ignore next */ + if (!env) { + throw new _exception2['default']('No environment passed to template'); + } + if (!templateSpec || !templateSpec.main) { + throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); + } + + templateSpec.main.decorator = templateSpec.main_d; + + // Note: Using env.VM references rather than local var references throughout this section to allow + // for external users to override these as pseudo-supported APIs. + env.VM.checkRevision(templateSpec.compiler); + + // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0) + var templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7; + + function invokePartialWrapper(partial, context, options) { + if (options.hash) { + context = Utils.extend({}, context, options.hash); + if (options.ids) { + options.ids[0] = true; + } + } + partial = env.VM.resolvePartial.call(this, partial, context, options); + + var extendedOptions = Utils.extend({}, options, { + hooks: this.hooks, + protoAccessControl: this.protoAccessControl + }); + + var result = env.VM.invokePartial.call(this, partial, context, extendedOptions); + + if (result == null && env.compile) { + options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); + result = options.partials[options.name](context, extendedOptions); + } + if (result != null) { + if (options.indent) { + var lines = result.split('\n'); + for (var i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = options.indent + lines[i]; + } + result = lines.join('\n'); + } + return result; + } else { + throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); + } + } + + // Just add water + var container = { + strict: function strict(obj, name, loc) { + if (!obj || !(name in obj)) { + throw new _exception2['default']('"' + name + '" not defined in ' + obj, { + loc: loc + }); + } + return obj[name]; + }, + lookupProperty: function lookupProperty(parent, propertyName) { + var result = parent[propertyName]; + if (result == null) { + return result; + } + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return result; + } + + if (_internalProtoAccess.resultIsAllowed(result, container.protoAccessControl, propertyName)) { + return result; + } + return undefined; + }, + lookup: function lookup(depths, name) { + var len = depths.length; + for (var i = 0; i < len; i++) { + var result = depths[i] && container.lookupProperty(depths[i], name); + if (result != null) { + return depths[i][name]; + } + } + }, + lambda: function lambda(current, context) { + return typeof current === 'function' ? current.call(context) : current; + }, + + escapeExpression: Utils.escapeExpression, + invokePartial: invokePartialWrapper, + + fn: function fn(i) { + var ret = templateSpec[i]; + ret.decorator = templateSpec[i + '_d']; + return ret; + }, + + programs: [], + program: function program(i, data, declaredBlockParams, blockParams, depths) { + var programWrapper = this.programs[i], + fn = this.fn(i); + if (data || depths || blockParams || declaredBlockParams) { + programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); + } else if (!programWrapper) { + programWrapper = this.programs[i] = wrapProgram(this, i, fn); + } + return programWrapper; + }, + + data: function data(value, depth) { + while (value && depth--) { + value = value._parent; + } + return value; + }, + mergeIfNeeded: function mergeIfNeeded(param, common) { + var obj = param || common; + + if (param && common && param !== common) { + obj = Utils.extend({}, common, param); + } + + return obj; + }, + // An empty object to use as replacement for null-contexts + nullContext: _Object$seal({}), + + noop: env.VM.noop, + compilerInfo: templateSpec.compiler + }; + + function ret(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var data = options.data; + + ret._setup(options); + if (!options.partial && templateSpec.useData) { + data = initData(context, data); + } + var depths = undefined, + blockParams = templateSpec.useBlockParams ? [] : undefined; + if (templateSpec.useDepths) { + if (options.depths) { + depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths; + } else { + depths = [context]; + } + } + + function main(context /*, options*/) { + return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); + } + + main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); + return main(context, options); + } + + ret.isTop = true; + + ret._setup = function (options) { + if (!options.partial) { + var mergedHelpers = Utils.extend({}, env.helpers, options.helpers); + wrapHelpersToPassLookupProperty(mergedHelpers, container); + container.helpers = mergedHelpers; + + if (templateSpec.usePartial) { + // Use mergeIfNeeded here to prevent compiling global partials multiple times + container.partials = container.mergeIfNeeded(options.partials, env.partials); + } + if (templateSpec.usePartial || templateSpec.useDecorators) { + container.decorators = Utils.extend({}, env.decorators, options.decorators); + } + + container.hooks = {}; + container.protoAccessControl = _internalProtoAccess.createProtoAccessControl(options); + + var keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7; + _helpers.moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers); + _helpers.moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers); + } else { + container.protoAccessControl = options.protoAccessControl; // internal option + container.helpers = options.helpers; + container.partials = options.partials; + container.decorators = options.decorators; + container.hooks = options.hooks; + } + }; + + ret._child = function (i, data, blockParams, depths) { + if (templateSpec.useBlockParams && !blockParams) { + throw new _exception2['default']('must pass block params'); + } + if (templateSpec.useDepths && !depths) { + throw new _exception2['default']('must pass parent depths'); + } + + return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); + }; + return ret; + } + + function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { + function prog(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var currentDepths = depths; + if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { + currentDepths = [context].concat(depths); + } + + return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); + } + + prog = executeDecorators(fn, prog, container, depths, data, blockParams); + + prog.program = i; + prog.depth = depths ? depths.length : 0; + prog.blockParams = declaredBlockParams || 0; + return prog; + } + + /** + * This is currently part of the official API, therefore implementation details should not be changed. + */ + + function resolvePartial(partial, context, options) { + if (!partial) { + if (options.name === '@partial-block') { + partial = options.data['partial-block']; + } else { + partial = options.partials[options.name]; + } + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; + } + + function invokePartial(partial, context, options) { + // Use the current closure context to save the partial-block if this partial + var currentPartialBlock = options.data && options.data['partial-block']; + options.partial = true; + if (options.ids) { + options.data.contextPath = options.ids[0] || options.data.contextPath; + } + + var partialBlock = undefined; + if (options.fn && options.fn !== noop) { + (function () { + options.data = _base.createFrame(options.data); + // Wrapper function to get access to currentPartialBlock from the closure + var fn = options.fn; + partialBlock = options.data['partial-block'] = function partialBlockWrapper(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + // Restore the partial-block from the closure for the execution of the block + // i.e. the part inside the block of the partial call. + options.data = _base.createFrame(options.data); + options.data['partial-block'] = currentPartialBlock; + return fn(context, options); + }; + if (fn.partials) { + options.partials = Utils.extend({}, options.partials, fn.partials); + } + })(); + } + + if (partial === undefined && partialBlock) { + partial = partialBlock; + } + + if (partial === undefined) { + throw new _exception2['default']('The partial ' + options.name + ' could not be found'); + } else if (partial instanceof Function) { + return partial(context, options); + } + } + + function noop() { + return ''; + } + + function initData(context, data) { + if (!data || !('root' in data)) { + data = data ? _base.createFrame(data) : {}; + data.root = context; + } + return data; + } + + function executeDecorators(fn, prog, container, depths, data, blockParams) { + if (fn.decorator) { + var props = {}; + prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); + Utils.extend(prog, props); + } + return prog; + } + + function wrapHelpersToPassLookupProperty(mergedHelpers, container) { + _Object$keys(mergedHelpers).forEach(function (helperName) { + var helper = mergedHelpers[helperName]; + mergedHelpers[helperName] = passLookupPropertyOption(helper, container); + }); + } + + function passLookupPropertyOption(helper, container) { + var lookupProperty = container.lookupProperty; + return _internalWrapHelper.wrapHelper(helper, function (options) { + return Utils.extend({ lookupProperty: lookupProperty }, options); + }); + } + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(40), __esModule: true }; + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + + __webpack_require__(41); + module.exports = __webpack_require__(21).Object.seal; + +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { + + // 19.1.2.17 Object.seal(O) + var isObject = __webpack_require__(42); + + __webpack_require__(18)('seal', function($seal){ + return function seal(it){ + return $seal && isObject(it) ? $seal(it) : it; + }; + }); + +/***/ }), +/* 42 */ +/***/ (function(module, exports) { + + module.exports = function(it){ + return typeof it === 'object' ? it !== null : typeof it === 'function'; + }; + +/***/ }), +/* 43 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.wrapHelper = wrapHelper; + + function wrapHelper(helper, transformOptionsFn) { + if (typeof helper !== 'function') { + // This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639 + // We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function. + return helper; + } + var wrapper = function wrapper() /* dynamic arguments */{ + var options = arguments[arguments.length - 1]; + arguments[arguments.length - 1] = transformOptionsFn(options); + return helper.apply(this, arguments); + }; + return wrapper; + } + +/***/ }), +/* 44 */ +/***/ (function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + + exports.__esModule = true; + + exports['default'] = function (Handlebars) { + /* istanbul ignore next */ + var root = typeof global !== 'undefined' ? global : window, + $Handlebars = root.Handlebars; + /* istanbul ignore next */ + Handlebars.noConflict = function () { + if (root.Handlebars === Handlebars) { + root.Handlebars = $Handlebars; + } + return Handlebars; + }; + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 45 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + var AST = { + // Public API used to evaluate derived attributes regarding AST nodes + helpers: { + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + helperExpression: function helperExpression(node) { + return node.type === 'SubExpression' || (node.type === 'MustacheStatement' || node.type === 'BlockStatement') && !!(node.params && node.params.length || node.hash); + }, + + scopedId: function scopedId(path) { + return (/^\.|this\b/.test(path.original) + ); + }, + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + simpleId: function simpleId(path) { + return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth; + } + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // must modify the object to operate properly. + exports['default'] = AST; + module.exports = exports['default']; + +/***/ }), +/* 46 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + exports.__esModule = true; + exports.parseWithoutProcessing = parseWithoutProcessing; + exports.parse = parse; + + var _parser = __webpack_require__(47); + + var _parser2 = _interopRequireDefault(_parser); + + var _whitespaceControl = __webpack_require__(48); + + var _whitespaceControl2 = _interopRequireDefault(_whitespaceControl); + + var _helpers = __webpack_require__(50); + + var Helpers = _interopRequireWildcard(_helpers); + + var _utils = __webpack_require__(5); + + exports.parser = _parser2['default']; + + var yy = {}; + _utils.extend(yy, Helpers); + + function parseWithoutProcessing(input, options) { + // Just return if an already-compiled AST was passed in. + if (input.type === 'Program') { + return input; + } + + _parser2['default'].yy = yy; + + // Altering the shared object here, but this is ok as parser is a sync operation + yy.locInfo = function (locInfo) { + return new yy.SourceLocation(options && options.srcName, locInfo); + }; + + var ast = _parser2['default'].parse(input); + + return ast; + } + + function parse(input, options) { + var ast = parseWithoutProcessing(input, options); + var strip = new _whitespaceControl2['default'](options); + + return strip.accept(ast); + } + +/***/ }), +/* 47 */ +/***/ (function(module, exports) { + + // File ignored in coverage tests via setting in .istanbul.yml + /* Jison generated parser */ + "use strict"; + + exports.__esModule = true; + var handlebars = (function () { + var parser = { trace: function trace() {}, + yy: {}, + symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "mustache_repetition0": 49, "mustache_option0": 50, "OPEN_UNESCAPED": 51, "mustache_repetition1": 52, "mustache_option1": 53, "CLOSE_UNESCAPED": 54, "OPEN_PARTIAL": 55, "partialName": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "param": 63, "sexpr": 64, "OPEN_SEXPR": 65, "sexpr_repetition0": 66, "sexpr_option0": 67, "CLOSE_SEXPR": 68, "hash": 69, "hash_repetition_plus0": 70, "hashSegment": 71, "ID": 72, "EQUALS": 73, "blockParams": 74, "OPEN_BLOCK_PARAMS": 75, "blockParams_repetition_plus0": 76, "CLOSE_BLOCK_PARAMS": 77, "path": 78, "dataName": 79, "STRING": 80, "NUMBER": 81, "BOOLEAN": 82, "UNDEFINED": 83, "NULL": 84, "DATA": 85, "pathSegments": 86, "SEP": 87, "$accept": 0, "$end": 1 }, + terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" }, + productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 0], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: + return $$[$0 - 1]; + break; + case 2: + this.$ = yy.prepareProgram($$[$0]); + break; + case 3: + this.$ = $$[$0]; + break; + case 4: + this.$ = $$[$0]; + break; + case 5: + this.$ = $$[$0]; + break; + case 6: + this.$ = $$[$0]; + break; + case 7: + this.$ = $$[$0]; + break; + case 8: + this.$ = $$[$0]; + break; + case 9: + this.$ = { + type: 'CommentStatement', + value: yy.stripComment($$[$0]), + strip: yy.stripFlags($$[$0], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 10: + this.$ = { + type: 'ContentStatement', + original: $$[$0], + value: $$[$0], + loc: yy.locInfo(this._$) + }; + + break; + case 11: + this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 12: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] }; + break; + case 13: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$); + break; + case 14: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$); + break; + case 15: + this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 16: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 17: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 18: + this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] }; + break; + case 19: + var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$), + program = yy.prepareProgram([inverse], $$[$0 - 1].loc); + program.chained = true; + + this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true }; + + break; + case 20: + this.$ = $$[$0]; + break; + case 21: + this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) }; + break; + case 22: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 23: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 24: + this.$ = { + type: 'PartialStatement', + name: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + indent: '', + strip: yy.stripFlags($$[$0 - 4], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 25: + this.$ = yy.preparePartialBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 26: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 4], $$[$0]) }; + break; + case 27: + this.$ = $$[$0]; + break; + case 28: + this.$ = $$[$0]; + break; + case 29: + this.$ = { + type: 'SubExpression', + path: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + loc: yy.locInfo(this._$) + }; + + break; + case 30: + this.$ = { type: 'Hash', pairs: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 31: + this.$ = { type: 'HashPair', key: yy.id($$[$0 - 2]), value: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 32: + this.$ = yy.id($$[$0 - 1]); + break; + case 33: + this.$ = $$[$0]; + break; + case 34: + this.$ = $$[$0]; + break; + case 35: + this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 36: + this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) }; + break; + case 37: + this.$ = { type: 'BooleanLiteral', value: $$[$0] === 'true', original: $$[$0] === 'true', loc: yy.locInfo(this._$) }; + break; + case 38: + this.$ = { type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(this._$) }; + break; + case 39: + this.$ = { type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(this._$) }; + break; + case 40: + this.$ = $$[$0]; + break; + case 41: + this.$ = $$[$0]; + break; + case 42: + this.$ = yy.preparePath(true, $$[$0], this._$); + break; + case 43: + this.$ = yy.preparePath(false, $$[$0], this._$); + break; + case 44: + $$[$0 - 2].push({ part: yy.id($$[$0]), original: $$[$0], separator: $$[$0 - 1] });this.$ = $$[$0 - 2]; + break; + case 45: + this.$ = [{ part: yy.id($$[$0]), original: $$[$0] }]; + break; + case 46: + this.$ = []; + break; + case 47: + $$[$0 - 1].push($$[$0]); + break; + case 48: + this.$ = []; + break; + case 49: + $$[$0 - 1].push($$[$0]); + break; + case 50: + this.$ = []; + break; + case 51: + $$[$0 - 1].push($$[$0]); + break; + case 58: + this.$ = []; + break; + case 59: + $$[$0 - 1].push($$[$0]); + break; + case 64: + this.$ = []; + break; + case 65: + $$[$0 - 1].push($$[$0]); + break; + case 70: + this.$ = []; + break; + case 71: + $$[$0 - 1].push($$[$0]); + break; + case 78: + this.$ = []; + break; + case 79: + $$[$0 - 1].push($$[$0]); + break; + case 82: + this.$ = []; + break; + case 83: + $$[$0 - 1].push($$[$0]); + break; + case 86: + this.$ = []; + break; + case 87: + $$[$0 - 1].push($$[$0]); + break; + case 90: + this.$ = []; + break; + case 91: + $$[$0 - 1].push($$[$0]); + break; + case 94: + this.$ = []; + break; + case 95: + $$[$0 - 1].push($$[$0]); + break; + case 98: + this.$ = [$$[$0]]; + break; + case 99: + $$[$0 - 1].push($$[$0]); + break; + case 100: + this.$ = [$$[$0]]; + break; + case 101: + $$[$0 - 1].push($$[$0]); + break; + } + }, + table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 15: [2, 48], 17: 39, 18: [2, 48] }, { 20: 41, 56: 40, 64: 42, 65: [1, 43], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 44, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 45, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 41, 56: 48, 64: 42, 65: [1, 43], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 49, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 50] }, { 72: [1, 35], 86: 51 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 52, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 53, 38: 55, 39: [1, 57], 43: 56, 44: [1, 58], 45: 54, 47: [2, 54] }, { 28: 59, 43: 60, 44: [1, 58], 47: [2, 56] }, { 13: 62, 15: [1, 20], 18: [1, 61] }, { 33: [2, 86], 57: 63, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 64, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 65, 47: [1, 66] }, { 30: 67, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 68, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 69, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 70, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 74, 33: [2, 80], 50: 71, 63: 72, 64: 75, 65: [1, 43], 69: 73, 70: 76, 71: 77, 72: [1, 78], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 79] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 50] }, { 20: 74, 53: 80, 54: [2, 84], 63: 81, 64: 75, 65: [1, 43], 69: 82, 70: 76, 71: 77, 72: [1, 78], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 83, 47: [1, 66] }, { 47: [2, 55] }, { 4: 84, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 85, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 86, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 87, 47: [1, 66] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 74, 33: [2, 88], 58: 88, 63: 89, 64: 75, 65: [1, 43], 69: 90, 70: 76, 71: 77, 72: [1, 78], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 91, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 92, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 74, 31: 93, 33: [2, 60], 63: 94, 64: 75, 65: [1, 43], 69: 95, 70: 76, 71: 77, 72: [1, 78], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 74, 33: [2, 66], 36: 96, 63: 97, 64: 75, 65: [1, 43], 69: 98, 70: 76, 71: 77, 72: [1, 78], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 74, 22: 99, 23: [2, 52], 63: 100, 64: 75, 65: [1, 43], 69: 101, 70: 76, 71: 77, 72: [1, 78], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 74, 33: [2, 92], 62: 102, 63: 103, 64: 75, 65: [1, 43], 69: 104, 70: 76, 71: 77, 72: [1, 78], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 105] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 106, 72: [1, 107], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 108], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 109] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 55, 39: [1, 57], 43: 56, 44: [1, 58], 45: 111, 46: 110, 47: [2, 76] }, { 33: [2, 70], 40: 112, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 113] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 74, 63: 115, 64: 75, 65: [1, 43], 67: 114, 68: [2, 96], 69: 116, 70: 76, 71: 77, 72: [1, 78], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 117] }, { 32: 118, 33: [2, 62], 74: 119, 75: [1, 120] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 121, 74: 122, 75: [1, 120] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 123] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 124] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 108] }, { 20: 74, 63: 125, 64: 75, 65: [1, 43], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 74, 33: [2, 72], 41: 126, 63: 127, 64: 75, 65: [1, 43], 69: 128, 70: 76, 71: 77, 72: [1, 78], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 129] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 130] }, { 33: [2, 63] }, { 72: [1, 132], 76: 131 }, { 33: [1, 133] }, { 33: [2, 69] }, { 15: [2, 12], 18: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 134, 74: 135, 75: [1, 120] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 137], 77: [1, 136] }, { 72: [2, 100], 77: [2, 100] }, { 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] }, { 33: [1, 138] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }], + defaultActions: { 4: [2, 1], 54: [2, 55], 56: [2, 20], 60: [2, 57], 73: [2, 81], 82: [2, 85], 86: [2, 18], 90: [2, 89], 101: [2, 53], 104: [2, 93], 110: [2, 19], 111: [2, 77], 116: [2, 97], 119: [2, 63], 122: [2, 69], 135: [2, 75], 136: [2, 32] }, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], + lstack = [], + table = this.table, + yytext = "", + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, + preErrorSymbol, + state, + action, + a, + r, + yyval = {}, + p, + len, + newState, + expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1 ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, { text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected }); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column }; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + /* Jison generated lexer */ + var lexer = (function () { + var lexer = { EOF: 1, + parseError: function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput: function setInput(input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 }; + if (this.options.ranges) this.yylloc.range = [0, 0]; + this.offset = 0; + return this; + }, + input: function input() { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput: function unput(ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len - 1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) this.yylineno -= lines.length - 1; + var r = this.yylloc.range; + + this.yylloc = { first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more: function more() { + this._more = true; + return this; + }, + less: function less(n) { + this.unput(this.match.slice(n)); + }, + pastInput: function pastInput() { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput: function upcomingInput() { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20 - next.length); + } + return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + showPosition: function showPosition() { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + next: function next() { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, match, tempMatch, index, col, lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = { first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index], this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) this.done = false; + if (token) return token;else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { text: "", token: null, line: this.yylineno }); + } + }, + lex: function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin: function begin(condition) { + this.conditionStack.push(condition); + }, + popState: function popState() { + return this.conditionStack.pop(); + }, + _currentRules: function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + }, + topState: function topState() { + return this.conditionStack[this.conditionStack.length - 2]; + }, + pushState: function begin(condition) { + this.begin(condition); + } }; + lexer.options = {}; + lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START) { + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substring(start, yy_.yyleng - end + start); + } + + var YYSTATE = YY_START; + switch ($avoiding_name_collisions) { + case 0: + if (yy_.yytext.slice(-2) === "\\\\") { + strip(0, 1); + this.begin("mu"); + } else if (yy_.yytext.slice(-1) === "\\") { + strip(0, 1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if (yy_.yytext) return 15; + + break; + case 1: + return 15; + break; + case 2: + this.popState(); + return 15; + + break; + case 3: + this.begin('raw');return 15; + break; + case 4: + this.popState(); + // Should be using `this.topState()` below, but it currently + // returns the second top instead of the first top. Opened an + // issue about it at https://github.com/zaach/jison/issues/291 + if (this.conditionStack[this.conditionStack.length - 1] === 'raw') { + return 15; + } else { + strip(5, 9); + return 'END_RAW_BLOCK'; + } + + break; + case 5: + return 15; + break; + case 6: + this.popState(); + return 14; + + break; + case 7: + return 65; + break; + case 8: + return 68; + break; + case 9: + return 19; + break; + case 10: + this.popState(); + this.begin('raw'); + return 23; + + break; + case 11: + return 55; + break; + case 12: + return 60; + break; + case 13: + return 29; + break; + case 14: + return 47; + break; + case 15: + this.popState();return 44; + break; + case 16: + this.popState();return 44; + break; + case 17: + return 34; + break; + case 18: + return 39; + break; + case 19: + return 51; + break; + case 20: + return 48; + break; + case 21: + this.unput(yy_.yytext); + this.popState(); + this.begin('com'); + + break; + case 22: + this.popState(); + return 14; + + break; + case 23: + return 48; + break; + case 24: + return 73; + break; + case 25: + return 72; + break; + case 26: + return 72; + break; + case 27: + return 87; + break; + case 28: + // ignore whitespace + break; + case 29: + this.popState();return 54; + break; + case 30: + this.popState();return 33; + break; + case 31: + yy_.yytext = strip(1, 2).replace(/\\"/g, '"');return 80; + break; + case 32: + yy_.yytext = strip(1, 2).replace(/\\'/g, "'");return 80; + break; + case 33: + return 85; + break; + case 34: + return 82; + break; + case 35: + return 82; + break; + case 36: + return 83; + break; + case 37: + return 84; + break; + case 38: + return 81; + break; + case 39: + return 75; + break; + case 40: + return 77; + break; + case 41: + return 72; + break; + case 42: + yy_.yytext = yy_.yytext.replace(/\\([\\\]])/g, '$1');return 72; + break; + case 43: + return 'INVALID'; + break; + case 44: + return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^\/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]+?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/]; + lexer.conditions = { "mu": { "rules": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "inclusive": false }, "emu": { "rules": [2], "inclusive": false }, "com": { "rules": [6], "inclusive": false }, "raw": { "rules": [3, 4, 5], "inclusive": false }, "INITIAL": { "rules": [0, 1, 44], "inclusive": true } }; + return lexer; + })(); + parser.lexer = lexer; + function Parser() { + this.yy = {}; + }Parser.prototype = parser;parser.Parser = Parser; + return new Parser(); + })();exports["default"] = handlebars; + module.exports = exports["default"]; + +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _visitor = __webpack_require__(49); + + var _visitor2 = _interopRequireDefault(_visitor); + + function WhitespaceControl() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + this.options = options; + } + WhitespaceControl.prototype = new _visitor2['default'](); + + WhitespaceControl.prototype.Program = function (program) { + var doStandalone = !this.options.ignoreStandalone; + + var isRoot = !this.isRootSeen; + this.isRootSeen = true; + + var body = program.body; + for (var i = 0, l = body.length; i < l; i++) { + var current = body[i], + strip = this.accept(current); + + if (!strip) { + continue; + } + + var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), + _isNextWhitespace = isNextWhitespace(body, i, isRoot), + openStandalone = strip.openStandalone && _isPrevWhitespace, + closeStandalone = strip.closeStandalone && _isNextWhitespace, + inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; + + if (strip.close) { + omitRight(body, i, true); + } + if (strip.open) { + omitLeft(body, i, true); + } + + if (doStandalone && inlineStandalone) { + omitRight(body, i); + + if (omitLeft(body, i)) { + // If we are on a standalone node, save the indent info for partials + if (current.type === 'PartialStatement') { + // Pull out the whitespace from the final line + current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; + } + } + } + if (doStandalone && openStandalone) { + omitRight((current.program || current.inverse).body); + + // Strip out the previous content node if it's whitespace only + omitLeft(body, i); + } + if (doStandalone && closeStandalone) { + // Always strip the next node + omitRight(body, i); + + omitLeft((current.inverse || current.program).body); + } + } + + return program; + }; + + WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) { + this.accept(block.program); + this.accept(block.inverse); + + // Find the inverse program that is involed with whitespace stripping. + var program = block.program || block.inverse, + inverse = block.program && block.inverse, + firstInverse = inverse, + lastInverse = inverse; + + if (inverse && inverse.chained) { + firstInverse = inverse.body[0].program; + + // Walk the inverse chain to find the last inverse that is actually in the chain. + while (lastInverse.chained) { + lastInverse = lastInverse.body[lastInverse.body.length - 1].program; + } + } + + var strip = { + open: block.openStrip.open, + close: block.closeStrip.close, + + // Determine the standalone candiacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(program.body), + closeStandalone: isPrevWhitespace((firstInverse || program).body) + }; + + if (block.openStrip.close) { + omitRight(program.body, null, true); + } + + if (inverse) { + var inverseStrip = block.inverseStrip; + + if (inverseStrip.open) { + omitLeft(program.body, null, true); + } + + if (inverseStrip.close) { + omitRight(firstInverse.body, null, true); + } + if (block.closeStrip.open) { + omitLeft(lastInverse.body, null, true); + } + + // Find standalone else statments + if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) { + omitLeft(program.body); + omitRight(firstInverse.body); + } + } else if (block.closeStrip.open) { + omitLeft(program.body, null, true); + } + + return strip; + }; + + WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) { + return mustache.strip; + }; + + WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) { + /* istanbul ignore next */ + var strip = node.strip || {}; + return { + inlineStandalone: true, + open: strip.open, + close: strip.close + }; + }; + + function isPrevWhitespace(body, i, isRoot) { + if (i === undefined) { + i = body.length; + } + + // Nodes that end with newlines are considered whitespace (but are special + // cased for strip operations) + var prev = body[i - 1], + sibling = body[i - 2]; + if (!prev) { + return isRoot; + } + + if (prev.type === 'ContentStatement') { + return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original); + } + } + function isNextWhitespace(body, i, isRoot) { + if (i === undefined) { + i = -1; + } + + var next = body[i + 1], + sibling = body[i + 2]; + if (!next) { + return isRoot; + } + + if (next.type === 'ContentStatement') { + return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original); + } + } + + // Marks the node to the right of the position as omitted. + // I.e. {{foo}}' ' will mark the ' ' node as omitted. + // + // If i is undefined, then the first child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitRight(body, i, multiple) { + var current = body[i == null ? 0 : i + 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) { + return; + } + + var original = current.value; + current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, ''); + current.rightStripped = current.value !== original; + } + + // Marks the node to the left of the position as omitted. + // I.e. ' '{{foo}} will mark the ' ' node as omitted. + // + // If i is undefined then the last child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitLeft(body, i, multiple) { + var current = body[i == null ? body.length - 1 : i - 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) { + return; + } + + // We omit the last node if it's whitespace only and not preceded by a non-content node. + var original = current.value; + current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); + current.leftStripped = current.value !== original; + return current.leftStripped; + } + + exports['default'] = WhitespaceControl; + module.exports = exports['default']; + +/***/ }), +/* 49 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function Visitor() { + this.parents = []; + } + + Visitor.prototype = { + constructor: Visitor, + mutating: false, + + // Visits a given value. If mutating, will replace the value if necessary. + acceptKey: function acceptKey(node, name) { + var value = this.accept(node[name]); + if (this.mutating) { + // Hacky sanity check: This may have a few false positives for type for the helper + // methods but will generally do the right thing without a lot of overhead. + if (value && !Visitor.prototype[value.type]) { + throw new _exception2['default']('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); + } + node[name] = value; + } + }, + + // Performs an accept operation with added sanity check to ensure + // required keys are not removed. + acceptRequired: function acceptRequired(node, name) { + this.acceptKey(node, name); + + if (!node[name]) { + throw new _exception2['default'](node.type + ' requires ' + name); + } + }, + + // Traverses a given array. If mutating, empty respnses will be removed + // for child elements. + acceptArray: function acceptArray(array) { + for (var i = 0, l = array.length; i < l; i++) { + this.acceptKey(array, i); + + if (!array[i]) { + array.splice(i, 1); + i--; + l--; + } + } + }, + + accept: function accept(object) { + if (!object) { + return; + } + + /* istanbul ignore next: Sanity code */ + if (!this[object.type]) { + throw new _exception2['default']('Unknown type: ' + object.type, object); + } + + if (this.current) { + this.parents.unshift(this.current); + } + this.current = object; + + var ret = this[object.type](object); + + this.current = this.parents.shift(); + + if (!this.mutating || ret) { + return ret; + } else if (ret !== false) { + return object; + } + }, + + Program: function Program(program) { + this.acceptArray(program.body); + }, + + MustacheStatement: visitSubExpression, + Decorator: visitSubExpression, + + BlockStatement: visitBlock, + DecoratorBlock: visitBlock, + + PartialStatement: visitPartial, + PartialBlockStatement: function PartialBlockStatement(partial) { + visitPartial.call(this, partial); + + this.acceptKey(partial, 'program'); + }, + + ContentStatement: function ContentStatement() /* content */{}, + CommentStatement: function CommentStatement() /* comment */{}, + + SubExpression: visitSubExpression, + + PathExpression: function PathExpression() /* path */{}, + + StringLiteral: function StringLiteral() /* string */{}, + NumberLiteral: function NumberLiteral() /* number */{}, + BooleanLiteral: function BooleanLiteral() /* bool */{}, + UndefinedLiteral: function UndefinedLiteral() /* literal */{}, + NullLiteral: function NullLiteral() /* literal */{}, + + Hash: function Hash(hash) { + this.acceptArray(hash.pairs); + }, + HashPair: function HashPair(pair) { + this.acceptRequired(pair, 'value'); + } + }; + + function visitSubExpression(mustache) { + this.acceptRequired(mustache, 'path'); + this.acceptArray(mustache.params); + this.acceptKey(mustache, 'hash'); + } + function visitBlock(block) { + visitSubExpression.call(this, block); + + this.acceptKey(block, 'program'); + this.acceptKey(block, 'inverse'); + } + function visitPartial(partial) { + this.acceptRequired(partial, 'name'); + this.acceptArray(partial.params); + this.acceptKey(partial, 'hash'); + } + + exports['default'] = Visitor; + module.exports = exports['default']; + +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.SourceLocation = SourceLocation; + exports.id = id; + exports.stripFlags = stripFlags; + exports.stripComment = stripComment; + exports.preparePath = preparePath; + exports.prepareMustache = prepareMustache; + exports.prepareRawBlock = prepareRawBlock; + exports.prepareBlock = prepareBlock; + exports.prepareProgram = prepareProgram; + exports.preparePartialBlock = preparePartialBlock; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function validateClose(open, close) { + close = close.path ? close.path.original : close; + + if (open.path.original !== close) { + var errorNode = { loc: open.path.loc }; + + throw new _exception2['default'](open.path.original + " doesn't match " + close, errorNode); + } + } + + function SourceLocation(source, locInfo) { + this.source = source; + this.start = { + line: locInfo.first_line, + column: locInfo.first_column + }; + this.end = { + line: locInfo.last_line, + column: locInfo.last_column + }; + } + + function id(token) { + if (/^\[.*\]$/.test(token)) { + return token.substring(1, token.length - 1); + } else { + return token; + } + } + + function stripFlags(open, close) { + return { + open: open.charAt(2) === '~', + close: close.charAt(close.length - 3) === '~' + }; + } + + function stripComment(comment) { + return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, ''); + } + + function preparePath(data, parts, loc) { + loc = this.locInfo(loc); + + var original = data ? '@' : '', + dig = [], + depth = 0; + + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i].part, + + // If we have [] syntax then we do not treat path references as operators, + // i.e. foo.[this] resolves to approximately context.foo['this'] + isLiteral = parts[i].original !== part; + original += (parts[i].separator || '') + part; + + if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { + if (dig.length > 0) { + throw new _exception2['default']('Invalid path: ' + original, { loc: loc }); + } else if (part === '..') { + depth++; + } + } else { + dig.push(part); + } + } + + return { + type: 'PathExpression', + data: data, + depth: depth, + parts: dig, + original: original, + loc: loc + }; + } + + function prepareMustache(path, params, hash, open, strip, locInfo) { + // Must use charAt to support IE pre-10 + var escapeFlag = open.charAt(3) || open.charAt(2), + escaped = escapeFlag !== '{' && escapeFlag !== '&'; + + var decorator = /\*/.test(open); + return { + type: decorator ? 'Decorator' : 'MustacheStatement', + path: path, + params: params, + hash: hash, + escaped: escaped, + strip: strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareRawBlock(openRawBlock, contents, close, locInfo) { + validateClose(openRawBlock, close); + + locInfo = this.locInfo(locInfo); + var program = { + type: 'Program', + body: contents, + strip: {}, + loc: locInfo + }; + + return { + type: 'BlockStatement', + path: openRawBlock.path, + params: openRawBlock.params, + hash: openRawBlock.hash, + program: program, + openStrip: {}, + inverseStrip: {}, + closeStrip: {}, + loc: locInfo + }; + } + + function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { + if (close && close.path) { + validateClose(openBlock, close); + } + + var decorator = /\*/.test(openBlock.open); + + program.blockParams = openBlock.blockParams; + + var inverse = undefined, + inverseStrip = undefined; + + if (inverseAndProgram) { + if (decorator) { + throw new _exception2['default']('Unexpected inverse block on decorator', inverseAndProgram); + } + + if (inverseAndProgram.chain) { + inverseAndProgram.program.body[0].closeStrip = close.strip; + } + + inverseStrip = inverseAndProgram.strip; + inverse = inverseAndProgram.program; + } + + if (inverted) { + inverted = inverse; + inverse = program; + program = inverted; + } + + return { + type: decorator ? 'DecoratorBlock' : 'BlockStatement', + path: openBlock.path, + params: openBlock.params, + hash: openBlock.hash, + program: program, + inverse: inverse, + openStrip: openBlock.strip, + inverseStrip: inverseStrip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareProgram(statements, loc) { + if (!loc && statements.length) { + var firstLoc = statements[0].loc, + lastLoc = statements[statements.length - 1].loc; + + /* istanbul ignore else */ + if (firstLoc && lastLoc) { + loc = { + source: firstLoc.source, + start: { + line: firstLoc.start.line, + column: firstLoc.start.column + }, + end: { + line: lastLoc.end.line, + column: lastLoc.end.column + } + }; + } + } + + return { + type: 'Program', + body: statements, + strip: {}, + loc: loc + }; + } + + function preparePartialBlock(open, program, close, locInfo) { + validateClose(open, close); + + return { + type: 'PartialBlockStatement', + name: open.path, + params: open.params, + hash: open.hash, + program: program, + openStrip: open.strip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + +/***/ }), +/* 51 */ +/***/ (function(module, exports, __webpack_require__) { + + /* eslint-disable new-cap */ + + 'use strict'; + + var _Object$create = __webpack_require__(34)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.Compiler = Compiler; + exports.precompile = precompile; + exports.compile = compile; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _ast = __webpack_require__(45); + + var _ast2 = _interopRequireDefault(_ast); + + var slice = [].slice; + + function Compiler() {} + + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + equals: function equals(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { + return false; + } + } + + // We know that length is the same between the two arrays because they are directly tied + // to the opcode behavior above. + len = this.children.length; + for (var i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + + return true; + }, + + guid: 0, + + compile: function compile(program, options) { + this.sourceNode = []; + this.opcodes = []; + this.children = []; + this.options = options; + this.stringParams = options.stringParams; + this.trackIds = options.trackIds; + + options.blockParams = options.blockParams || []; + + options.knownHelpers = _utils.extend(_Object$create(null), { + helperMissing: true, + blockHelperMissing: true, + each: true, + 'if': true, + unless: true, + 'with': true, + log: true, + lookup: true + }, options.knownHelpers); + + return this.accept(program); + }, + + compileProgram: function compileProgram(program) { + var childCompiler = new this.compiler(), + // eslint-disable-line new-cap + result = childCompiler.compile(program, this.options), + guid = this.guid++; + + this.usePartial = this.usePartial || result.usePartial; + + this.children[guid] = result; + this.useDepths = this.useDepths || result.useDepths; + + return guid; + }, + + accept: function accept(node) { + /* istanbul ignore next: Sanity code */ + if (!this[node.type]) { + throw new _exception2['default']('Unknown type: ' + node.type, node); + } + + this.sourceNode.unshift(node); + var ret = this[node.type](node); + this.sourceNode.shift(); + return ret; + }, + + Program: function Program(program) { + this.options.blockParams.unshift(program.blockParams); + + var body = program.body, + bodyLength = body.length; + for (var i = 0; i < bodyLength; i++) { + this.accept(body[i]); + } + + this.options.blockParams.shift(); + + this.isSimple = bodyLength === 1; + this.blockParams = program.blockParams ? program.blockParams.length : 0; + + return this; + }, + + BlockStatement: function BlockStatement(block) { + transformLiteralToPath(block); + + var program = block.program, + inverse = block.inverse; + + program = program && this.compileProgram(program); + inverse = inverse && this.compileProgram(inverse); + + var type = this.classifySexpr(block); + + if (type === 'helper') { + this.helperSexpr(block, program, inverse); + } else if (type === 'simple') { + this.simpleSexpr(block); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('blockValue', block.path.original); + } else { + this.ambiguousSexpr(block, program, inverse); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('ambiguousBlockValue'); + } + + this.opcode('append'); + }, + + DecoratorBlock: function DecoratorBlock(decorator) { + var program = decorator.program && this.compileProgram(decorator.program); + var params = this.setupFullMustacheParams(decorator, program, undefined), + path = decorator.path; + + this.useDecorators = true; + this.opcode('registerDecorator', params.length, path.original); + }, + + PartialStatement: function PartialStatement(partial) { + this.usePartial = true; + + var program = partial.program; + if (program) { + program = this.compileProgram(partial.program); + } + + var params = partial.params; + if (params.length > 1) { + throw new _exception2['default']('Unsupported number of partial arguments: ' + params.length, partial); + } else if (!params.length) { + if (this.options.explicitPartialContext) { + this.opcode('pushLiteral', 'undefined'); + } else { + params.push({ type: 'PathExpression', parts: [], depth: 0 }); + } + } + + var partialName = partial.name.original, + isDynamic = partial.name.type === 'SubExpression'; + if (isDynamic) { + this.accept(partial.name); + } + + this.setupFullMustacheParams(partial, program, undefined, true); + + var indent = partial.indent || ''; + if (this.options.preventIndent && indent) { + this.opcode('appendContent', indent); + indent = ''; + } + + this.opcode('invokePartial', isDynamic, partialName, indent); + this.opcode('append'); + }, + PartialBlockStatement: function PartialBlockStatement(partialBlock) { + this.PartialStatement(partialBlock); + }, + + MustacheStatement: function MustacheStatement(mustache) { + this.SubExpression(mustache); + + if (mustache.escaped && !this.options.noEscape) { + this.opcode('appendEscaped'); + } else { + this.opcode('append'); + } + }, + Decorator: function Decorator(decorator) { + this.DecoratorBlock(decorator); + }, + + ContentStatement: function ContentStatement(content) { + if (content.value) { + this.opcode('appendContent', content.value); + } + }, + + CommentStatement: function CommentStatement() {}, + + SubExpression: function SubExpression(sexpr) { + transformLiteralToPath(sexpr); + var type = this.classifySexpr(sexpr); + + if (type === 'simple') { + this.simpleSexpr(sexpr); + } else if (type === 'helper') { + this.helperSexpr(sexpr); + } else { + this.ambiguousSexpr(sexpr); + } + }, + ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) { + var path = sexpr.path, + name = path.parts[0], + isBlock = program != null || inverse != null; + + this.opcode('getContext', path.depth); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + path.strict = true; + this.accept(path); + + this.opcode('invokeAmbiguous', name, isBlock); + }, + + simpleSexpr: function simpleSexpr(sexpr) { + var path = sexpr.path; + path.strict = true; + this.accept(path); + this.opcode('resolvePossibleLambda'); + }, + + helperSexpr: function helperSexpr(sexpr, program, inverse) { + var params = this.setupFullMustacheParams(sexpr, program, inverse), + path = sexpr.path, + name = path.parts[0]; + + if (this.options.knownHelpers[name]) { + this.opcode('invokeKnownHelper', params.length, name); + } else if (this.options.knownHelpersOnly) { + throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr); + } else { + path.strict = true; + path.falsy = true; + + this.accept(path); + this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path)); + } + }, + + PathExpression: function PathExpression(path) { + this.addDepth(path.depth); + this.opcode('getContext', path.depth); + + var name = path.parts[0], + scoped = _ast2['default'].helpers.scopedId(path), + blockParamId = !path.depth && !scoped && this.blockParamIndex(name); + + if (blockParamId) { + this.opcode('lookupBlockParam', blockParamId, path.parts); + } else if (!name) { + // Context reference, i.e. `{{foo .}}` or `{{foo ..}}` + this.opcode('pushContext'); + } else if (path.data) { + this.options.data = true; + this.opcode('lookupData', path.depth, path.parts, path.strict); + } else { + this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped); + } + }, + + StringLiteral: function StringLiteral(string) { + this.opcode('pushString', string.value); + }, + + NumberLiteral: function NumberLiteral(number) { + this.opcode('pushLiteral', number.value); + }, + + BooleanLiteral: function BooleanLiteral(bool) { + this.opcode('pushLiteral', bool.value); + }, + + UndefinedLiteral: function UndefinedLiteral() { + this.opcode('pushLiteral', 'undefined'); + }, + + NullLiteral: function NullLiteral() { + this.opcode('pushLiteral', 'null'); + }, + + Hash: function Hash(hash) { + var pairs = hash.pairs, + i = 0, + l = pairs.length; + + this.opcode('pushHash'); + + for (; i < l; i++) { + this.pushParam(pairs[i].value); + } + while (i--) { + this.opcode('assignToHash', pairs[i].key); + } + this.opcode('popHash'); + }, + + // HELPERS + opcode: function opcode(name) { + this.opcodes.push({ + opcode: name, + args: slice.call(arguments, 1), + loc: this.sourceNode[0].loc + }); + }, + + addDepth: function addDepth(depth) { + if (!depth) { + return; + } + + this.useDepths = true; + }, + + classifySexpr: function classifySexpr(sexpr) { + var isSimple = _ast2['default'].helpers.simpleId(sexpr.path); + + var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var isHelper = !isBlockParam && _ast2['default'].helpers.helperExpression(sexpr); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. + var isEligible = !isBlockParam && (isHelper || isSimple); + + // if ambiguous, we can possibly resolve the ambiguity now + // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. + if (isEligible && !isHelper) { + var _name = sexpr.path.parts[0], + options = this.options; + if (options.knownHelpers[_name]) { + isHelper = true; + } else if (options.knownHelpersOnly) { + isEligible = false; + } + } + + if (isHelper) { + return 'helper'; + } else if (isEligible) { + return 'ambiguous'; + } else { + return 'simple'; + } + }, + + pushParams: function pushParams(params) { + for (var i = 0, l = params.length; i < l; i++) { + this.pushParam(params[i]); + } + }, + + pushParam: function pushParam(val) { + var value = val.value != null ? val.value : val.original || ''; + + if (this.stringParams) { + if (value.replace) { + value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.'); + } + + if (val.depth) { + this.addDepth(val.depth); + } + this.opcode('getContext', val.depth || 0); + this.opcode('pushStringParam', value, val.type); + + if (val.type === 'SubExpression') { + // SubExpressions get evaluated and passed in + // in string params mode. + this.accept(val); + } + } else { + if (this.trackIds) { + var blockParamIndex = undefined; + if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) { + blockParamIndex = this.blockParamIndex(val.parts[0]); + } + if (blockParamIndex) { + var blockParamChild = val.parts.slice(1).join('.'); + this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild); + } else { + value = val.original || value; + if (value.replace) { + value = value.replace(/^this(?:\.|$)/, '').replace(/^\.\//, '').replace(/^\.$/, ''); + } + + this.opcode('pushId', val.type, value); + } + } + this.accept(val); + } + }, + + setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) { + var params = sexpr.params; + this.pushParams(params); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + if (sexpr.hash) { + this.accept(sexpr.hash); + } else { + this.opcode('emptyHash', omitEmpty); + } + + return params; + }, + + blockParamIndex: function blockParamIndex(name) { + for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) { + var blockParams = this.options.blockParams[depth], + param = blockParams && _utils.indexOf(blockParams, name); + if (blockParams && param >= 0) { + return [depth, param]; + } + } + } + }; + + function precompile(input, options, env) { + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input); + } + + options = options || {}; + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options); + return new env.JavaScriptCompiler().compile(environment, options); + } + + function compile(input, options, env) { + if (options === undefined) options = {}; + + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input); + } + + options = _utils.extend({}, options); + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var compiled = undefined; + + function compileInput() { + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options), + templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true); + return env.template(templateSpec); + } + + // Template is only compiled on first use and cached after that point. + function ret(context, execOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled.call(this, context, execOptions); + } + ret._setup = function (setupOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._setup(setupOptions); + }; + ret._child = function (i, data, blockParams, depths) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._child(i, data, blockParams, depths); + }; + return ret; + } + + function argEquals(a, b) { + if (a === b) { + return true; + } + + if (_utils.isArray(a) && _utils.isArray(b) && a.length === b.length) { + for (var i = 0; i < a.length; i++) { + if (!argEquals(a[i], b[i])) { + return false; + } + } + return true; + } + } + + function transformLiteralToPath(sexpr) { + if (!sexpr.path.parts) { + var literal = sexpr.path; + // Casting to string here to make false and 0 literal values play nicely with the rest + // of the system. + sexpr.path = { + type: 'PathExpression', + data: false, + depth: 0, + parts: [literal.original + ''], + original: literal.original + '', + loc: literal.loc + }; + } + } + +/***/ }), +/* 52 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$keys = __webpack_require__(13)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _base = __webpack_require__(4); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _codeGen = __webpack_require__(53); + + var _codeGen2 = _interopRequireDefault(_codeGen); + + function Literal(value) { + this.value = value; + } + + function JavaScriptCompiler() {} + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function nameLookup(parent, name /*, type */) { + return this.internalNameLookup(parent, name); + }, + depthedLookup: function depthedLookup(name) { + return [this.aliasable('container.lookup'), '(depths, "', name, '")']; + }, + + compilerInfo: function compilerInfo() { + var revision = _base.COMPILER_REVISION, + versions = _base.REVISION_CHANGES[revision]; + return [revision, versions]; + }, + + appendToBuffer: function appendToBuffer(source, location, explicit) { + // Force a source as this simplifies the merge logic. + if (!_utils.isArray(source)) { + source = [source]; + } + source = this.source.wrap(source, location); + + if (this.environment.isSimple) { + return ['return ', source, ';']; + } else if (explicit) { + // This is a case where the buffer operation occurs as a child of another + // construct, generally braces. We have to explicitly output these buffer + // operations to ensure that the emitted code goes in the correct location. + return ['buffer += ', source, ';']; + } else { + source.appendToBuffer = true; + return source; + } + }, + + initializeBuffer: function initializeBuffer() { + return this.quotedString(''); + }, + // END PUBLIC API + internalNameLookup: function internalNameLookup(parent, name) { + this.lookupPropertyFunctionIsUsed = true; + return ['lookupProperty(', parent, ',', JSON.stringify(name), ')']; + }, + + lookupPropertyFunctionIsUsed: false, + + compile: function compile(environment, options, context, asObject) { + this.environment = environment; + this.options = options; + this.stringParams = this.options.stringParams; + this.trackIds = this.options.trackIds; + this.precompile = !asObject; + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + decorators: [], + programs: [], + environments: [] + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + this.aliases = {}; + this.registers = { list: [] }; + this.hashes = []; + this.compileStack = []; + this.inlineStack = []; + this.blockParams = []; + + this.compileChildren(environment, options); + + this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat; + this.useBlockParams = this.useBlockParams || environment.useBlockParams; + + var opcodes = environment.opcodes, + opcode = undefined, + firstLoc = undefined, + i = undefined, + l = undefined; + + for (i = 0, l = opcodes.length; i < l; i++) { + opcode = opcodes[i]; + + this.source.currentLocation = opcode.loc; + firstLoc = firstLoc || opcode.loc; + this[opcode.opcode].apply(this, opcode.args); + } + + // Flush any trailing content that might be pending. + this.source.currentLocation = firstLoc; + this.pushSource(''); + + /* istanbul ignore next */ + if (this.stackSlot || this.inlineStack.length || this.compileStack.length) { + throw new _exception2['default']('Compile completed with content left on stack'); + } + + if (!this.decorators.isEmpty()) { + this.useDecorators = true; + + this.decorators.prepend(['var decorators = container.decorators, ', this.lookupPropertyFunctionVarDeclaration(), ';\n']); + this.decorators.push('return fn;'); + + if (asObject) { + this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]); + } else { + this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n'); + this.decorators.push('}\n'); + this.decorators = this.decorators.merge(); + } + } else { + this.decorators = undefined; + } + + var fn = this.createFunctionContext(asObject); + if (!this.isChild) { + var ret = { + compiler: this.compilerInfo(), + main: fn + }; + + if (this.decorators) { + ret.main_d = this.decorators; // eslint-disable-line camelcase + ret.useDecorators = true; + } + + var _context = this.context; + var programs = _context.programs; + var decorators = _context.decorators; + + for (i = 0, l = programs.length; i < l; i++) { + if (programs[i]) { + ret[i] = programs[i]; + if (decorators[i]) { + ret[i + '_d'] = decorators[i]; + ret.useDecorators = true; + } + } + } + + if (this.environment.usePartial) { + ret.usePartial = true; + } + if (this.options.data) { + ret.useData = true; + } + if (this.useDepths) { + ret.useDepths = true; + } + if (this.useBlockParams) { + ret.useBlockParams = true; + } + if (this.options.compat) { + ret.compat = true; + } + + if (!asObject) { + ret.compiler = JSON.stringify(ret.compiler); + + this.source.currentLocation = { start: { line: 1, column: 0 } }; + ret = this.objectLiteral(ret); + + if (options.srcName) { + ret = ret.toStringWithSourceMap({ file: options.destName }); + ret.map = ret.map && ret.map.toString(); + } else { + ret = ret.toString(); + } + } else { + ret.compilerOptions = this.options; + } + + return ret; + } else { + return fn; + } + }, + + preamble: function preamble() { + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = new _codeGen2['default'](this.options.srcName); + this.decorators = new _codeGen2['default'](this.options.srcName); + }, + + createFunctionContext: function createFunctionContext(asObject) { + // istanbul ignore next + + var _this = this; + + var varDeclarations = ''; + + var locals = this.stackVars.concat(this.registers.list); + if (locals.length > 0) { + varDeclarations += ', ' + locals.join(', '); + } + + // Generate minimizer alias mappings + // + // When using true SourceNodes, this will update all references to the given alias + // as the source nodes are reused in situ. For the non-source node compilation mode, + // aliases will not be used, but this case is already being run on the client and + // we aren't concern about minimizing the template size. + var aliasCount = 0; + _Object$keys(this.aliases).forEach(function (alias) { + var node = _this.aliases[alias]; + if (node.children && node.referenceCount > 1) { + varDeclarations += ', alias' + ++aliasCount + '=' + alias; + node.children[0] = 'alias' + aliasCount; + } + }); + + if (this.lookupPropertyFunctionIsUsed) { + varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration(); + } + + var params = ['container', 'depth0', 'helpers', 'partials', 'data']; + + if (this.useBlockParams || this.useDepths) { + params.push('blockParams'); + } + if (this.useDepths) { + params.push('depths'); + } + + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(varDeclarations); + + if (asObject) { + params.push(source); + + return Function.apply(this, params); + } else { + return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']); + } + }, + mergeSource: function mergeSource(varDeclarations) { + var isSimple = this.environment.isSimple, + appendOnly = !this.forceBuffer, + appendFirst = undefined, + sourceSeen = undefined, + bufferStart = undefined, + bufferEnd = undefined; + this.source.each(function (line) { + if (line.appendToBuffer) { + if (bufferStart) { + line.prepend(' + '); + } else { + bufferStart = line; + } + bufferEnd = line; + } else { + if (bufferStart) { + if (!sourceSeen) { + appendFirst = true; + } else { + bufferStart.prepend('buffer += '); + } + bufferEnd.add(';'); + bufferStart = bufferEnd = undefined; + } + + sourceSeen = true; + if (!isSimple) { + appendOnly = false; + } + } + }); + + if (appendOnly) { + if (bufferStart) { + bufferStart.prepend('return '); + bufferEnd.add(';'); + } else if (!sourceSeen) { + this.source.push('return "";'); + } + } else { + varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer()); + + if (bufferStart) { + bufferStart.prepend('return buffer + '); + bufferEnd.add(';'); + } else { + this.source.push('return buffer;'); + } + } + + if (varDeclarations) { + this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')); + } + + return this.source.merge(); + }, + + lookupPropertyFunctionVarDeclaration: function lookupPropertyFunctionVarDeclaration() { + return '\n lookupProperty = container.lookupProperty || function(parent, propertyName) {\n if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {\n return parent[propertyName];\n }\n return undefined\n }\n '.trim(); + }, + + // [blockValue] + // + // On stack, before: hash, inverse, program, value + // On stack, after: return value of blockHelperMissing + // + // The purpose of this opcode is to take a block of the form + // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and + // replace it on the stack with the result of properly + // invoking blockHelperMissing. + blockValue: function blockValue(name) { + var blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs(name, 0, params); + + var blockName = this.popStack(); + params.splice(1, 0, blockName); + + this.push(this.source.functionCall(blockHelperMissing, 'call', params)); + }, + + // [ambiguousBlockValue] + // + // On stack, before: hash, inverse, program, value + // Compiler value, before: lastHelper=value of last found helper, if any + // On stack, after, if no lastHelper: same as [blockValue] + // On stack, after, if lastHelper: value + ambiguousBlockValue: function ambiguousBlockValue() { + // We're being a bit cheeky and reusing the options value from the prior exec + var blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs('', 0, params, true); + + this.flushInline(); + + var current = this.topStack(); + params.splice(1, 0, current); + + this.pushSource(['if (!', this.lastHelper, ') { ', current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), '}']); + }, + + // [appendContent] + // + // On stack, before: ... + // On stack, after: ... + // + // Appends the string value of `content` to the current buffer + appendContent: function appendContent(content) { + if (this.pendingContent) { + content = this.pendingContent + content; + } else { + this.pendingLocation = this.source.currentLocation; + } + + this.pendingContent = content; + }, + + // [append] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Coerces `value` to a String and appends it to the current buffer. + // + // If `value` is truthy, or 0, it is coerced into a string and appended + // Otherwise, the empty string is appended + append: function append() { + if (this.isInline()) { + this.replaceStack(function (current) { + return [' != null ? ', current, ' : ""']; + }); + + this.pushSource(this.appendToBuffer(this.popStack())); + } else { + var local = this.popStack(); + this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']); + if (this.environment.isSimple) { + this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']); + } + } + }, + + // [appendEscaped] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Escape `value` and append it to the buffer + appendEscaped: function appendEscaped() { + this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'), '(', this.popStack(), ')'])); + }, + + // [getContext] + // + // On stack, before: ... + // On stack, after: ... + // Compiler value, after: lastContext=depth + // + // Set the value of the `lastContext` compiler value to the depth + getContext: function getContext(depth) { + this.lastContext = depth; + }, + + // [pushContext] + // + // On stack, before: ... + // On stack, after: currentContext, ... + // + // Pushes the value of the current context onto the stack. + pushContext: function pushContext() { + this.pushStackLiteral(this.contextName(this.lastContext)); + }, + + // [lookupOnContext] + // + // On stack, before: ... + // On stack, after: currentContext[name], ... + // + // Looks up the value of `name` on the current context and pushes + // it onto the stack. + lookupOnContext: function lookupOnContext(parts, falsy, strict, scoped) { + var i = 0; + + if (!scoped && this.options.compat && !this.lastContext) { + // The depthed query is expected to handle the undefined logic for the root level that + // is implemented below, so we evaluate that directly in compat mode + this.push(this.depthedLookup(parts[i++])); + } else { + this.pushContext(); + } + + this.resolvePath('context', parts, i, falsy, strict); + }, + + // [lookupBlockParam] + // + // On stack, before: ... + // On stack, after: blockParam[name], ... + // + // Looks up the value of `parts` on the given block param and pushes + // it onto the stack. + lookupBlockParam: function lookupBlockParam(blockParamId, parts) { + this.useBlockParams = true; + + this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']); + this.resolvePath('context', parts, 1); + }, + + // [lookupData] + // + // On stack, before: ... + // On stack, after: data, ... + // + // Push the data lookup operator + lookupData: function lookupData(depth, parts, strict) { + if (!depth) { + this.pushStackLiteral('data'); + } else { + this.pushStackLiteral('container.data(data, ' + depth + ')'); + } + + this.resolvePath('data', parts, 0, true, strict); + }, + + resolvePath: function resolvePath(type, parts, i, falsy, strict) { + // istanbul ignore next + + var _this2 = this; + + if (this.options.strict || this.options.assumeObjects) { + this.push(strictLookup(this.options.strict && strict, this, parts, type)); + return; + } + + var len = parts.length; + for (; i < len; i++) { + /* eslint-disable no-loop-func */ + this.replaceStack(function (current) { + var lookup = _this2.nameLookup(current, parts[i], type); + // We want to ensure that zero and false are handled properly if the context (falsy flag) + // needs to have the special handling for these values. + if (!falsy) { + return [' != null ? ', lookup, ' : ', current]; + } else { + // Otherwise we can use generic falsy handling + return [' && ', lookup]; + } + }); + /* eslint-enable no-loop-func */ + } + }, + + // [resolvePossibleLambda] + // + // On stack, before: value, ... + // On stack, after: resolved value, ... + // + // If the `value` is a lambda, replace it on the stack by + // the return value of the lambda + resolvePossibleLambda: function resolvePossibleLambda() { + this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']); + }, + + // [pushStringParam] + // + // On stack, before: ... + // On stack, after: string, currentContext, ... + // + // This opcode is designed for use in string mode, which + // provides the string value of a parameter along with its + // depth rather than resolving it immediately. + pushStringParam: function pushStringParam(string, type) { + this.pushContext(); + this.pushString(type); + + // If it's a subexpression, the string result + // will be pushed after this opcode. + if (type !== 'SubExpression') { + if (typeof string === 'string') { + this.pushString(string); + } else { + this.pushStackLiteral(string); + } + } + }, + + emptyHash: function emptyHash(omitEmpty) { + if (this.trackIds) { + this.push('{}'); // hashIds + } + if (this.stringParams) { + this.push('{}'); // hashContexts + this.push('{}'); // hashTypes + } + this.pushStackLiteral(omitEmpty ? 'undefined' : '{}'); + }, + pushHash: function pushHash() { + if (this.hash) { + this.hashes.push(this.hash); + } + this.hash = { values: {}, types: [], contexts: [], ids: [] }; + }, + popHash: function popHash() { + var hash = this.hash; + this.hash = this.hashes.pop(); + + if (this.trackIds) { + this.push(this.objectLiteral(hash.ids)); + } + if (this.stringParams) { + this.push(this.objectLiteral(hash.contexts)); + this.push(this.objectLiteral(hash.types)); + } + + this.push(this.objectLiteral(hash.values)); + }, + + // [pushString] + // + // On stack, before: ... + // On stack, after: quotedString(string), ... + // + // Push a quoted version of `string` onto the stack + pushString: function pushString(string) { + this.pushStackLiteral(this.quotedString(string)); + }, + + // [pushLiteral] + // + // On stack, before: ... + // On stack, after: value, ... + // + // Pushes a value onto the stack. This operation prevents + // the compiler from creating a temporary variable to hold + // it. + pushLiteral: function pushLiteral(value) { + this.pushStackLiteral(value); + }, + + // [pushProgram] + // + // On stack, before: ... + // On stack, after: program(guid), ... + // + // Push a program expression onto the stack. This takes + // a compile-time guid and converts it into a runtime-accessible + // expression. + pushProgram: function pushProgram(guid) { + if (guid != null) { + this.pushStackLiteral(this.programExpression(guid)); + } else { + this.pushStackLiteral(null); + } + }, + + // [registerDecorator] + // + // On stack, before: hash, program, params..., ... + // On stack, after: ... + // + // Pops off the decorator's parameters, invokes the decorator, + // and inserts the decorator into the decorators list. + registerDecorator: function registerDecorator(paramSize, name) { + var foundDecorator = this.nameLookup('decorators', name, 'decorator'), + options = this.setupHelperArgs(name, paramSize); + + this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']); + }, + + // [invokeHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // Pops off the helper's parameters, invokes the helper, + // and pushes the helper's return value onto the stack. + // + // If the helper is not found, `helperMissing` is called. + invokeHelper: function invokeHelper(paramSize, name, isSimple) { + var nonHelper = this.popStack(), + helper = this.setupHelper(paramSize, name); + + var possibleFunctionCalls = []; + + if (isSimple) { + // direct call to helper + possibleFunctionCalls.push(helper.name); + } + // call a function from the input object + possibleFunctionCalls.push(nonHelper); + if (!this.options.strict) { + possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing')); + } + + var functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')']; + var functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams); + this.push(functionCall); + }, + + itemsSeparatedBy: function itemsSeparatedBy(items, separator) { + var result = []; + result.push(items[0]); + for (var i = 1; i < items.length; i++) { + result.push(separator, items[i]); + } + return result; + }, + // [invokeKnownHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // This operation is used when the helper is known to exist, + // so a `helperMissing` fallback is not required. + invokeKnownHelper: function invokeKnownHelper(paramSize, name) { + var helper = this.setupHelper(paramSize, name); + this.push(this.source.functionCall(helper.name, 'call', helper.callParams)); + }, + + // [invokeAmbiguous] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of disambiguation + // + // This operation is used when an expression like `{{foo}}` + // is provided, but we don't know at compile-time whether it + // is a helper or a path. + // + // This operation emits more code than the other options, + // and can be avoided by passing the `knownHelpers` and + // `knownHelpersOnly` flags at compile-time. + invokeAmbiguous: function invokeAmbiguous(name, helperCall) { + this.useRegister('helper'); + + var nonHelper = this.popStack(); + + this.emptyHash(); + var helper = this.setupHelper(0, name, helperCall); + + var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); + + var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; + if (!this.options.strict) { + lookup[0] = '(helper = '; + lookup.push(' != null ? helper : ', this.aliasable('container.hooks.helperMissing')); + } + + this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']); + }, + + // [invokePartial] + // + // On stack, before: context, ... + // On stack after: result of partial invocation + // + // This operation pops off a context, invokes a partial with that context, + // and pushes the result of the invocation back. + invokePartial: function invokePartial(isDynamic, name, indent) { + var params = [], + options = this.setupParams(name, 1, params); + + if (isDynamic) { + name = this.popStack(); + delete options.name; + } + + if (indent) { + options.indent = JSON.stringify(indent); + } + options.helpers = 'helpers'; + options.partials = 'partials'; + options.decorators = 'container.decorators'; + + if (!isDynamic) { + params.unshift(this.nameLookup('partials', name, 'partial')); + } else { + params.unshift(name); + } + + if (this.options.compat) { + options.depths = 'depths'; + } + options = this.objectLiteral(options); + params.push(options); + + this.push(this.source.functionCall('container.invokePartial', '', params)); + }, + + // [assignToHash] + // + // On stack, before: value, ..., hash, ... + // On stack, after: ..., hash, ... + // + // Pops a value off the stack and assigns it to the current hash + assignToHash: function assignToHash(key) { + var value = this.popStack(), + context = undefined, + type = undefined, + id = undefined; + + if (this.trackIds) { + id = this.popStack(); + } + if (this.stringParams) { + type = this.popStack(); + context = this.popStack(); + } + + var hash = this.hash; + if (context) { + hash.contexts[key] = context; + } + if (type) { + hash.types[key] = type; + } + if (id) { + hash.ids[key] = id; + } + hash.values[key] = value; + }, + + pushId: function pushId(type, name, child) { + if (type === 'BlockParam') { + this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : '')); + } else if (type === 'PathExpression') { + this.pushString(name); + } else if (type === 'SubExpression') { + this.pushStackLiteral('true'); + } else { + this.pushStackLiteral('null'); + } + }, + + // HELPERS + + compiler: JavaScriptCompiler, + + compileChildren: function compileChildren(environment, options) { + var children = environment.children, + child = undefined, + compiler = undefined; + + for (var i = 0, l = children.length; i < l; i++) { + child = children[i]; + compiler = new this.compiler(); // eslint-disable-line new-cap + + var existing = this.matchExistingProgram(child); + + if (existing == null) { + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + var index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile); + this.context.decorators[index] = compiler.decorators; + this.context.environments[index] = child; + + this.useDepths = this.useDepths || compiler.useDepths; + this.useBlockParams = this.useBlockParams || compiler.useBlockParams; + child.useDepths = this.useDepths; + child.useBlockParams = this.useBlockParams; + } else { + child.index = existing.index; + child.name = 'program' + existing.index; + + this.useDepths = this.useDepths || existing.useDepths; + this.useBlockParams = this.useBlockParams || existing.useBlockParams; + } + } + }, + matchExistingProgram: function matchExistingProgram(child) { + for (var i = 0, len = this.context.environments.length; i < len; i++) { + var environment = this.context.environments[i]; + if (environment && environment.equals(child)) { + return environment; + } + } + }, + + programExpression: function programExpression(guid) { + var child = this.environment.children[guid], + programParams = [child.index, 'data', child.blockParams]; + + if (this.useBlockParams || this.useDepths) { + programParams.push('blockParams'); + } + if (this.useDepths) { + programParams.push('depths'); + } + + return 'container.program(' + programParams.join(', ') + ')'; + }, + + useRegister: function useRegister(name) { + if (!this.registers[name]) { + this.registers[name] = true; + this.registers.list.push(name); + } + }, + + push: function push(expr) { + if (!(expr instanceof Literal)) { + expr = this.source.wrap(expr); + } + + this.inlineStack.push(expr); + return expr; + }, + + pushStackLiteral: function pushStackLiteral(item) { + this.push(new Literal(item)); + }, + + pushSource: function pushSource(source) { + if (this.pendingContent) { + this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)); + this.pendingContent = undefined; + } + + if (source) { + this.source.push(source); + } + }, + + replaceStack: function replaceStack(callback) { + var prefix = ['('], + stack = undefined, + createdStack = undefined, + usedLiteral = undefined; + + /* istanbul ignore next */ + if (!this.isInline()) { + throw new _exception2['default']('replaceStack on non-inline'); + } + + // We want to merge the inline statement into the replacement statement via ',' + var top = this.popStack(true); + + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = [top.value]; + prefix = ['(', stack]; + usedLiteral = true; + } else { + // Get or create the current stack name for use by the inline + createdStack = true; + var _name = this.incrStack(); + + prefix = ['((', this.push(_name), ' = ', top, ')']; + stack = this.topStack(); + } + + var item = callback.call(this, stack); + + if (!usedLiteral) { + this.popStack(); + } + if (createdStack) { + this.stackSlot--; + } + this.push(prefix.concat(item, ')')); + }, + + incrStack: function incrStack() { + this.stackSlot++; + if (this.stackSlot > this.stackVars.length) { + this.stackVars.push('stack' + this.stackSlot); + } + return this.topStackName(); + }, + topStackName: function topStackName() { + return 'stack' + this.stackSlot; + }, + flushInline: function flushInline() { + var inlineStack = this.inlineStack; + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + /* istanbul ignore if */ + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + var stack = this.incrStack(); + this.pushSource([stack, ' = ', entry, ';']); + this.compileStack.push(stack); + } + } + }, + isInline: function isInline() { + return this.inlineStack.length; + }, + + popStack: function popStack(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && item instanceof Literal) { + return item.value; + } else { + if (!inline) { + /* istanbul ignore next */ + if (!this.stackSlot) { + throw new _exception2['default']('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function topStack() { + var stack = this.isInline() ? this.inlineStack : this.compileStack, + item = stack[stack.length - 1]; + + /* istanbul ignore if */ + if (item instanceof Literal) { + return item.value; + } else { + return item; + } + }, + + contextName: function contextName(context) { + if (this.useDepths && context) { + return 'depths[' + context + ']'; + } else { + return 'depth' + context; + } + }, + + quotedString: function quotedString(str) { + return this.source.quotedString(str); + }, + + objectLiteral: function objectLiteral(obj) { + return this.source.objectLiteral(obj); + }, + + aliasable: function aliasable(name) { + var ret = this.aliases[name]; + if (ret) { + ret.referenceCount++; + return ret; + } + + ret = this.aliases[name] = this.source.wrap(name); + ret.aliasable = true; + ret.referenceCount = 1; + + return ret; + }, + + setupHelper: function setupHelper(paramSize, name, blockHelper) { + var params = [], + paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); + var foundHelper = this.nameLookup('helpers', name, 'helper'), + callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : (container.nullContext || {})'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: [callContext].concat(params) + }; + }, + + setupParams: function setupParams(helper, paramSize, params) { + var options = {}, + contexts = [], + types = [], + ids = [], + objectArgs = !params, + param = undefined; + + if (objectArgs) { + params = []; + } + + options.name = this.quotedString(helper); + options.hash = this.popStack(); + + if (this.trackIds) { + options.hashIds = this.popStack(); + } + if (this.stringParams) { + options.hashTypes = this.popStack(); + options.hashContexts = this.popStack(); + } + + var inverse = this.popStack(), + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + options.fn = program || 'container.noop'; + options.inverse = inverse || 'container.noop'; + } + + // The parameters go on to the stack in order (making sure that they are evaluated in order) + // so we need to pop them off the stack in reverse order + var i = paramSize; + while (i--) { + param = this.popStack(); + params[i] = param; + + if (this.trackIds) { + ids[i] = this.popStack(); + } + if (this.stringParams) { + types[i] = this.popStack(); + contexts[i] = this.popStack(); + } + } + + if (objectArgs) { + options.args = this.source.generateArray(params); + } + + if (this.trackIds) { + options.ids = this.source.generateArray(ids); + } + if (this.stringParams) { + options.types = this.source.generateArray(types); + options.contexts = this.source.generateArray(contexts); + } + + if (this.options.data) { + options.data = 'data'; + } + if (this.useBlockParams) { + options.blockParams = 'blockParams'; + } + return options; + }, + + setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) { + var options = this.setupParams(helper, paramSize, params); + options.loc = JSON.stringify(this.source.currentLocation); + options = this.objectLiteral(options); + if (useRegister) { + this.useRegister('options'); + params.push('options'); + return ['options=', options]; + } else if (params) { + params.push(options); + return ''; + } else { + return options; + } + } + }; + + (function () { + var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' '); + + var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for (var i = 0, l = reservedWords.length; i < l; i++) { + compilerWords[reservedWords[i]] = true; + } + })(); + + /** + * @deprecated May be removed in the next major version + */ + JavaScriptCompiler.isValidJavaScriptVariableName = function (name) { + return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name); + }; + + function strictLookup(requireTerminal, compiler, parts, type) { + var stack = compiler.popStack(), + i = 0, + len = parts.length; + if (requireTerminal) { + len--; + } + + for (; i < len; i++) { + stack = compiler.nameLookup(stack, parts[i], type); + } + + if (requireTerminal) { + return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ', ', JSON.stringify(compiler.source.currentLocation), ' )']; + } else { + return stack; + } + } + + exports['default'] = JavaScriptCompiler; + module.exports = exports['default']; + +/***/ }), +/* 53 */ +/***/ (function(module, exports, __webpack_require__) { + + /* global define */ + 'use strict'; + + var _Object$keys = __webpack_require__(13)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var SourceNode = undefined; + + try { + /* istanbul ignore next */ + if (false) { + // We don't support this in AMD environments. For these environments, we asusme that + // they are running on the browser and thus have no need for the source-map library. + var SourceMap = require('source-map'); + SourceNode = SourceMap.SourceNode; + } + } catch (err) {} + /* NOP */ + + /* istanbul ignore if: tested but not covered in istanbul due to dist build */ + if (!SourceNode) { + SourceNode = function (line, column, srcFile, chunks) { + this.src = ''; + if (chunks) { + this.add(chunks); + } + }; + /* istanbul ignore next */ + SourceNode.prototype = { + add: function add(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src += chunks; + }, + prepend: function prepend(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src = chunks + this.src; + }, + toStringWithSourceMap: function toStringWithSourceMap() { + return { code: this.toString() }; + }, + toString: function toString() { + return this.src; + } + }; + } + + function castChunk(chunk, codeGen, loc) { + if (_utils.isArray(chunk)) { + var ret = []; + + for (var i = 0, len = chunk.length; i < len; i++) { + ret.push(codeGen.wrap(chunk[i], loc)); + } + return ret; + } else if (typeof chunk === 'boolean' || typeof chunk === 'number') { + // Handle primitives that the SourceNode will throw up on + return chunk + ''; + } + return chunk; + } + + function CodeGen(srcFile) { + this.srcFile = srcFile; + this.source = []; + } + + CodeGen.prototype = { + isEmpty: function isEmpty() { + return !this.source.length; + }, + prepend: function prepend(source, loc) { + this.source.unshift(this.wrap(source, loc)); + }, + push: function push(source, loc) { + this.source.push(this.wrap(source, loc)); + }, + + merge: function merge() { + var source = this.empty(); + this.each(function (line) { + source.add([' ', line, '\n']); + }); + return source; + }, + + each: function each(iter) { + for (var i = 0, len = this.source.length; i < len; i++) { + iter(this.source[i]); + } + }, + + empty: function empty() { + var loc = this.currentLocation || { start: {} }; + return new SourceNode(loc.start.line, loc.start.column, this.srcFile); + }, + wrap: function wrap(chunk) { + var loc = arguments.length <= 1 || arguments[1] === undefined ? this.currentLocation || { start: {} } : arguments[1]; + + if (chunk instanceof SourceNode) { + return chunk; + } + + chunk = castChunk(chunk, this, loc); + + return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk); + }, + + functionCall: function functionCall(fn, type, params) { + params = this.generateList(params); + return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']); + }, + + quotedString: function quotedString(str) { + return '"' + (str + '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + objectLiteral: function objectLiteral(obj) { + // istanbul ignore next + + var _this = this; + + var pairs = []; + + _Object$keys(obj).forEach(function (key) { + var value = castChunk(obj[key], _this); + if (value !== 'undefined') { + pairs.push([_this.quotedString(key), ':', value]); + } + }); + + var ret = this.generateList(pairs); + ret.prepend('{'); + ret.add('}'); + return ret; + }, + + generateList: function generateList(entries) { + var ret = this.empty(); + + for (var i = 0, len = entries.length; i < len; i++) { + if (i) { + ret.add(','); + } + + ret.add(castChunk(entries[i], this)); + } + + return ret; + }, + + generateArray: function generateArray(entries) { + var ret = this.generateList(entries); + ret.prepend('['); + ret.add(']'); + + return ret; + } + }; + + exports['default'] = CodeGen; + module.exports = exports['default']; + +/***/ }) +/******/ ]) +}); +; \ No newline at end of file diff --git a/www/test/1.8.0/test/lib/morphdom-umd.js b/www/test/1.8.0/test/lib/morphdom-umd.js new file mode 100644 index 000000000..21fdc3a9a --- /dev/null +++ b/www/test/1.8.0/test/lib/morphdom-umd.js @@ -0,0 +1,763 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.morphdom = factory()); +}(this, function () { 'use strict'; + + var DOCUMENT_FRAGMENT_NODE = 11; + + function morphAttrs(fromNode, toNode) { + var toNodeAttrs = toNode.attributes; + var attr; + var attrName; + var attrNamespaceURI; + var attrValue; + var fromValue; + + // document-fragments dont have attributes so lets not do anything + if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) { + return; + } + + // update attributes on original DOM element + for (var i = toNodeAttrs.length - 1; i >= 0; i--) { + attr = toNodeAttrs[i]; + attrName = attr.name; + attrNamespaceURI = attr.namespaceURI; + attrValue = attr.value; + + if (attrNamespaceURI) { + attrName = attr.localName || attrName; + fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName); + + if (fromValue !== attrValue) { + if (attr.prefix === 'xmlns'){ + attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix + } + fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue); + } + } else { + fromValue = fromNode.getAttribute(attrName); + + if (fromValue !== attrValue) { + fromNode.setAttribute(attrName, attrValue); + } + } + } + + // Remove any extra attributes found on the original DOM element that + // weren't found on the target element. + var fromNodeAttrs = fromNode.attributes; + + for (var d = fromNodeAttrs.length - 1; d >= 0; d--) { + attr = fromNodeAttrs[d]; + attrName = attr.name; + attrNamespaceURI = attr.namespaceURI; + + if (attrNamespaceURI) { + attrName = attr.localName || attrName; + + if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) { + fromNode.removeAttributeNS(attrNamespaceURI, attrName); + } + } else { + if (!toNode.hasAttribute(attrName)) { + fromNode.removeAttribute(attrName); + } + } + } + } + + var range; // Create a range object for efficently rendering strings to elements. + var NS_XHTML = 'http://www.w3.org/1999/xhtml'; + + var doc = typeof document === 'undefined' ? undefined : document; + var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template'); + var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange(); + + function createFragmentFromTemplate(str) { + var template = doc.createElement('template'); + template.innerHTML = str; + return template.content.childNodes[0]; + } + + function createFragmentFromRange(str) { + if (!range) { + range = doc.createRange(); + range.selectNode(doc.body); + } + + var fragment = range.createContextualFragment(str); + return fragment.childNodes[0]; + } + + function createFragmentFromWrap(str) { + var fragment = doc.createElement('body'); + fragment.innerHTML = str; + return fragment.childNodes[0]; + } + + /** + * This is about the same + * var html = new DOMParser().parseFromString(str, 'text/html'); + * return html.body.firstChild; + * + * @method toElement + * @param {String} str + */ + function toElement(str) { + str = str.trim(); + if (HAS_TEMPLATE_SUPPORT) { + // avoid restrictions on content for things like `Hi` which + // createContextualFragment doesn't support + //