From 72cc3f95c945d0e8c399640c4d28e1645628f298 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 6 Jan 2022 17:08:07 +0800 Subject: [PATCH 01/36] Initial documentation of framework rewrite --- jsframework-introduction.md | 47 +++++++++++++++++ jsframework-migration-guide.md | 93 ++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 jsframework-introduction.md create mode 100644 jsframework-migration-guide.md diff --git a/jsframework-introduction.md b/jsframework-introduction.md new file mode 100644 index 00000000..b2bc21e2 --- /dev/null +++ b/jsframework-introduction.md @@ -0,0 +1,47 @@ +# Winter JavaScript Framework + +- [Introduction](#introduction) +- [Features](#features) +- [Including the framework](#framework-script) + + +## Introduction + +Winter includes a new, *optional*, JavaScript framework, which acts as an upgrade to the previous [AJAX Framework](../ajax/introduction) and provides many new useful features in an extensible fashion, whilst dropping previous hard dependencies to supercharge your projects even further. The framework takes advantage of the incredible enhancements made to the JavaScript ecosystem in recent years to provide a unique experience available only on Winter. + + +## Features + +- Rewritten AJAX and JavaScript framework, built from the ground-up using the latest JavaScript syntax (ES2015+) and functionality. +- jQuery is no longer a "hard" dependency, allowing the framework to be used across a wide varierty of JavaScript frameworks. +- Easy, comprehensive extensibility and event handling in-built into the framework. + + +## Including the framework + +> Before proceeding, please read the [Migration Guide](../jsframework/migration-guide), especially if you intend to use this framework on an existing project. + +The JavaScript framework is optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% winterjs %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts). This instructs Winter to include the framework assets at this point. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. + +By default, this tag just loads the base framework, which *does not* include the AJAX functionality, to allow people to completely customise the AJAX functionality if they wish. + +You can specify further attributes to the tag to include optional additional functionality for the framework: + +Attribute | Includes +--------- | -------- +`request` | The base [JavaScript AJAX](../jsframework/request) request functionality +`attr` | The [HTML data attribute](../jsframework/data-attr-ajax) request functionality +`extras` | [Several useful UI enhancements](../jsframework/extras), including popups, flash messages, loading states and transitions. +`all` | Includes everything above + +For example, to include the framework with just the JavaScript AJAX request functionality, the tag would be: + +```twig +{% winterjs request %} +``` + +Or to include both the JavaScript AJAX and HTML data attribute request functionality: + +```twig +{% winterjs request attr %} +``` \ No newline at end of file diff --git a/jsframework-migration-guide.md b/jsframework-migration-guide.md new file mode 100644 index 00000000..18103a3e --- /dev/null +++ b/jsframework-migration-guide.md @@ -0,0 +1,93 @@ +# Migration Guide + +- [Introduction](#introduction) +- [Breaking changes](#breaking-changes) + - [Browser support is more strict](#browser-support) + - [jQuery is no longer required](#no-jquery) + - [JavaScript in the HTML data attribute framework is deprecated](#html-callbacks) + - [AJAX events are triggered as DOM events](#ajax-dom-events) +- [Other changes](#other-changes) + - [JavaScript AJAX Requests](#js-requests) + + +## Introduction + +While care has been given to ensure that the new Winter JavaScript framework covers the entire scope of functionality that the original [AJAX framework](../ajax/introduction) provided, there are subtle differences between the two frameworks. Please take the time to read through this document to ensure that you are across the changes, especially if you intend to upgrade an existing project to use this new framework. + + +## Breaking changes + + +### Browser support is more strict + +The new framework drops support for Internet Explorer, as well as some less-used, or discontinued, browsers such as the Samsung Internet Browser and Opera Mini. The framework targets, at the very least, support for the ECMAScript 2015 (ES2015) JavaScript language. + +Our build script is set up to consider the following browsers as compatible with the framework: + +- The browser must have at least a 0.5% market share. +- The browser must be within the last 4 released versions of that browser. +- The browser must NOT be Internet Explorer. +- The browser must NOT be discontinued by the developer. + +For people who wish to support older browsers such as Internet Explorer, you may continue to use the original [AJAX framework](../ajax/introduction), which is still supported by the Winter maintainer team, but will likely not be receiving any new features going forward. + + +### jQuery is no longer required + +We have removed the hard dependency with jQuery, which also means that no jQuery functionality exists in this new framework. If you relied on jQuery being available for your own JavaScript functionality, you must include jQuery yourself in your theme. + + +### JavaScript in the HTML data attribute framework is deprecated + +The original [AJAX framework](../ajax/attributes-api#data-attributes) allowed for arbitrary JavaScript code to be specified within the callback data attributes, for example, `data-request-success`, `data-request-error` and `data-request-complete`, as a way of allowing JavaScript to run additional tasks depending on the success or failure of an AJAX request made through the HTML data attributes. + +We have dropped support of this feature due to its use of the `eval()` method in JavaScript to execute this JavaScript, which has security implications (especially on front-facing code) and prevents people from using content security policies on their sites without the use of the `unsafe-eval` [CSP rule](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). + +If you wish to use JavaScript with the AJAX functionality, you must either use the [JavaScript Request functionality](../jsframework/request), or use the original [AJAX framework](../ajax/introduction) which retains this feature. + + +### AJAX events are triggered as DOM events + +Previously, the original AJAX framework used jQuery's Event system to trigger events on elements that are affected by an AJAX request. As jQuery is no longer used, we now use DOM events in their place. + +This change requires us to provide event data as properties of the DOM event, not as handler parameters. + +For example, the `ajaxAlways` event which is triggered on an element when an AJAX request is triggered on an element could have a listener set up through jQuery as follows: + +```js +$('#element').on('ajaxAlways', function (event, context, data, status, xhr) { + console.log(context); // The Request's context + console.log(data); // Data returned from the AJAX response +}); +``` + +Now, you must look at the Event object properties for this information: + +```js +$('#element').on('ajaxAlways', function (event) { + console.log(event.request); // The Request object + console.log(event.responseData); // Data returned from the AJAX response +}); +``` + +Please review the [JavaScript Request](../jsframework/requests) documentation for information on what properties are available for DOM events. + + +## Other changes + + +### JavaScript AJAX Requests + +#### Making a request + +The original framework used a jQuery extension to call AJAX requests via JavaScript: + +```js +$('#element').request('onAjaxHandler') +``` + +This is now changed to use the base Winter class to call the Request extension: + +```js +Winter.request('#element', 'onAjaxHandler'); +``` \ No newline at end of file From e59f4a78446ddb6f6a008624e40b6fbbb78013aa Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 10 Jan 2022 15:00:43 +0800 Subject: [PATCH 02/36] Snowboard documentation --- config/toc-docs.yaml | 11 ++++ jsframework-introduction.md | 47 --------------- snowboard-introduction.md | 50 ++++++++++++++++ ...n-guide.md => snowboard-migration-guide.md | 10 ++-- snowboard-request.md | 59 +++++++++++++++++++ 5 files changed, 125 insertions(+), 52 deletions(-) delete mode 100644 jsframework-introduction.md create mode 100644 snowboard-introduction.md rename jsframework-migration-guide.md => snowboard-migration-guide.md (78%) create mode 100644 snowboard-request.md diff --git a/config/toc-docs.yaml b/config/toc-docs.yaml index 23619f9a..527e0b9a 100644 --- a/config/toc-docs.yaml +++ b/config/toc-docs.yaml @@ -66,6 +66,17 @@ Plugins: ajax/javascript-api: "JavaScript API" ajax/extras: "Extra Features" +Snowboard: + icon: "icon-bolt" + pages: + snowboard/introduction: "Introduction" + snowboard/migration-guide: "Migration Guide" + snowboard/request: "AJAX Requests (JavaScript API)" + snowboard/data-attr: "AJAX Requests (Data Attributes API)" + snowboard/extras: "Extra Features" + snowboard/plugin-development: "Plugin Development" + snowboard/event-handling: "Event Handling" + Database: icon: "icon-hdd" pages: diff --git a/jsframework-introduction.md b/jsframework-introduction.md deleted file mode 100644 index b2bc21e2..00000000 --- a/jsframework-introduction.md +++ /dev/null @@ -1,47 +0,0 @@ -# Winter JavaScript Framework - -- [Introduction](#introduction) -- [Features](#features) -- [Including the framework](#framework-script) - - -## Introduction - -Winter includes a new, *optional*, JavaScript framework, which acts as an upgrade to the previous [AJAX Framework](../ajax/introduction) and provides many new useful features in an extensible fashion, whilst dropping previous hard dependencies to supercharge your projects even further. The framework takes advantage of the incredible enhancements made to the JavaScript ecosystem in recent years to provide a unique experience available only on Winter. - - -## Features - -- Rewritten AJAX and JavaScript framework, built from the ground-up using the latest JavaScript syntax (ES2015+) and functionality. -- jQuery is no longer a "hard" dependency, allowing the framework to be used across a wide varierty of JavaScript frameworks. -- Easy, comprehensive extensibility and event handling in-built into the framework. - - -## Including the framework - -> Before proceeding, please read the [Migration Guide](../jsframework/migration-guide), especially if you intend to use this framework on an existing project. - -The JavaScript framework is optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% winterjs %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts). This instructs Winter to include the framework assets at this point. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. - -By default, this tag just loads the base framework, which *does not* include the AJAX functionality, to allow people to completely customise the AJAX functionality if they wish. - -You can specify further attributes to the tag to include optional additional functionality for the framework: - -Attribute | Includes ---------- | -------- -`request` | The base [JavaScript AJAX](../jsframework/request) request functionality -`attr` | The [HTML data attribute](../jsframework/data-attr-ajax) request functionality -`extras` | [Several useful UI enhancements](../jsframework/extras), including popups, flash messages, loading states and transitions. -`all` | Includes everything above - -For example, to include the framework with just the JavaScript AJAX request functionality, the tag would be: - -```twig -{% winterjs request %} -``` - -Or to include both the JavaScript AJAX and HTML data attribute request functionality: - -```twig -{% winterjs request attr %} -``` \ No newline at end of file diff --git a/snowboard-introduction.md b/snowboard-introduction.md new file mode 100644 index 00000000..3deea03d --- /dev/null +++ b/snowboard-introduction.md @@ -0,0 +1,50 @@ +# Winter JavaScript Framework (Snowboard) + +- [Introduction](#introduction) +- [Features](#features) +- [Including the framework](#framework-script) + + +## Introduction + +Winter includes a new, *optional*, JavaScript framework called **Snowboard**, which acts as an upgrade to the previous [AJAX Framework](../ajax/introduction) and provides many new useful features in an extensible fashion, whilst dropping previous hard dependencies to supercharge your projects even further. + +The framework takes advantage of the incredible enhancements made to the JavaScript ecosystem in recent years to provide a unique experience, available only on Winter. + + +## Features + +- Rewritten AJAX and JavaScript framework, built from the ground-up using the latest JavaScript syntax (ES2015+) and functionality. +- jQuery is no longer a "hard" dependency, allowing the framework to be used across a wide varierty of JavaScript frameworks. +- Easy, comprehensive extensibility and event handling in-built into the framework. +- Small footprint and full control over which core functionalities to include ensures your website loads quick. + + +## Including the framework + +> Before proceeding, please read the [Migration Guide](../snowboard/migration-guide), especially if you intend to use this framework on an existing project. + +Snowboard is optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. + +By default, this tag just loads the base framework and does not include any of the additional functionality, such as the AJAX framework, in order to allow usage of Snowboard without any clutter. + +You can then specify further attributes to the tag to include optional additional functionality for the framework: + +Attribute | Includes +--------- | -------- +`request` | The base [JavaScript AJAX](../snowboard/request) request functionality +`attr` | The [HTML data attribute](../snowboard/data-attr) request functionality +`extras` | [Several useful UI enhancements](../snowboard/extras), including flash messages, loading states and transitions. +`all` | Includes everything above + +For example, to include the framework with just the JavaScript AJAX request functionality, the tag would be: + +```twig +{% snowboard request %} +``` + +Or to include both the JavaScript AJAX and HTML data attribute request functionality: + +```twig +{% snowboard request attr %} +``` \ No newline at end of file diff --git a/jsframework-migration-guide.md b/snowboard-migration-guide.md similarity index 78% rename from jsframework-migration-guide.md rename to snowboard-migration-guide.md index 18103a3e..8a5da875 100644 --- a/jsframework-migration-guide.md +++ b/snowboard-migration-guide.md @@ -12,7 +12,7 @@ ## Introduction -While care has been given to ensure that the new Winter JavaScript framework covers the entire scope of functionality that the original [AJAX framework](../ajax/introduction) provided, there are subtle differences between the two frameworks. Please take the time to read through this document to ensure that you are across the changes, especially if you intend to upgrade an existing project to use this new framework. +While care has been given to ensure that the Snowboard framework covers the entire scope of functionality that the original [AJAX framework](../ajax/introduction) provided, there are subtle differences between the two frameworks. Please take the time to read through this document to ensure that you are across the changes, especially if you intend to upgrade an existing project to use this new framework. ## Breaking changes @@ -20,7 +20,7 @@ While care has been given to ensure that the new Winter JavaScript framework cov ### Browser support is more strict -The new framework drops support for Internet Explorer, as well as some less-used, or discontinued, browsers such as the Samsung Internet Browser and Opera Mini. The framework targets, at the very least, support for the ECMAScript 2015 (ES2015) JavaScript language. +Snowboard drops support for Internet Explorer, as well as some less-used, or discontinued, browsers such as the Samsung Internet Browser and Opera Mini. The framework targets, at the very least, support for the ECMAScript 2015 (ES2015) JavaScript language. Our build script is set up to consider the following browsers as compatible with the framework: @@ -43,7 +43,7 @@ The original [AJAX framework](../ajax/attributes-api#data-attributes) allowed fo We have dropped support of this feature due to its use of the `eval()` method in JavaScript to execute this JavaScript, which has security implications (especially on front-facing code) and prevents people from using content security policies on their sites without the use of the `unsafe-eval` [CSP rule](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). -If you wish to use JavaScript with the AJAX functionality, you must either use the [JavaScript Request functionality](../jsframework/request), or use the original [AJAX framework](../ajax/introduction) which retains this feature. +If you wish to use JavaScript with the AJAX functionality, you must either use the [JavaScript Request functionality](../snowboard/request), or use the original [AJAX framework](../ajax/introduction) which retains this feature. ### AJAX events are triggered as DOM events @@ -70,7 +70,7 @@ $('#element').on('ajaxAlways', function (event) { }); ``` -Please review the [JavaScript Request](../jsframework/requests) documentation for information on what properties are available for DOM events. +Please review the [JavaScript Request](../snowboard/request) documentation for information on what properties are available for DOM events. ## Other changes @@ -89,5 +89,5 @@ $('#element').request('onAjaxHandler') This is now changed to use the base Winter class to call the Request extension: ```js -Winter.request('#element', 'onAjaxHandler'); +Snowboard.request('#element', 'onAjaxHandler'); ``` \ No newline at end of file diff --git a/snowboard-request.md b/snowboard-request.md new file mode 100644 index 00000000..03726956 --- /dev/null +++ b/snowboard-request.md @@ -0,0 +1,59 @@ +# AJAX Requests (JavaScript API) + + +## Introduction + +Snowboard provides core AJAX functionality via the `Request` JavaScript class. The `Request` class provides powerful flexibility and reusability in making AJAX Requests with the Backend functionality in Winter. It can be loaded by adding the following tag into your CMS Theme's page or layout: + +```twig +{% snowboard request %} +``` + +And can be called using the following code in your JavaScript: + +```js +Snowboard.request('#element', 'onAjax', {}); +``` + +The `request` method takes three parameters: + +Parameter | Required | Description +--------- | -------- | ----------- +`element` | No | The element that this AJAX request is targeting, either as a `HTMLElement` instance, or as a CSS-selector string. This can be any element, but usually will be used with a `form` element. +`handler` | **Yes** | The AJAX handler to call. This should be in the format `on`. +`options` | No | The [AJAX request options](#available-options), as an object. + + +## Available options + +All options below are optional. + +Option | Type | Description +------ | ---- | ----------- +`confirm` | `string` | If provided, the user will be prompted with this confirmation message, and will be required to confirm if they wish to proceed with the request. +`data` | `Object` | Extra data that will be sent to the server along with any form data, if available. If `files` is `true`, you may also include files in the request by using [`Blob` objects](https://developer.mozilla.org/en-US/docs/Web/API/Blob). +`redirect` | `string` | If provided, the browser will be redirected to this URL after a request is successfully completed. +`form` | `HTMLElement` or `string` | Specifies the form that data will be extracted from and sent in the request. If this is not provided, the form will be automatically determined from the element provided with the request. If no element is given, then no form will be used. +`files` | `boolean` | If `true`, this request will accept file uploads in the data. +`browserValidate` | `boolean` | If `true`, the in-built client-side validation provided by most common browsers will be performed before sending the request. This is only applied if a form is used in the request. +`flash` | `boolean` | If `true`, the request will process and display any flash messages returned in the response. +`update` | `Object` | Specifies a list of partials and page elements that can be changed through the AJAX response. The key of the object represents the partial name and the value represents the page element (as a CSS selector) to target for the update. If the selector string is prepended with an `@` symbol, the content will be appended to the target. If the selector string is prepended with a `^` symbol, it will instead be prepended to the target. + +The following callbacks may also be specified in the `options` parameter. All callbacks expect a function to be provided. The `this` keyword inside all callbacks will be assigned the `Request` instance that represents this AJAX request. + +Callback | Description +-------- | ----------- +`beforeUpdate` | Executes before page elements are updated with the response data. The function receives one parameter: the response data from the AJAX response as an object. +`success` | Execures when the AJAX request is successfully responded to. The function receives two parameters: the response data from the AJAX response as an object, and the `Request` instance. +`error` | Executes when the AJAX request fails due to a server-side error or validation error. The function receives two parameters: the response data from the AJAX response as an object, and the `Request` instance. +`complete` | Executes when the AJAX request is complete, regardless of success or failure. The function receives two parameters: the response data from the AJAX response as an object, and the `Request` instance. + +Finally, the following option parameters define override functionality for various actions that the `Request` instance may take during the processing of a response. As with the callback methods, these must be provided a function. + +Option | Description +------ | ----------- +`handleConfirmMessage` | Defines a custom handler for any confirmations requested of the user. It will be provided one parameter: the confirmation message, as a string. +`handleErrorMessage` | Defines a custom handler for any errors occuring during the request. It will be provided one parameter: the error message, as string. +`handleValidationMessage` | Defines a custom handler for validation errors occurring during the request. It will be provided two parameters: the first being the message returned, as a string. The second will be an object, with field names as the key and messages as the value. +`handleFlashMessage` | Defines a custom handler for flash messages. It will be provided two parameters: the message of the flash message as a string, and the type of flash message as a string. +`handleRedirectResponse` | Defines a custom handler for any redirects. It will be provided one parameter: the URL to redirect to, as a string. \ No newline at end of file From 8ca881addf33d7b5ec1748e7e24b3179630f50f1 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 10 Jan 2022 15:02:00 -0600 Subject: [PATCH 03/36] Update snowboard-introduction.md --- snowboard-introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index 3deea03d..d57457d8 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -15,7 +15,7 @@ The framework takes advantage of the incredible enhancements made to the JavaScr ## Features - Rewritten AJAX and JavaScript framework, built from the ground-up using the latest JavaScript syntax (ES2015+) and functionality. -- jQuery is no longer a "hard" dependency, allowing the framework to be used across a wide varierty of JavaScript frameworks. +- jQuery is no longer a "hard" dependency, allowing the framework to be used across a wide variety of JavaScript frameworks. - Easy, comprehensive extensibility and event handling in-built into the framework. - Small footprint and full control over which core functionalities to include ensures your website loads quick. From a9eb0aefb33efbbbbdd5396a50328c9b7e48c83c Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 10 Jan 2022 15:02:41 -0600 Subject: [PATCH 04/36] Update snowboard-introduction.md --- snowboard-introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index d57457d8..1e961cbb 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -24,7 +24,7 @@ The framework takes advantage of the incredible enhancements made to the JavaScr > Before proceeding, please read the [Migration Guide](../snowboard/migration-guide), especially if you intend to use this framework on an existing project. -Snowboard is optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. +Snowboard can be optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. By default, this tag just loads the base framework and does not include any of the additional functionality, such as the AJAX framework, in order to allow usage of Snowboard without any clutter. From c2e05632ba4a90f7600d2e5aa27480b2ede362be Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 10 Jan 2022 15:28:30 -0600 Subject: [PATCH 05/36] Update snowboard-introduction.md --- snowboard-introduction.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index 1e961cbb..cfacc93a 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -26,25 +26,18 @@ The framework takes advantage of the incredible enhancements made to the JavaScr Snowboard can be optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. -By default, this tag just loads the base framework and does not include any of the additional functionality, such as the AJAX framework, in order to allow usage of Snowboard without any clutter. +By default only the base Snowboard framework is included by the `{% snowboard %}` token in order to allow for complete control over which additional features (such as the AJAX framework) are desired to be included in your themes. You can then specify further attributes to the tag to include optional additional functionality for the framework: Attribute | Includes --------- | -------- +`all` | Includes all available plugins `request` | The base [JavaScript AJAX](../snowboard/request) request functionality `attr` | The [HTML data attribute](../snowboard/data-attr) request functionality `extras` | [Several useful UI enhancements](../snowboard/extras), including flash messages, loading states and transitions. -`all` | Includes everything above -For example, to include the framework with just the JavaScript AJAX request functionality, the tag would be: +To add Snowboard to your theme with all of its features enabled, you would use the following: ```twig -{% snowboard request %} -``` - -Or to include both the JavaScript AJAX and HTML data attribute request functionality: - -```twig -{% snowboard request attr %} -``` \ No newline at end of file +{% snowboard all %} \ No newline at end of file From cd1b1c9db0bfb0b10302fbd01edff83fdc3e5abc Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 10 Jan 2022 15:30:00 -0600 Subject: [PATCH 06/36] Update snowboard-introduction.md --- snowboard-introduction.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index cfacc93a..3781ce4f 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -40,4 +40,17 @@ Attribute | Includes To add Snowboard to your theme with all of its features enabled, you would use the following: ```twig -{% snowboard all %} \ No newline at end of file +{% snowboard all %} +`` + +To include the framework with just the JavaScript AJAX request functionality: + +```twig +{% snowboard request %} +`` + +Or to include both the JavaScript AJAX and HTML data attribute request functionality: + +```twig +{% snowboard request attr %} +`` \ No newline at end of file From 22de01c5f960b3017d6bf129f5dd3ce9a6d41f0e Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 10 Jan 2022 15:48:01 -0600 Subject: [PATCH 07/36] WIP --- config/toc-docs.yaml | 4 +- snowboard-data-attributes.md | 14 ++ snowboard-extras.md | 219 ++++++++++++++++++++++++++++++++ snowboard-handlers.md | 136 ++++++++++++++++++++ snowboard-introduction.md | 6 +- snowboard-plugin-development.md | 6 + 6 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 snowboard-data-attributes.md create mode 100644 snowboard-extras.md create mode 100644 snowboard-handlers.md create mode 100644 snowboard-plugin-development.md diff --git a/config/toc-docs.yaml b/config/toc-docs.yaml index 527e0b9a..7311bd71 100644 --- a/config/toc-docs.yaml +++ b/config/toc-docs.yaml @@ -71,11 +71,11 @@ Snowboard: pages: snowboard/introduction: "Introduction" snowboard/migration-guide: "Migration Guide" + snowboard/handlers: "Serverside Event Handlers" snowboard/request: "AJAX Requests (JavaScript API)" - snowboard/data-attr: "AJAX Requests (Data Attributes API)" + snowboard/data-attributes: "AJAX Requests (Data Attributes API)" snowboard/extras: "Extra Features" snowboard/plugin-development: "Plugin Development" - snowboard/event-handling: "Event Handling" Database: icon: "icon-hdd" diff --git a/snowboard-data-attributes.md b/snowboard-data-attributes.md new file mode 100644 index 00000000..1b6030ed --- /dev/null +++ b/snowboard-data-attributes.md @@ -0,0 +1,14 @@ +# AJAX Requests (Data Attributes API) + +- [Introduction](#introduction) +- [Available Data Attributes](#available-attributes) +- [Usage Examples](#usage-examples) + + +## Introduction + + +## Available Data Attributes + + +## Usage Examples \ No newline at end of file diff --git a/snowboard-extras.md b/snowboard-extras.md new file mode 100644 index 00000000..e4be7b70 --- /dev/null +++ b/snowboard-extras.md @@ -0,0 +1,219 @@ +# Extra Features + +- [Introduction](#introduction) +- [Loading Indicator](#loader-stripe) +- [Form Validation](#ajax-validation) + - [Throwing a Validation Error](#throw-validation-exception) + - [Displaying Error Messages](#error-messages) + - [Displaying Errors with Fields](#field-errors) +- [Loading Button](#loader-button) +- [Flash Messages](#ajax-flash) +- [Usage Example](#usage-example) + + +## Introduction + +When using the Snowboard framework, you have the option to specify the **extras** flag which includes additional features. These features are often useful when working with AJAX requests in frontend CMS pages. + +```twig +{% snowboard extras %} +``` + +Or + +```twig +{% snowboard all %} +``` + + +## Loading Indicator + +The first feature you should notice is a loading indicator that is displayed on the top of the page when an AJAX request runs. The indicator hooks in to [global events](../ajax/javascript-api#global-events) used by the AJAX framework. + +When an AJAX request starts the `ajaxPromise` event is fired that displays the indicator and puts the mouse cursor in a loading state. The `ajaxFail` and `ajaxDone` events are used to detect when the request finishes, where the indicator is hidden again. + + +## Form validation + +You may specify the `data-request-validate` attribute on a form to enable validation features. + +```html +
+ +
+``` + + +### Throwing a validation error + +In the server side AJAX handler you may throw a [validation exception](../services/error-log#validation-exception) using the `ValidationException` class to make a field invalid, where the first argument is an array. The array should use field names for the keys and the error messages for the values. + +```php +function onSubmit() +{ + throw new ValidationException(['name' => 'You must give a name!']); +} +``` + +> **NOTE**: You can also pass an instance of the [validation service](../services/validation) as the first argument of the exception. + + +### Displaying error messages + +Inside the form, you may display the first error message by using the `data-validate-error` attribute on a container element. The content inside the container will be set to the error message and the element will be made visible. + +```html +
+``` + +To display multiple error messages, include an element with the `data-message` attribute. In this example the paragraph tag will be duplicated and set with content for each message that exists. + +```html +
+

+
+``` + +To add custom classes on AJAX invalidation, hook into the `ajaxInvalidField` and `ajaxPromise` JS events. + +```js +$(window).on('ajaxInvalidField', function(event, fieldElement, fieldName, errorMsg, isFirst) { + $(fieldElement).closest('.form-group').addClass('has-error'); +}); + +$(document).on('ajaxPromise', '[data-request]', function() { + $(this).closest('form').find('.form-group.has-error').removeClass('has-error'); +}); +``` + + +### Displaying errors with fields + +Alternatively, you can show validation messages for individual fields by defining an element that uses the `data-validate-for` attribute, passing the field name as the value. + +```html + + + + +
+``` + +If the element is left empty, it will be populated with the validation text from the server. Otherwise you can specify any text you like and it will be displayed instead. + +```html +
+ Oops.. phone number is invalid! +
+``` + + +## Loading button + +When any element contains the `data-attach-loading` attribute, the CSS class `wn-loading` will be added to it during the AJAX request. This class will spawn a *loading spinner* on button and anchor elements using the `:after` CSS selector. + +```html +
+ +
+ + + Do something + +``` + + +## Flash messages + +Specify the `data-request-flash` attribute on a form to enable the use of flash messages on successful AJAX requests. + +```html +
+ +
+``` + +Combined with use of the `Flash` facade in the event handler, a flash message will appear after the request finishes. + +```php +function onSuccess() +{ + Flash::success('You did it!'); +} +``` + +To remain consistent with AJAX based flash messages, you can render a [standard flash message](../markup/tag-flash) when the page loads by placing this code in your page or layout. + +```twig +{% flash %} +

+ {{ message }} +

+{% endflash %} +``` + + +## Usage example + +Below is a complete example of form validation. It calls the `onDoSomething` event handler that triggers a loading submit button, performs validation on the form fields, then displays a successful flash message. + +```html +
+ +
+ + +
+ +
+ + +
+ + + +
+

+
+ +
+``` + +The AJAX event handler looks at the POST data sent by the client and applies some rules to the validator. If the validation fails, a `ValidationException` is thrown, otherwise a `Flash::success` message is returned. + +```php +function onDoSomething() +{ + $data = post(); + + $rules = [ + 'name' => 'required', + 'email' => 'required|email', + ]; + + $validation = Validator::make($data, $rules); + + if ($validation->fails()) { + throw new ValidationException($validation); + } + + Flash::success('Jobs done!'); +} +``` diff --git a/snowboard-handlers.md b/snowboard-handlers.md new file mode 100644 index 00000000..33005788 --- /dev/null +++ b/snowboard-handlers.md @@ -0,0 +1,136 @@ +# Serverside Event Handlers + +- [Introduction](#introduction) + - [Calling a handler](#calling-handlers) +- [Redirects in AJAX handlers](#redirects-in-handlers) +- [Returning data from AJAX handlers](#returning-data-from-handlers) +- [Throwing an AJAX exception](#throw-ajax-exception) +- [Running code before handlers](#before-handler) + + +## Introduction + +AJAX event handlers are PHP functions that can be defined in the page or layout [PHP section](../cms/themes#php-section) or inside [components](../cms/components). Handler names should have the following pattern: `onName`. All handlers support the use of [updating partials](../ajax/update-partials) as part of the AJAX request. + +```php +function onSubmitContactForm() +{ + // ... +} +``` + +If two handlers with the same name are defined in a page and layout together, the page handler will be executed. The handlers defined in [components](../cms/components) have the lowest priority. + + +### Calling a handler + +Every AJAX request should specify a handler name, either using the [data attributes API](../ajax/attributes-api) or the [JavaScript API](../ajax/javascript-api). When the request is made, the server will search all the registered handlers and locate the first one it finds. + +```html + + + + + +``` + +If two components register the same handler name, it is advised to prefix the handler with the [component short name or alias](../cms/components#aliases). If a component uses an alias of **mycomponent** the handler can be targeted with `mycomponent::onName`. + +```html + +``` + +You may want to use the [`__SELF__`](../plugin/components#referencing-self) reference variable instead of the hard coded alias in case the user changes the component alias used on the page. + +```twig +
+``` + +#### Generic handler + +Sometimes you may need to make an AJAX request for the sole purpose of updating page contents, not needing to execute any code. You may use the `onAjax` handler for this purpose. This handler is available everywhere without needing to write any code. + +```html + +``` + + +## Redirects in AJAX handlers + +If you need to redirect the browser to another location, return the `Redirect` object from the AJAX handler. The framework will redirect the browser as soon as the response is returned from the server. Example AJAX handler: + +```php +function onRedirectMe() +{ + return Redirect::to('http://google.com'); +} +``` + + +## Returning data from AJAX handlers + +In advanced cases you may want to return structured data from your AJAX handlers. If an AJAX handler returns an array, you can access its elements in the `success` event handler. Example AJAX handler: + +```php +function onFetchDataFromServer() +{ + /* Some server-side code */ + + return [ + 'totalUsers' => 1000, + 'totalProjects' => 937 + ]; +} +``` + +The data can be fetched with the data attributes API: + +```html + +``` + +The same with the JavaScript API: + +```html + +``` + + +## Throwing an AJAX exception + +You may throw an [AJAX exception](../services/error-log#ajax-exception) using the `AjaxException` class to treat the response as an error while retaining the ability to send response contents as normal. Simply pass the response contents as the first argument of the exception. + +```php +throw new AjaxException([ + 'error' => 'Not enough questions', + 'questionsNeeded' => 2 +]); +``` + +> **NOTE**: When throwing this exception type [partials will be updated](../ajax/update-partials) as normal. + + +## Running code before handlers + +Sometimes you may want code to execute before a handler executes. Defining an `onInit` function as part of the [page execution life cycle](../cms/layouts#dynamic-pages) allows code to run before every AJAX handler. + +```php +function onInit() +{ + // From a page or layout PHP code section +} +``` + +You may define an `init` method inside a [component class](../plugin/components#page-cycle-init) or [backend widget class](../backend/widgets). + +```php +function init() +{ + // From a component or widget class +} +``` diff --git a/snowboard-introduction.md b/snowboard-introduction.md index 3781ce4f..a014dcb3 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -41,16 +41,16 @@ To add Snowboard to your theme with all of its features enabled, you would use t ```twig {% snowboard all %} -`` +``` To include the framework with just the JavaScript AJAX request functionality: ```twig {% snowboard request %} -`` +``` Or to include both the JavaScript AJAX and HTML data attribute request functionality: ```twig {% snowboard request attr %} -`` \ No newline at end of file +``` \ No newline at end of file diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md new file mode 100644 index 00000000..54e6ded7 --- /dev/null +++ b/snowboard-plugin-development.md @@ -0,0 +1,6 @@ +# Snowboard Plugin Development + +- [Introduction](#introduction) + + +## Introduction From 596794f7ea534b589a7de5c6cdab1cb612a0584f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 12 Jan 2022 14:44:17 +0800 Subject: [PATCH 08/36] Finalise JS request docs, minor tweaks --- snowboard-introduction.md | 4 +- snowboard-migration-guide.md | 6 +- snowboard-request.md | 277 ++++++++++++++++++++++++++++++++++- 3 files changed, 281 insertions(+), 6 deletions(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index a014dcb3..6b0c2640 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -24,7 +24,7 @@ The framework takes advantage of the incredible enhancements made to the JavaScr > Before proceeding, please read the [Migration Guide](../snowboard/migration-guide), especially if you intend to use this framework on an existing project. -Snowboard can be optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners. +Snowboard can be optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners, and should also be located before the `{% scripts %}` tag to allow [plugins](../snowboard/plugin-development) to provide Snowboard plugins if they wish. By default only the base Snowboard framework is included by the `{% snowboard %}` token in order to allow for complete control over which additional features (such as the AJAX framework) are desired to be included in your themes. @@ -34,7 +34,7 @@ Attribute | Includes --------- | -------- `all` | Includes all available plugins `request` | The base [JavaScript AJAX](../snowboard/request) request functionality -`attr` | The [HTML data attribute](../snowboard/data-attr) request functionality +`attr` | The [HTML data attribute](../snowboard/data-attributes) request functionality `extras` | [Several useful UI enhancements](../snowboard/extras), including flash messages, loading states and transitions. To add Snowboard to your theme with all of its features enabled, you would use the following: diff --git a/snowboard-migration-guide.md b/snowboard-migration-guide.md index 8a5da875..93bc161d 100644 --- a/snowboard-migration-guide.md +++ b/snowboard-migration-guide.md @@ -83,11 +83,11 @@ Please review the [JavaScript Request](../snowboard/request) documentation for i The original framework used a jQuery extension to call AJAX requests via JavaScript: ```js -$('#element').request('onAjaxHandler') +$('#element').request('onAjaxHandler', { /* ... options .. */ }) ``` -This is now changed to use the base Winter class to call the Request extension: +This is now changed to use the base Winter class to call the Request plugin: ```js -Snowboard.request('#element', 'onAjaxHandler'); +Snowboard.request('#element', 'onAjaxHandler', { /* ... options .. */ }); ``` \ No newline at end of file diff --git a/snowboard-request.md b/snowboard-request.md index 03726956..6a831507 100644 --- a/snowboard-request.md +++ b/snowboard-request.md @@ -1,5 +1,13 @@ # AJAX Requests (JavaScript API) +- [Introduction](#introduction) +- [Request workflow](#request-workflow) +- [Available options](#available-options) +- [Global events](#global-events) +- [Element events](#element-events) +- [Usage examples](#usage-examples) +- [Extending or replacing the Request class](#extending-replacing) + ## Introduction @@ -15,6 +23,8 @@ And can be called using the following code in your JavaScript: Snowboard.request('#element', 'onAjax', {}); ``` +The base `Request` class, by default, uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided in most modern browsers to execute AJAX requests from the frontend to the backend in Winter. + The `request` method takes three parameters: Parameter | Required | Description @@ -23,6 +33,22 @@ Parameter | Required | Description `handler` | **Yes** | The AJAX handler to call. This should be in the format `on`. `options` | No | The [AJAX request options](#available-options), as an object. + +## Request workflow + +AJAX requests made through the `Request` class go through the following process: + +- The request is initialized and validated with the given element and options. +- If `browserValidate` is enabled and the AJAX request is done with a form, browser-side validation occurs. If this fails, the request is cancelled. +- If `confirm` is specified, a confirmation is presented to the user. If they cancel, the request is cancelled. +- The data provided to the Request, along with any form data if applicable, will be compiled. +- The AJAX request is sent to the Backend context using the [given handler](../snowboard/handlers). +- A response is received from the Backend context and processed, from here, one of three things happen: + - If the response is successful, then any partials that are instructed to be updated will be updated at this point. + - If the response is unsuccessful due to a validation error, then a validation message will be shown and the failing fields will be highlighted. + - If the response is unsuccessful due to any other error, then an error message will be shown. +- The request is then complete. + ## Available options @@ -56,4 +82,253 @@ Option | Description `handleErrorMessage` | Defines a custom handler for any errors occuring during the request. It will be provided one parameter: the error message, as string. `handleValidationMessage` | Defines a custom handler for validation errors occurring during the request. It will be provided two parameters: the first being the message returned, as a string. The second will be an object, with field names as the key and messages as the value. `handleFlashMessage` | Defines a custom handler for flash messages. It will be provided two parameters: the message of the flash message as a string, and the type of flash message as a string. -`handleRedirectResponse` | Defines a custom handler for any redirects. It will be provided one parameter: the URL to redirect to, as a string. \ No newline at end of file +`handleRedirectResponse` | Defines a custom handler for any redirects. It will be provided one parameter: the URL to redirect to, as a string. + + +## Global events + +The `Request` class fires several global events which can be used by plugins to augment or override the functionality of the `Request` class. [Snowboard plugins](../snowboard/plugin-development) can be configured to listen to, and act upon, these events by using the `listen()` method to direct the event to a method with the plugin class. + +```js +class HandleFlash extends Snowboard.Singleton +{ + /** + * Defines listeners for global events. + * + * @returns {Object} + */ + listens() { + return { + // when the "ajaxFlashMessages" event is called, run the "doFlashMessage" method in this class + ajaxFlashMessages: 'doFlashMessages', + }; + } + + doFlashMessages(messages) { + Object.entries(messages).forEach((entry) => { + const [cssClass, message] = entry; + + showFlash(message, cssClass); + }); + } +} +``` + +Some events are called as Promise events, which means that your listener must itself return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) this is either resolved or rejected. + +```js +class ConfirmEverything extends Snowboard.Singleton +{ + /** + * Defines listeners for global events. + * + * @returns {Object} + */ + listens() { + return { + // when the "ajaxConfirmMessage" event is called, run the "confirm" method in this class + ajaxConfirmMessage: 'confirm', + }; + } + + // Confirm all confirmation messages + confirm() { + return Promise.resolve(true); + } +} +``` + +The following events are called during the Request process: + +Event | Promise? | Description +----- | -------- | ----------- +`ajaxSetup` | No | Called after the Request is initialized and checked that it can be called. It is provided one parameter: the `Request` instance. It is intended to be used for modifying the Request parameters before sending to the server. Returning `false` in any event listeners will cancel the request. +`ajaxConfirmMessage` | Yes | Called if `confirm` is specified, and the Request is ready to be sent to the server. This allows developers to customise the confirmation process or display. It is provided two parameters: the `Request` instance, and the confirmation message as a string. If an event listener rejects the Promise, this will cancel the request. +`ajaxBeforeSend` | No | Called immediately before the Request is sent. It can be used for final changes to the Request, or cancelling it prematurely. It is provided one parameter: the `Request` instance. Returning `false` in any event listeners will cancel the request. +`ajaxStart` | No | Called when the Request is sent. It is provided two parameters: a Promise tied to the AJAX call that is resolved or rejected by the response, and the `Request` instance. This event cannot be cancelled. +`ajaxBeforeUpdate` | Yes | Called when a successful response is returned and partials are going to be updated. It is provided two parameters: the raw response data, and the `Request` instance. It can be used to determine which partials will be updated, or can also be used to cancel the partial updates. If an event listener rejects the Promise, no partials will be updated. +`ajaxUpdate` | No | Called when an individual partial is updated. It is provided three parameters: the element that is updated, the content that has been added to the element, and the `Request` instance. It can be used to make further updates to an element, or handle updates. Note that this event is fired *after* the element is updated. This event cannot be cancelled. +`ajaxUpdateComplete` | No | Called when the partials are updated. It is provided two parameters: an array of elements that have been updated, and the `Request` instance. It can be used to determine which partials have been updated. This event cannot be cancelled. +`ajaxSuccess` | No | Called when a successful response is returned and all partial updating is completed. It is provided two parameters: the raw response data as an object, and the `Request` instance. It can be used to cancel further response handling (ie. redirects, flash messages). Returning `false` in any event listeners will prevent any further response handling from taking place. +`ajaxError` | No | Called when an unsuccessful response is returned from the AJAX request. It is provided two parameters: the raw response data as an object, and the `Request` instance. It can be used to cancel further error handling. Returning `false` in any event listeners will prevent any further response handling from taking place. +`ajaxRedirect` | No | Called when a redirect is to take place, either from the response or through the `redirect` option. It is provided two parameters: the URL to redirect to as a string, and the `Request` instance. Returning `false` in any event listeners will prevent the redirect from executing. +`ajaxErrorMessage` | No | Called when an error message is to be shown to the user. It is provided two parameters: the error message as a string, and the `Request` instance. Returning `false` in any event listeners will prevent the default error handling (showing an alert to the user) from executing. +`ajaxFlashMessages` | No | Called when one or more flash messages are to be shown to the user. It is provided two parameters: the flash messages as an array of objects, and the `Request` instance. There is no default functionality for flash messages, so if no event listeners trigger for this event, no activity will occur. +`ajaxValidationErrors` | No | Called when a validation error is returned in the response. It is provided three parameters: the form where validation failed as a `HTMLElement`, an array of fields and messages within the form, and the `Request` instance. There is no default functionality for validation errors, so if no event listeners trigger for this event, no activity will occur. + + +## Element events + +In addition to global events, local events are fired on elements that trigger an AJAX request. These events are treated as [DOM events](https://developer.mozilla.org/en-US/docs/Web/API/Event) and thus can be listened to by normal DOM event listeners or your framework of choice. The `Request` class will inject properties in the event depending on the type of event, however, the `event.request` property will always be the `Request` instance. + +```js +const element = document.getElementById('my-button'); +element.addEventListener('ajaxAlways', (event) => { + console.log(event.request); // The Request instance + console.log(event.responseData); // The raw response data as an object + console.log(event.responseError); // An error object if the response failed +}); +``` + +In most cases, you can cancel the event, and thus the Request, by adding `event.preventDefault()` to your callback. + +```js +const element = document.getElementById('my-button'); +element.addEventListener('ajaxSetup', (event) => { + // Never process a request for this element + event.preventDefault(); +}); +``` + +The following events are called during the Request process directly on the element that triggered the request: + +Event | Description +----- | ----------- +`ajaxSetup` | Called after the Request is initialized and checked that it can be called. +`ajaxPromise` | Called when the AJAX request is sent to the server. A property called `promise` is provided, which is the Promise that is resolved or rejected when the response succeeds or fails. +`ajaxUpdate` | Called when an element is updated by a partial update from an AJAX response. **This event is fired on the element that is updated, not the triggering element.** and thus does not get given the `Request` instance. A property called `content` is provided with the content that was added to this element. +`ajaxDone` | Called when this element makes a successful AJAX request. A property called `responseData` is provided with the raw response data, as an object. +`ajaxFail` | Called when this element makes an unsuccessful AJAX request. A property called `responseError` is provided with the error object. +`ajaxAlways` | Called when an AJAX request is completed, regardless of success or failure. It is provided two properties: `responseData` and `responseError`, which represent the raw response data and error object, depending on whether the AJAX request succeeded or failed. + + +## Usage examples + +Make a simple request to an AJAX handler without specifying a triggering element. + +```js +Snowboard.request(null, 'onAjax'); +``` + +Request a confirmation before submitting a form, and redirect them to a success page. + +```js +Snowboard.request('form', 'onSubmit', { + confirm: 'Are you sure you wish to submit this data?', + redirect: '/form/success', +}); +``` + +Run a calculation handler and inject some data from the page, then update the total. + +```js +Snowboard.request('#calculate', 'onCalculate', { + data: { + firstValue: document.getElementById('first-value').value, + secondValue: document.getElementById('second-value').value, + }, + update: { + totalResult: '.total-result' + }, +}); +``` + +Run a calculation handler and show a success message when calculated. + +```js +Snowboard.request('#calculate', 'onCalculate', { + data: { + firstValue: document.getElementById('first-value').value, + secondValue: document.getElementById('second-value').value, + }, + success: (data) => { + const total = data.total; + alert('The answer is ' + total); + }, +}); +``` + +Prompt a user to confirm a redirect. + +```js +Snowboard.request('form', 'onSubmit', { + handleRedirectResponse: (url) => { + if (confirm('Are you sure you wish to go to this URL: ' + url)) { + window.location.assign(url); + } else { + alert('Redirect cancelled'); + } + }, +}); +``` + +Track when an element is updated from an AJAX request. + +```js +const element = document.getElementById('updated-element'); +element.addEventListener('ajaxUpdate', (event) => { + console.log('The "updated-element" event was updated with the following content:', event.content); +}); +``` + +Disable file uploads globally from AJAX requests by modifying the `Request` instance. + +```js +// In your own Snowboard plugin +class DisableFileUploads extends Snowboard.Singleton +{ + listens() { + return { + ajaxSetup: 'stopFileUploads', + }; + } + + stopFileUploads(request) { + request.options.files = false; + } +} +``` + + +## Extending or replacing the Request class + +As part of making Snowboard an extensible and flexible platform, developers have the option to extend, or replace entirely, the base Request class. This allows developers to use their own preferred platforms and frameworks for executing the AJAX functionality, partial updates, and much more. + +For example, if a developer wanted to use the [Axios library](https://github.com/axios/axios) to execute AJAX requests, as opposed to the in-built Fetch API, one could do this by creating their own Snowboard plugin and extending the `Request` class, replacing the `doAjax()` method in their own class: + +```js +const axios = require('axios'); + +class AxiosRequest extends Request +{ + doAjax() { + // Allow plugins to cancel the AJAX request before sending + if (this.snowboard.globalEvent('ajaxBeforeSend', this) === false) { + return Promise.resolve({ + cancelled: true, + }); + } + + const ajaxPromise = axios({ + method: 'post', + url: this.url, + headers: this.headers, + data: this.data, + }); + + this.snowboard.globalEvent('ajaxStart', ajaxPromise, this); + + if (this.element) { + const event = new Event('ajaxPromise'); + event.promise = ajaxPromise; + this.element.dispatchEvent(event); + } + + return ajaxPromise; + } +} +``` + +You could then either replace the `request` plugin with this class, or alias it as something else: + +```js +Snowboard.removePlugin('request'); +Snowboard.addPlugin('request', AxiosRequest); + +// Or run it as an alias +Snowboard.addPlugin('axios', AxiosRequest); +// And call it thusly +Snowboard.axios('#my-element', 'onSubmit'); +``` + +For more information on the best practices with setting up a Snowboard plugin, view the [Plugin Development](../snowboard/plugin-development) documentation. \ No newline at end of file From b0767e15778e3e53b7aecbaf97a0c891744a62a7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 12 Jan 2022 15:04:33 +0800 Subject: [PATCH 09/36] Add data attributes documentation --- snowboard-data-attributes.md | 87 +++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/snowboard-data-attributes.md b/snowboard-data-attributes.md index 1b6030ed..35238a4e 100644 --- a/snowboard-data-attributes.md +++ b/snowboard-data-attributes.md @@ -7,8 +7,91 @@ ## Introduction +The Data Attributes API is the more simple way of embedding AJAX functionality in your themes and plugins, and removes +the need to be experienced with JavaScript. While the [JavaScript API](../snowboard/request) has had numerous changes +from the original [AJAX framework](../ajax/introduction), the Data Attributes API has remain largely unchanged, whilst +still being powered by the new JavaScript functionality under the hood. + +It can be loaded by adding the following tag into your CMS Theme's page or layout: + +```twig +{% snowboard request attr %} +``` + +> **Note:** As per the [Migration Guide](../snowboard/migration-guide), arbitrary JavaScript is no longer allowed through the Data Attributes API. Thus, the `data-request-before-update`, `data-request-success`, `data-request-error` and `data-request-complete` attributes are no longer supported. Please use the [JavaScript API](../snowboard/request) if you require this functionality. + ## Available Data Attributes - -## Usage Examples \ No newline at end of file +Triggering an AJAX request from a valid element is as simple as adding the `data-request` attribute to that element. This generally should be done on a button, link or form. You can also customize the AJAX request using the following attributes: + +Attribute | Description +--------- | ----------- +`data-request` | Specifies the AJAX handler name to target for the request. +`data-request-confirm` | Specifies the confirmation message to present to the user before proceeding with the request. If the user cancels, the request is not sent. +`data-request-redirect` | Specifies a URL to redirect the browser to, if a successful AJAX response is received. +`data-request-url` | Specifies the URL to send the AJAX request to. By default, this will be the current URL. +`data-request-update` | Specifies a list of partials and page elements (CSS selectors) to update on a successful AJAX response. The format is as follows: `partial: selector, partial: selector`. Usage of quotes is required in most cases: `'partial': 'selector'`. If the selector is prepended with an `@` symbol, the content received from the server will be appended to the element. If the selector is prepended with a `^` symbol, the content will be prepended. Otherwise, received content will replace the original content in the element. +`data-request-data` | Specifies additional data to send with the request to the server. The format is as follows: `'var': 'value', 'var2': 'new value'`. You may also specify this same attribute on any parent elements of the triggering element, and this data will be merged with the parent data (with the triggering data taking preference). It will also be merged with any form data, if this request triggers within a form. +`data-request-form` | Specifies the form that the AJAX request will include its data from. If this is unspecified, the closest form will be used, or if the element itself is a form, then this will be used. +`data-request-flash` | Specifies if flash messages will be accepted from the response. +`data-request-files` | Specifies if file data will be included in the request. This will allow any file inputs in the form to work. +`data-browser-validate` | Specifies if the in-built browser validation will be triggered. If present, the request will be cancelled if the browser validation fails. +`data-track-input` | Specifies if an input will trigger an AJAX request anytime the input changes. An optional number can be specified in this attribute, which represents the amount of milliseconds between any change and the AJAX request triggering. + + +When the `data-request` attribute is specified for an element, the element triggers an AJAX request when a user interacts with it. Depending on the type of element, the request is triggered on the following events: + +Element | Event +------------- | ------------- +**Forms** | when the form is submitted. +**Links, buttons** | when the element is clicked. +**Text, number, and password fields** | when the text is changed and only if the `data-track-input` attribute is presented. +**Dropdowns, checkboxes, radios** | when the element is selected. + + +## Usage examples + +Trigger the `onCalculate` handler when the form is submitted. Update the element with the identifier "result" with the **calcresult** partial: + +```html + +``` + +Request a confirmation when the Delete button is clicked before the request is sent: + +```html + + ... + +``` + +Redirect to another page after the successful request: + +```html + +``` + +Send a POST parameter `mode` with a value `update`: + +```html + +``` + +Send a POST parameter `id` with value `7` across multiple elements: + +```html +
+ + +
+``` + +Including [file uploads](../services/request-input#files) with a request: + +```html + + + + +``` From 1e2bd734d1a940844be5ee6cc00717058d0733a2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 16 Jan 2022 11:49:06 +0800 Subject: [PATCH 10/36] WIP other pages --- snowboard-extras.md | 16 +++++---------- snowboard-handlers.md | 48 +++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/snowboard-extras.md b/snowboard-extras.md index e4be7b70..853d117c 100644 --- a/snowboard-extras.md +++ b/snowboard-extras.md @@ -1,7 +1,7 @@ # Extra Features - [Introduction](#introduction) -- [Loading Indicator](#loader-stripe) +- [Loading indicator](#loader-stripe) - [Form Validation](#ajax-validation) - [Throwing a Validation Error](#throw-validation-exception) - [Displaying Error Messages](#error-messages) @@ -10,7 +10,7 @@ - [Flash Messages](#ajax-flash) - [Usage Example](#usage-example) - + ## Introduction When using the Snowboard framework, you have the option to specify the **extras** flag which includes additional features. These features are often useful when working with AJAX requests in frontend CMS pages. @@ -19,18 +19,12 @@ When using the Snowboard framework, you have the option to specify the **extras* {% snowboard extras %} ``` -Or - -```twig -{% snowboard all %} -``` - -## Loading Indicator +## Loading indicator -The first feature you should notice is a loading indicator that is displayed on the top of the page when an AJAX request runs. The indicator hooks in to [global events](../ajax/javascript-api#global-events) used by the AJAX framework. +The loading indicator is a loading bar that is displayed on the top of the page when an AJAX request runs. The indicator hooks in to [global events](../snowboard/request#global-events) used by the Snowboard framework. -When an AJAX request starts the `ajaxPromise` event is fired that displays the indicator and puts the mouse cursor in a loading state. The `ajaxFail` and `ajaxDone` events are used to detect when the request finishes, where the indicator is hidden again. +When an AJAX request starts, the `ajaxPromise` event is fired. This displays the loading indicator at the top of the page. When this promise is resolved, the loading bar is removed. ## Form validation diff --git a/snowboard-handlers.md b/snowboard-handlers.md index 33005788..e9ae39b7 100644 --- a/snowboard-handlers.md +++ b/snowboard-handlers.md @@ -1,7 +1,8 @@ -# Serverside Event Handlers +# Server-side Event Handlers - [Introduction](#introduction) - [Calling a handler](#calling-handlers) + - [Generic handler](#generic-handler) - [Redirects in AJAX handlers](#redirects-in-handlers) - [Returning data from AJAX handlers](#returning-data-from-handlers) - [Throwing an AJAX exception](#throw-ajax-exception) @@ -10,12 +11,16 @@ ## Introduction -AJAX event handlers are PHP functions that can be defined in the page or layout [PHP section](../cms/themes#php-section) or inside [components](../cms/components). Handler names should have the following pattern: `onName`. All handlers support the use of [updating partials](../ajax/update-partials) as part of the AJAX request. +AJAX event handlers are PHP functions that can be defined in the page or layout [PHP section](../cms/themes#php-section) or inside [components](../cms/components) and are used to execute the server-side functionality of an AJAX request made by the [Request API](../snowboard/request) or [Data Attributes API](../snowboard/data-attributes). + +Handler method names should be specified with the `on` prefix, followed by the event name in PascalCase - for example, `onMyHandler` or `onCreatePost`. + +All handlers support the use of [updating partials](#updating-partials) as part of the AJAX response. This behavior can also be controlled via the `update` option in the [Request API](../snowboard/request) or the `data-request-update` attribute in the [Data Attributes API](../snowboard/data-attributes). ```php function onSubmitContactForm() { - // ... + // AJAX handler functionality goes here } ``` @@ -24,14 +29,14 @@ If two handlers with the same name are defined in a page and layout together, th ### Calling a handler -Every AJAX request should specify a handler name, either using the [data attributes API](../ajax/attributes-api) or the [JavaScript API](../ajax/javascript-api). When the request is made, the server will search all the registered handlers and locate the first one it finds. +Every AJAX request should specify a handler name. When the request is made, the server will search all the registered handlers and locates the prioritised handler. ```html - + ``` If two components register the same handler name, it is advised to prefix the handler with the [component short name or alias](../cms/components#aliases). If a component uses an alias of **mycomponent** the handler can be targeted with `mycomponent::onName`. @@ -45,8 +50,8 @@ You may want to use the [`__SELF__`](../plugin/components#referencing-self) refe ```twig
``` - -#### Generic handler + +### Generic handler Sometimes you may need to make an AJAX request for the sole purpose of updating page contents, not needing to execute any code. You may use the `onAjax` handler for this purpose. This handler is available everywhere without needing to write any code. @@ -57,7 +62,7 @@ Sometimes you may need to make an AJAX request for the sole purpose of updating ## Redirects in AJAX handlers -If you need to redirect the browser to another location, return the `Redirect` object from the AJAX handler. The framework will redirect the browser as soon as the response is returned from the server. Example AJAX handler: +If you need to redirect the browser to another location, return the `Redirect` object from the AJAX handler. The framework will redirect the browser as soon as the response is returned from the server. ```php function onRedirectMe() @@ -66,10 +71,12 @@ function onRedirectMe() } ``` +You may also specify a `redirect` in the Request API options, or through the `data-request-redirect` data attribute. This setting will take precedence over any redirect returned in the AJAX response. + ## Returning data from AJAX handlers -In advanced cases you may want to return structured data from your AJAX handlers. If an AJAX handler returns an array, you can access its elements in the `success` event handler. Example AJAX handler: +You may want to return structured, arbitrary data from your AJAX handlers. If an AJAX handler returns an array, you can access its elements in the `success` callback handler. ```php function onFetchDataFromServer() @@ -83,22 +90,19 @@ function onFetchDataFromServer() } ``` -The data can be fetched with the data attributes API: +Then, in JavaScript: -```html - +```js +Snowboard.request(this, 'onHandleForm', { + success: function(data) { + console.log(data); + } +}); ``` -The same with the JavaScript API: +Data returned in this fashion **cannot** be accessed through the [Data Attributes API](../snowboard/data-attributes). -```html - -``` +You may also retrieve the data in [several events](../snowboard/request#global-events) that fire as part of the Request lifecycle. ## Throwing an AJAX exception @@ -112,7 +116,7 @@ throw new AjaxException([ ]); ``` -> **NOTE**: When throwing this exception type [partials will be updated](../ajax/update-partials) as normal. +> **NOTE**: When throwing this exception type, [partials will be updated](../ajax/update-partials) as normal. ## Running code before handlers From f031e6d49bf74e58e92f5be79e1cfebbe5da435e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 16 Jan 2022 12:03:25 +0800 Subject: [PATCH 11/36] Tweak extras content --- snowboard-extras.md | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/snowboard-extras.md b/snowboard-extras.md index 853d117c..b600ac92 100644 --- a/snowboard-extras.md +++ b/snowboard-extras.md @@ -2,13 +2,13 @@ - [Introduction](#introduction) - [Loading indicator](#loader-stripe) -- [Form Validation](#ajax-validation) - - [Throwing a Validation Error](#throw-validation-exception) - - [Displaying Error Messages](#error-messages) - - [Displaying Errors with Fields](#field-errors) -- [Loading Button](#loader-button) -- [Flash Messages](#ajax-flash) -- [Usage Example](#usage-example) +- [Form validation](#ajax-validation) + - [Throwing a validation error](#throw-validation-exception) + - [Displaying error messages](#error-messages) + - [Displaying errors with fields](#field-errors) +- [Loading button](#loader-button) +- [Flash messages](#ajax-flash) +- [Usage examples](#usage-examples) ## Introduction @@ -29,7 +29,7 @@ When an AJAX request starts, the `ajaxPromise` event is fired. This displays the ## Form validation -You may specify the `data-request-validate` attribute on a form to enable validation features. +You may specify the `data-request-validate` attribute on a form to enable server-side validation features with fields and forms. ```html ### Throwing a validation error -In the server side AJAX handler you may throw a [validation exception](../services/error-log#validation-exception) using the `ValidationException` class to make a field invalid, where the first argument is an array. The array should use field names for the keys and the error messages for the values. +In the server side AJAX handler, you may throw a [validation exception](../services/error-log#validation-exception) using the `ValidationException` class to make a field invalid. The exception should be provided an array, which states the field names for the keys, and the error messages for the values. ```php function onSubmit() @@ -51,7 +51,7 @@ function onSubmit() } ``` -> **NOTE**: You can also pass an instance of the [validation service](../services/validation) as the first argument of the exception. +> **NOTE**: You can also pass a [Validator](../services/validation) instance as the first argument of the exception instead, to use the in-built validation service. ### Displaying error messages @@ -70,17 +70,7 @@ To display multiple error messages, include an element with the `data-message` a ``` -To add custom classes on AJAX invalidation, hook into the `ajaxInvalidField` and `ajaxPromise` JS events. - -```js -$(window).on('ajaxInvalidField', function(event, fieldElement, fieldName, errorMsg, isFirst) { - $(fieldElement).closest('.form-group').addClass('has-error'); -}); - -$(document).on('ajaxPromise', '[data-request]', function() { - $(this).closest('form').find('.form-group.has-error').removeClass('has-error'); -}); -``` +The `handleValidationErrors` callback, and the `ajaxValidationErrors` global event, that are available with the [Request API](../snowboard/request#global-events) allow you to fully customise the client-side validation handling. The `handleValidationErrors` callback can be used to control validation per request, while the `ajaxValidationErrors` global event can be used by [Snowboard plugins](../snowboard/plugin-development) to augment the client-side validation in a global fashion. ### Displaying errors with fields @@ -158,8 +148,8 @@ To remain consistent with AJAX based flash messages, you can render a [standard {% endflash %} ``` - -## Usage example + +## Usage examples Below is a complete example of form validation. It calls the `onDoSomething` event handler that triggers a loading submit button, performs validation on the form fields, then displays a successful flash message. From 01c26ca9eb86af95ef9c384f70e4a29dae860cb1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 19 Jan 2022 17:52:55 +0800 Subject: [PATCH 12/36] Add fetch options API --- snowboard-request.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snowboard-request.md b/snowboard-request.md index 6a831507..6ce84c2f 100644 --- a/snowboard-request.md +++ b/snowboard-request.md @@ -64,6 +64,7 @@ Option | Type | Description `browserValidate` | `boolean` | If `true`, the in-built client-side validation provided by most common browsers will be performed before sending the request. This is only applied if a form is used in the request. `flash` | `boolean` | If `true`, the request will process and display any flash messages returned in the response. `update` | `Object` | Specifies a list of partials and page elements that can be changed through the AJAX response. The key of the object represents the partial name and the value represents the page element (as a CSS selector) to target for the update. If the selector string is prepended with an `@` symbol, the content will be appended to the target. If the selector string is prepended with a `^` symbol, it will instead be prepended to the target. +`fetchOptions` | `Object` | If specified, this will override the options used with the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to make the request. The following callbacks may also be specified in the `options` parameter. All callbacks expect a function to be provided. The `this` keyword inside all callbacks will be assigned the `Request` instance that represents this AJAX request. @@ -145,6 +146,7 @@ Event | Promise? | Description `ajaxSetup` | No | Called after the Request is initialized and checked that it can be called. It is provided one parameter: the `Request` instance. It is intended to be used for modifying the Request parameters before sending to the server. Returning `false` in any event listeners will cancel the request. `ajaxConfirmMessage` | Yes | Called if `confirm` is specified, and the Request is ready to be sent to the server. This allows developers to customise the confirmation process or display. It is provided two parameters: the `Request` instance, and the confirmation message as a string. If an event listener rejects the Promise, this will cancel the request. `ajaxBeforeSend` | No | Called immediately before the Request is sent. It can be used for final changes to the Request, or cancelling it prematurely. It is provided one parameter: the `Request` instance. Returning `false` in any event listeners will cancel the request. +`ajaxFetchOptions` | No | Called immediately when the `Fetch API` is initialised to make the request. It can be used to modify the Fetch options via a plugin. It is provided two parameters: the current Fetch options, as an object, and the `Request` instance. This event cannot be cancelled. `ajaxStart` | No | Called when the Request is sent. It is provided two parameters: a Promise tied to the AJAX call that is resolved or rejected by the response, and the `Request` instance. This event cannot be cancelled. `ajaxBeforeUpdate` | Yes | Called when a successful response is returned and partials are going to be updated. It is provided two parameters: the raw response data, and the `Request` instance. It can be used to determine which partials will be updated, or can also be used to cancel the partial updates. If an event listener rejects the Promise, no partials will be updated. `ajaxUpdate` | No | Called when an individual partial is updated. It is provided three parameters: the element that is updated, the content that has been added to the element, and the `Request` instance. It can be used to make further updates to an element, or handle updates. Note that this event is fired *after* the element is updated. This event cannot be cancelled. From 382630ce2bfc1b27da68fc4aa0497f5c43f18b2b Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 19 Jan 2022 15:31:20 -0600 Subject: [PATCH 13/36] Added documentation for the Snowboard utilities --- config/toc-docs.yaml | 1 + snowboard-utilities.md | 269 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 snowboard-utilities.md diff --git a/config/toc-docs.yaml b/config/toc-docs.yaml index 8c1e5c1f..e9e9ba1c 100644 --- a/config/toc-docs.yaml +++ b/config/toc-docs.yaml @@ -75,6 +75,7 @@ Snowboard: snowboard/request: "AJAX Requests (JavaScript API)" snowboard/data-attributes: "AJAX Requests (Data Attributes API)" snowboard/extras: "Extra Features" + snowboard/utilities: "Utilities" snowboard/plugin-development: "Plugin Development" Database: diff --git a/snowboard-utilities.md b/snowboard-utilities.md new file mode 100644 index 00000000..80af2374 --- /dev/null +++ b/snowboard-utilities.md @@ -0,0 +1,269 @@ +# Snowboard Utilities + +- [Introduction](#introduction) +- [Cookie](#cookie) + - [Basic Usage](#cookie-basic-usage) + - [Encoding](#cookie-encoding) + - [Cookie Attributes](#cookie-attributes) + - [expires](#cookie-attributes-expires) + - [path](#cookie-attributes-path) + - [domain](#cookie-attributes-domain) + - [secure](#cookie-attributes-secure) + - [sameSite](#cookie-attributes-sameSite) + - [Setting Defaults](#cookie-attributes-defaults) + - [Converters](#cookie-converters) + - [Read](#cookie-converters-read) + - [Write](#cookie-converters-write) +- [Debounce](#debounce) +- [JSON Parser](#json-parser) +- [Sanitizer](#sanitizer) + + +## Introduction + +The Snowboard framework included several small utilities by default that help make development easier. + + +## Cookie + +The Cookie utility is a small wrapper around the [js-cookie](https://github.com/js-cookie/js-cookie/) package that provides a simple, lightweight JS API for interacting with browser cookies. + + +### Basic Usage + +Create a cookie, valid across the entire site: + +```js +Snowboard.cookie().set('name', 'value') +``` + +Create a cookie that expires 7 days from now, valid across the entire site: + +```js +Snowboard.cookie().set('name', 'value', { expires: 7 }) +``` + +Create an expiring cookie, valid to the path of the current page: + +```js +Snowboard.cookie().set('name', 'value', { expires: 7, path: '' }) +``` + +Read cookie: + +```js +Snowboard.cookie().get('name') // => 'value' +Snowboard.cookie().get('nothing') // => undefined +``` + +Read all visible cookies: + +```js +Snowboard.cookie().get() // => { name: 'value' } +``` + +>**NOTE:** Cookies can only be read if the place they are being read from has access to read the cookie according to the browser. + +Delete cookie: + +```js +Snowboard.cookie().remove('name') +``` + +Delete a cookie valid to the path of the current page: + +```js +Snowboard.cookie().set('name', 'value', { path: '' }) +Snowboard.cookie().remove('name') // fail! +Snowboard.cookie().remove('name', { path: '' }) // removed! +``` + +>**IMPORTANT!** When deleting a cookie and you're not relying on the [default attributes](#cookie-attributes-defaults), you must pass the exact same path and domain attributes that were used to set the cookie + +```js +Snowboard.cookie().remove('name', { path: '', domain: '.yourdomain.com' }) +``` + +>**NOTE:** Removing a nonexistent cookie neither raises any exception nor returns any value. + + +### Encoding + +The package is [RFC 6265](http://tools.ietf.org/html/rfc6265#section-4.1.1) compliant. All special characters that are not allowed in the cookie-name or cookie-value are encoded with each one's UTF-8 Hex equivalent using [percent-encoding](http://en.wikipedia.org/wiki/Percent-encoding). +The only character in cookie-name or cookie-value that is allowed and still encoded is the percent `%` character, it is escaped in order to interpret percent input as literal. +Please note that the default encoding/decoding strategy is meant to be interoperable [only between cookies that are read/written by js-cookie](https://github.com/js-cookie/js-cookie/pull/200#discussion_r63270778). To override the default encoding/decoding strategy you need to use a [converter](#cookie-converters). + +>**NOTE:** According to [RFC 6265](https://tools.ietf.org/html/rfc6265#section-6.1), your cookies may get deleted if they are too big or there are too many cookies in the same domain, [more details here](https://github.com/js-cookie/js-cookie/wiki/Frequently-Asked-Questions#why-are-my-cookies-being-deleted). + + +### Cookie Attributes + +Cookie attributes can be set globally by creating an instance of the API via `withAttributes()`, or individually for each call to `Snowboard.cookie().set(...)` by passing a plain object as the last argument. Per-call attributes override the default attributes. + +>**NOTE:** You should never allow untrusted input to set the cookie attributes or you might be exposed to a [XSS attack](https://github.com/js-cookie/js-cookie/issues/396). + + +#### expires + +Defines when the cookie will be removed. Value must be a [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) which will be interpreted as days from time of creation or a [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) instance. If omitted, the cookie becomes a session cookie. + +To create a cookie that expires in less than a day, you can check the [FAQ on the Wiki](https://github.com/js-cookie/js-cookie/wiki/Frequently-Asked-Questions#expire-cookies-in-less-than-a-day). + +**Default:** Cookie is removed when the user closes the browser. + +**Examples:** + +```js +Snowboard.cookie().set('name', 'value', { expires: 365 }) +Snowboard.cookie().get('name') // => 'value' +Snowboard.cookie().remove('name') +``` + + +#### path + +A [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) indicating the path where the cookie is visible. + +**Default:** `/` + +**Examples:** + +```js +Snowboard.cookie().set('name', 'value', { path: '' }) +Snowboard.cookie().get('name') // => 'value' +Snowboard.cookie().remove('name', { path: '' }) +``` + + +#### domain + +A [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) indicating a valid domain where the cookie should be visible. The cookie will also be visible to all subdomains. + +**Default:** Cookie is visible only to the domain or subdomain of the page where the cookie was created + +**Examples:** + +Assuming a cookie that is being created on `example.com`: + +```js +Snowboard.cookie().set('name', 'value', { domain: 'subdomain.example.com' }) +Snowboard.cookie().get('name') // => undefined (need to read at 'subdomain.example.com') +``` + + +#### secure + +Either `true` or `false`, indicating if the cookie transmission requires a secure protocol (https). + +**Default:** No secure protocol requirement. + +**Examples:** + +```js +Snowboard.cookie().set('name', 'value', { secure: true }) +Snowboard.cookie().get('name') // => 'value' +Snowboard.cookie().remove('name') +``` + + +#### sameSite + +A [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), allowing to control whether the browser is sending a cookie along with cross-site requests. + +Default: not set. + +>**NOTE:** More recent browsers are making "Lax" the default value even without specifiying anything here. + +**Examples:** + +```js +Snowboard.cookie().set('name', 'value', { sameSite: 'strict' }) +Snowboard.cookie().get('name') // => 'value' +Snowboard.cookie().remove('name') +``` + + +#### Setting up defaults + +```js +const Cookies = Snowboard.cookie().withAttributes({ path: '/', domain: '.example.com' }) +Cookies.set('example', 'value'); +``` + + +### Converters + + +#### Read + +Create a new instance that overrides the default decoding implementation. All get methods that rely in a proper decoding to work, such as `Snowboard.cookie().get()` and `Snowboard.cookie().get('name')`, will run the given converter for each cookie. The returned value will be used as the cookie value. + +Example from reading one of the cookies that can only be decoded using the `escape` function: + +```js +document.cookie = 'escaped=%u5317'; +document.cookie = 'default=%E5%8C%97'; +var Cookies = Snowboard.cookie().withConverter({ + read: function (value, name) { + if (name === 'escaped') { + return unescape(value); + } + // Fall back to default for all other cookies + return Snowboard.cookie().converter.read(value, name); + } +}) +Cookies.get('escaped') // 北 +Cookies.get('default') // 北 +Cookies.get() // { escaped: '北', default: '北' } +``` + + +#### Write + +Create a new instance that overrides the default encoding implementation: + +```js +var Cookies = Snowboard.cookie().withConverter({ + write: function (value, name) { + return value.toUpperCase(); + } +}); +``` + + +## Debounce + +... + + +## JSON Parser + +The JSON Parser utility is used to safely parse JSON-like (JS-object strings) data that does not strictly meet the JSON specifications. It is especially useful for parsing the values provided in the `data-request-data` attribute used by the [Data Attributes](data-attributes) functionality. + +This is somewhat similar to [JSON5](https://json5.org/) or [RJSON](http://www.relaxedjson.org/), but not exactly. The key aspect is that it allows for data represented as a JavaScript Object in string form as if it was actively running JS to be parsed without the use of `eval()` which could cause issues with Content Security Policies that block the use of `eval()`. + +>**NOTE:** Although this functionality is documented it is unlikely that regular developers will ever have need of interacting with this feature. + +### Usage: + +```js +let data = "key: value, otherKey: 'other value'; +let object = Snowboard.jsonParser().parse(`{${data}}`); +``` + + +## Sanitizer + +The Sanitizer utility is a client-side HTML sanitizer designed mostly to prevent self-XSS attacks. Such an attack could look like a user copying content from a website that uses clipboard injection to hijack the values actually stored in the clipboard and then having the user paste the content into an environment where the content would be treated as HTML, typically in richeditor / WYSIWYG fields. + +The sanitizer utility will strip all attributes that start with `on` (usually JS event handlers as attributes, i.e. `onload` or `onerror`) or contain the `javascript:` pseudo protocol in their values. + +It is available both as a global function (`wnSanitize(html)`) and as a Snowboard plugin. + +The following example shows how the Froala WYSIWYG editor can be hooked into to protect against a clipboard injection / self-XSS attack. + +```js +$froalaEditor.on('froalaEditor.paste.beforeCleanup', function (ev, editor, clipboard_html) { + return Snowboard.sanitizer().sanitize(clipboard_html); +}); +``` \ No newline at end of file From 3020a89bb2b7b12bbf71b9f98540f840bbe54d1d Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 19 Jan 2022 15:46:40 -0600 Subject: [PATCH 14/36] WIP --- snowboard-introduction.md | 15 ++++++++------- snowboard-plugin-development.md | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index 6b0c2640..9e40ae87 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -1,13 +1,14 @@ -# Winter JavaScript Framework (Snowboard) +# Snowboard.js - Winter JavaScript Framework - [Introduction](#introduction) - [Features](#features) - [Including the framework](#framework-script) +- [Concepts](#concepts) ## Introduction -Winter includes a new, *optional*, JavaScript framework called **Snowboard**, which acts as an upgrade to the previous [AJAX Framework](../ajax/introduction) and provides many new useful features in an extensible fashion, whilst dropping previous hard dependencies to supercharge your projects even further. +Winter includes an optional JavaScript framework called **Snowboard**, which acts as an upgrade to the previous [AJAX Framework](../ajax/introduction) and provides many new useful features in an extensible fashion, whilst dropping previous hard dependencies to supercharge your projects even further. The framework takes advantage of the incredible enhancements made to the JavaScript ecosystem in recent years to provide a unique experience, available only on Winter. @@ -15,8 +16,8 @@ The framework takes advantage of the incredible enhancements made to the JavaScr ## Features - Rewritten AJAX and JavaScript framework, built from the ground-up using the latest JavaScript syntax (ES2015+) and functionality. -- jQuery is no longer a "hard" dependency, allowing the framework to be used across a wide variety of JavaScript frameworks. -- Easy, comprehensive extensibility and event handling in-built into the framework. +- No dependency on jQuery, allowing the framework to be used across a wide variety of JavaScript projects. +- Easy, comprehensive extensibility and event handling. - Small footprint and full control over which core functionalities to include ensures your website loads quick. @@ -24,9 +25,9 @@ The framework takes advantage of the incredible enhancements made to the JavaScr > Before proceeding, please read the [Migration Guide](../snowboard/migration-guide), especially if you intend to use this framework on an existing project. -Snowboard can be optionally included in your [CMS theme](../cms/themes). To use the framework, you should include it by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners, and should also be located before the `{% scripts %}` tag to allow [plugins](../snowboard/plugin-development) to provide Snowboard plugins if they wish. +Snowboard can be included in your [CMS theme](../cms/themes) by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners, and it should also be located before the `{% scripts %}` tag to allow third party code (i.e. [Winter Plugins](../plugin/registration#Introduction)) to provide [Snowboard plugins](plugin-development) if they wish. -By default only the base Snowboard framework is included by the `{% snowboard %}` token in order to allow for complete control over which additional features (such as the AJAX framework) are desired to be included in your themes. +By default, only the base Snowboard framework is included by the `{% snowboard %}` token in order to allow for complete control over which additional features (such as the AJAX framework) are desired to be included in your themes. You can then specify further attributes to the tag to include optional additional functionality for the framework: @@ -53,4 +54,4 @@ Or to include both the JavaScript AJAX and HTML data attribute request functiona ```twig {% snowboard request attr %} -``` \ No newline at end of file +``` diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md index 54e6ded7..1cb3c401 100644 --- a/snowboard-plugin-development.md +++ b/snowboard-plugin-development.md @@ -1,6 +1,28 @@ # Snowboard Plugin Development - [Introduction](#introduction) +- [Framework Concepts](#concepts) ## Introduction + +... + + +## Framework Concepts + +The four cornerstones of this framework are as follows: + +### The `Snowboard` class +Represents the global container for the application. This stores all activated plugins and provides instances of these modules as required. It is synonymous with the `Application` class of Laravel, or a `Vue` instance. + +### The `PluginLoader` class +The main conduit between a plugin and the Snowboard app. This provides the mechanism for delivering instances of each module (or a single instance of Singleton plugin) and resolves dependencies. + +### The `PluginBase` class +A plugin is a specific extension to the Snowboard application. It can contain code that provides new functionality or augments other functionality already registered in the application. A plugin can be reused - in essence, each call to the plugin will create a new instance and will be independent from other instances. This would be used for scenarios such as Flash messages, AJAX requests and the like. + +### The `Singleton` class +An extension of the `PluginBase` class. It signifies to the application that this plugin should exist once, and all uses of this plugin should use the same instance. This would be useful for things like event handlers, extending functionality of another plugin or global functionality. + +A singleton is initialized automatically when the DOM is ready, if it is called directly or if it listens to an event that is fired. The singleton instance is then used no matter how many times it is called from the Snowboard class. From 7c0160ed149779993cde69b80ca84848861e3ca3 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 19 Jan 2022 15:53:06 -0600 Subject: [PATCH 15/36] styling --- snowboard-data-attributes.md | 15 +++++++++------ snowboard-request.md | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/snowboard-data-attributes.md b/snowboard-data-attributes.md index 35238a4e..af623de3 100644 --- a/snowboard-data-attributes.md +++ b/snowboard-data-attributes.md @@ -7,10 +7,7 @@ ## Introduction -The Data Attributes API is the more simple way of embedding AJAX functionality in your themes and plugins, and removes -the need to be experienced with JavaScript. While the [JavaScript API](../snowboard/request) has had numerous changes -from the original [AJAX framework](../ajax/introduction), the Data Attributes API has remain largely unchanged, whilst -still being powered by the new JavaScript functionality under the hood. +The Data Attributes API is the simpler way of embedding AJAX functionality in your themes and plugins, and removes the need to be experienced with JavaScript. While the [JavaScript API](../snowboard/request) has had numerous changes from the original [AJAX framework](../ajax/introduction), the Data Attributes API has remain largely unchanged, despite being powered by the new Snowboard framework under the hood. It can be loaded by adding the following tag into your CMS Theme's page or layout: @@ -18,12 +15,18 @@ It can be loaded by adding the following tag into your CMS Theme's page or layou {% snowboard request attr %} ``` -> **Note:** As per the [Migration Guide](../snowboard/migration-guide), arbitrary JavaScript is no longer allowed through the Data Attributes API. Thus, the `data-request-before-update`, `data-request-success`, `data-request-error` and `data-request-complete` attributes are no longer supported. Please use the [JavaScript API](../snowboard/request) if you require this functionality. +> **NOTE:** As per the [Migration Guide](../snowboard/migration-guide), arbitrary JavaScript is no longer allowed through the Data Attributes API. Thus, the `data-request-before-update`, `data-request-success`, `data-request-error` and `data-request-complete` attributes are no longer supported. Please use the [JavaScript API](../snowboard/request) if you require this functionality. ## Available Data Attributes -Triggering an AJAX request from a valid element is as simple as adding the `data-request` attribute to that element. This generally should be done on a button, link or form. You can also customize the AJAX request using the following attributes: +Triggering an AJAX request from a valid element is as simple as adding the `data-request` attribute to that element. This generally should be done on a button, link, or form. You can also customize the AJAX request using the following attributes: + + +
Attribute | Description --------- | ----------- diff --git a/snowboard-request.md b/snowboard-request.md index 6ce84c2f..b33a2cf4 100644 --- a/snowboard-request.md +++ b/snowboard-request.md @@ -23,10 +23,16 @@ And can be called using the following code in your JavaScript: Snowboard.request('#element', 'onAjax', {}); ``` -The base `Request` class, by default, uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided in most modern browsers to execute AJAX requests from the frontend to the backend in Winter. +The base `Request` class, by default, uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided in most modern browsers to execute AJAX requests from the frontend to the backend in Winter. The `request` method takes three parameters: + +
+ Parameter | Required | Description --------- | -------- | ----------- `element` | No | The element that this AJAX request is targeting, either as a `HTMLElement` instance, or as a CSS-selector string. This can be any element, but usually will be used with a `form` element. @@ -54,6 +60,12 @@ AJAX requests made through the `Request` class go through the following process: All options below are optional. + +
+ Option | Type | Description ------ | ---- | ----------- `confirm` | `string` | If provided, the user will be prompted with this confirmation message, and will be required to confirm if they wish to proceed with the request. @@ -77,6 +89,12 @@ Callback | Description Finally, the following option parameters define override functionality for various actions that the `Request` instance may take during the processing of a response. As with the callback methods, these must be provided a function. + +
+ Option | Description ------ | ----------- `handleConfirmMessage` | Defines a custom handler for any confirmations requested of the user. It will be provided one parameter: the confirmation message, as a string. @@ -101,7 +119,7 @@ class HandleFlash extends Snowboard.Singleton listens() { return { // when the "ajaxFlashMessages" event is called, run the "doFlashMessage" method in this class - ajaxFlashMessages: 'doFlashMessages', + ajaxFlashMessages: 'doFlashMessages', }; } @@ -128,7 +146,7 @@ class ConfirmEverything extends Snowboard.Singleton listens() { return { // when the "ajaxConfirmMessage" event is called, run the "confirm" method in this class - ajaxConfirmMessage: 'confirm', + ajaxConfirmMessage: 'confirm', }; } @@ -141,6 +159,12 @@ class ConfirmEverything extends Snowboard.Singleton The following events are called during the Request process: + +
+ Event | Promise? | Description ----- | -------- | ----------- `ajaxSetup` | No | Called after the Request is initialized and checked that it can be called. It is provided one parameter: the `Request` instance. It is intended to be used for modifying the Request parameters before sending to the server. Returning `false` in any event listeners will cancel the request. @@ -184,6 +208,12 @@ element.addEventListener('ajaxSetup', (event) => { The following events are called during the Request process directly on the element that triggered the request: + +
+ Event | Description ----- | ----------- `ajaxSetup` | Called after the Request is initialized and checked that it can be called. From 4640093fac0a88138a9ccc26d51ead90bcf2f9b4 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 19 Jan 2022 16:05:14 -0600 Subject: [PATCH 16/36] Reorganizing --- snowboard-extras.md | 122 ++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/snowboard-extras.md b/snowboard-extras.md index b600ac92..e120bf21 100644 --- a/snowboard-extras.md +++ b/snowboard-extras.md @@ -1,19 +1,19 @@ -# Extra Features +# Extra UI Features - [Introduction](#introduction) - [Loading indicator](#loader-stripe) +- [Loading button](#loader-button) +- [Flash messages](#ajax-flash) - [Form validation](#ajax-validation) - [Throwing a validation error](#throw-validation-exception) - [Displaying error messages](#error-messages) - [Displaying errors with fields](#field-errors) -- [Loading button](#loader-button) -- [Flash messages](#ajax-flash) -- [Usage examples](#usage-examples) + - [Usage examples](#usage-examples) ## Introduction -When using the Snowboard framework, you have the option to specify the **extras** flag which includes additional features. These features are often useful when working with AJAX requests in frontend CMS pages. +When using the Snowboard framework, you have the option to specify the `extras` flag which includes additional UI features. These features are often useful when working with AJAX requests in frontend CMS pages. ```twig {% snowboard extras %} @@ -26,6 +26,61 @@ The loading indicator is a loading bar that is displayed on the top of the page When an AJAX request starts, the `ajaxPromise` event is fired. This displays the loading indicator at the top of the page. When this promise is resolved, the loading bar is removed. + +## Loading button + +When any element contains the `data-attach-loading` attribute, the CSS class `wn-loading` will be added to it during the AJAX request. This class will spawn a *loading spinner* on button and anchor elements using the `:after` CSS selector. + +```html + + + + + + Do something + +``` + + +## Flash messages + +Specify the `data-request-flash` attribute on a form to enable the use of flash messages on successful AJAX requests. + +```html +
+ +
+``` + +Combined with use of the `Flash` facade in the event handler, a flash message will appear after the request finishes. + +```php +function onSuccess() +{ + Flash::success('You did it!'); +} +``` + +When using AJAX Flash messages you should also ensure that your theme supports [standard flash messages](../markup/tag-flash) by placing the following code in your page or layout in order to render Flash messages that haven't been displayed yet when the page loads. + +```twig +{% flash %} +

+ {{ message }} +

+{% endflash %} +``` + ## Form validation @@ -93,63 +148,8 @@ If the element is left empty, it will be populated with the validation text from ``` - -## Loading button - -When any element contains the `data-attach-loading` attribute, the CSS class `wn-loading` will be added to it during the AJAX request. This class will spawn a *loading spinner* on button and anchor elements using the `:after` CSS selector. - -```html -
- -
- - - Do something - -``` - - -## Flash messages - -Specify the `data-request-flash` attribute on a form to enable the use of flash messages on successful AJAX requests. - -```html -
- -
-``` - -Combined with use of the `Flash` facade in the event handler, a flash message will appear after the request finishes. - -```php -function onSuccess() -{ - Flash::success('You did it!'); -} -``` - -To remain consistent with AJAX based flash messages, you can render a [standard flash message](../markup/tag-flash) when the page loads by placing this code in your page or layout. - -```twig -{% flash %} -

- {{ message }} -

-{% endflash %} -``` - -## Usage examples +### Usage examples Below is a complete example of form validation. It calls the `onDoSomething` event handler that triggers a loading submit button, performs validation on the form fields, then displays a successful flash message. From 2b1a10a315d76b60772fba70b51e0dabb93e8431 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 19 Jan 2022 16:19:42 -0600 Subject: [PATCH 17/36] Tweaks --- snowboard-handlers.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/snowboard-handlers.md b/snowboard-handlers.md index e9ae39b7..3c67f8ad 100644 --- a/snowboard-handlers.md +++ b/snowboard-handlers.md @@ -29,14 +29,16 @@ If two handlers with the same name are defined in a page and layout together, th ### Calling a handler -Every AJAX request should specify a handler name. When the request is made, the server will search all the registered handlers and locates the prioritised handler. +Every AJAX request should specify a handler name. When the request is made, the server will search all the registered handlers and run the handler with the highest priority. ```html - + ``` If two components register the same handler name, it is advised to prefix the handler with the [component short name or alias](../cms/components#aliases). If a component uses an alias of **mycomponent** the handler can be targeted with `mycomponent::onName`. @@ -45,7 +47,7 @@ If two components register the same handler name, it is advised to prefix the ha ``` -You may want to use the [`__SELF__`](../plugin/components#referencing-self) reference variable instead of the hard coded alias in case the user changes the component alias used on the page. +You should use the [`__SELF__`](../plugin/components#referencing-self) variable instead of the hard coded alias in order to support multiple instances of your component existing on the same page. ```twig
@@ -53,10 +55,24 @@ You may want to use the [`__SELF__`](../plugin/components#referencing-self) refe ### Generic handler -Sometimes you may need to make an AJAX request for the sole purpose of updating page contents, not needing to execute any code. You may use the `onAjax` handler for this purpose. This handler is available everywhere without needing to write any code. +Sometimes you may need to make an AJAX request for the sole purpose of updating page contents by pulling partial updates without executing any code. You may use the `onAjax` handler for this purpose. This handler is available everywhere the AJAX framework can respond. -```html - +#### `clock.htm` Partial +```twig +The time is {{ 'now' | date('H:i:s') }} +``` + +#### `index.htm` Page +```twig + + {% partial 'clock' %} + + ``` From 7dc6be9bbb5848fa032c524623d03b7e4bf92843 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 19 Jan 2022 16:43:43 -0600 Subject: [PATCH 18/36] wip --- snowboard-request.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/snowboard-request.md b/snowboard-request.md index b33a2cf4..c0012144 100644 --- a/snowboard-request.md +++ b/snowboard-request.md @@ -11,19 +11,21 @@ ## Introduction -Snowboard provides core AJAX functionality via the `Request` JavaScript class. The `Request` class provides powerful flexibility and reusability in making AJAX Requests with the Backend functionality in Winter. It can be loaded by adding the following tag into your CMS Theme's page or layout: +Snowboard provides core AJAX functionality via the `Request` Snowboard plugin. The `Request` plugin provides powerful flexibility and reusability in making AJAX Requests with the Backend functionality in Winter. It can be loaded by adding the following tag into your CMS Theme's page or layout: ```twig {% snowboard request %} ``` -And can be called using the following code in your JavaScript: +And called using the following code in your JavaScript: ```js Snowboard.request('#element', 'onAjax', {}); ``` -The base `Request` class, by default, uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided in most modern browsers to execute AJAX requests from the frontend to the backend in Winter. +The base `Request` plugin uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided in most modern browsers to execute AJAX requests from the frontend to the backend in Winter. + +>**NOTE:** If you would like to replace any part of the functionality of the base `Request` plugin then you can write a custom [Snowboard Plugin](plugin-development) that extends and overrides the base `Request` plugin to customize it as desired. The `request` method takes three parameters: @@ -78,7 +80,7 @@ Option | Type | Description `update` | `Object` | Specifies a list of partials and page elements that can be changed through the AJAX response. The key of the object represents the partial name and the value represents the page element (as a CSS selector) to target for the update. If the selector string is prepended with an `@` symbol, the content will be appended to the target. If the selector string is prepended with a `^` symbol, it will instead be prepended to the target. `fetchOptions` | `Object` | If specified, this will override the options used with the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to make the request. -The following callbacks may also be specified in the `options` parameter. All callbacks expect a function to be provided. The `this` keyword inside all callbacks will be assigned the `Request` instance that represents this AJAX request. +The following callbacks may also be specified in the `options` parameter. All callbacks expect a function to be provided. The `this` keyword inside all callbacks will be assigned the `Request` instance that represents the current AJAX request. Callback | Description -------- | ----------- @@ -87,7 +89,7 @@ Callback | Description `error` | Executes when the AJAX request fails due to a server-side error or validation error. The function receives two parameters: the response data from the AJAX response as an object, and the `Request` instance. `complete` | Executes when the AJAX request is complete, regardless of success or failure. The function receives two parameters: the response data from the AJAX response as an object, and the `Request` instance. -Finally, the following option parameters define override functionality for various actions that the `Request` instance may take during the processing of a response. As with the callback methods, these must be provided a function. +Finally, the following option parameters define override functionality for various actions that the `Request` instance may take during the processing of a response. As with the callback methods, these must be provided as a function.
-Option | Description ------- | ----------- -`handleConfirmMessage` | Defines a custom handler for any confirmations requested of the user. It will be provided one parameter: the confirmation message, as a string. -`handleErrorMessage` | Defines a custom handler for any errors occuring during the request. It will be provided one parameter: the error message, as string. -`handleValidationMessage` | Defines a custom handler for validation errors occurring during the request. It will be provided two parameters: the first being the message returned, as a string. The second will be an object, with field names as the key and messages as the value. -`handleFlashMessage` | Defines a custom handler for flash messages. It will be provided two parameters: the message of the flash message as a string, and the type of flash message as a string. -`handleRedirectResponse` | Defines a custom handler for any redirects. It will be provided one parameter: the URL to redirect to, as a string. +Option | Parameters | Description +------ | ---------- | ----------- +`handleConfirmMessage` | `(string) confirmationMessage` | Handles any confirmations requested of the user. +`handleErrorMessage` | `(string) errorMessage` | Handles any errors occuring during the request +`handleValidationMessage` | `(string) message, (Object) fieldMessages` | Handles validation errors occurring during the request. `fieldMessages` has field names as the key and messages as the value. +`handleFlashMessage` | `(string) message, (string) type` | Handles flash messages. +`handleRedirectResponse` | `(string) redirectUrl` | Handles redirect responses. ## Global events @@ -167,22 +167,22 @@ The following events are called during the Request process:
-Event | Promise? | Description ------ | -------- | ----------- -`ajaxSetup` | No | Called after the Request is initialized and checked that it can be called. It is provided one parameter: the `Request` instance. It is intended to be used for modifying the Request parameters before sending to the server. Returning `false` in any event listeners will cancel the request. -`ajaxConfirmMessage` | Yes | Called if `confirm` is specified, and the Request is ready to be sent to the server. This allows developers to customise the confirmation process or display. It is provided two parameters: the `Request` instance, and the confirmation message as a string. If an event listener rejects the Promise, this will cancel the request. -`ajaxBeforeSend` | No | Called immediately before the Request is sent. It can be used for final changes to the Request, or cancelling it prematurely. It is provided one parameter: the `Request` instance. Returning `false` in any event listeners will cancel the request. -`ajaxFetchOptions` | No | Called immediately when the `Fetch API` is initialised to make the request. It can be used to modify the Fetch options via a plugin. It is provided two parameters: the current Fetch options, as an object, and the `Request` instance. This event cannot be cancelled. -`ajaxStart` | No | Called when the Request is sent. It is provided two parameters: a Promise tied to the AJAX call that is resolved or rejected by the response, and the `Request` instance. This event cannot be cancelled. -`ajaxBeforeUpdate` | Yes | Called when a successful response is returned and partials are going to be updated. It is provided two parameters: the raw response data, and the `Request` instance. It can be used to determine which partials will be updated, or can also be used to cancel the partial updates. If an event listener rejects the Promise, no partials will be updated. -`ajaxUpdate` | No | Called when an individual partial is updated. It is provided three parameters: the element that is updated, the content that has been added to the element, and the `Request` instance. It can be used to make further updates to an element, or handle updates. Note that this event is fired *after* the element is updated. This event cannot be cancelled. -`ajaxUpdateComplete` | No | Called when the partials are updated. It is provided two parameters: an array of elements that have been updated, and the `Request` instance. It can be used to determine which partials have been updated. This event cannot be cancelled. -`ajaxSuccess` | No | Called when a successful response is returned and all partial updating is completed. It is provided two parameters: the raw response data as an object, and the `Request` instance. It can be used to cancel further response handling (ie. redirects, flash messages). Returning `false` in any event listeners will prevent any further response handling from taking place. -`ajaxError` | No | Called when an unsuccessful response is returned from the AJAX request. It is provided two parameters: the raw response data as an object, and the `Request` instance. It can be used to cancel further error handling. Returning `false` in any event listeners will prevent any further response handling from taking place. -`ajaxRedirect` | No | Called when a redirect is to take place, either from the response or through the `redirect` option. It is provided two parameters: the URL to redirect to as a string, and the `Request` instance. Returning `false` in any event listeners will prevent the redirect from executing. -`ajaxErrorMessage` | No | Called when an error message is to be shown to the user. It is provided two parameters: the error message as a string, and the `Request` instance. Returning `false` in any event listeners will prevent the default error handling (showing an alert to the user) from executing. -`ajaxFlashMessages` | No | Called when one or more flash messages are to be shown to the user. It is provided two parameters: the flash messages as an array of objects, and the `Request` instance. There is no default functionality for flash messages, so if no event listeners trigger for this event, no activity will occur. -`ajaxValidationErrors` | No | Called when a validation error is returned in the response. It is provided three parameters: the form where validation failed as a `HTMLElement`, an array of fields and messages within the form, and the `Request` instance. There is no default functionality for validation errors, so if no event listeners trigger for this event, no activity will occur. +Event | Promise? | Parameters | Description +----- | -------- | ---------- | ----------- +`ajaxSetup` | No | `(Request) request` | Called after the Request is initialized and checked that it can be called. It is intended to be used for modifying the Request parameters before sending to the server. Returning `false` in any event listeners will cancel the request. +`ajaxConfirmMessage` | Yes | `(Request) request, (string) confirmationMessage` | Called if `confirm` is specified, and the Request is ready to be sent to the server. This allows developers to customise the confirmation process or display. If an event listener rejects the Promise, this will cancel the request. +`ajaxBeforeSend` | No | `(Request) request` | Called immediately before the Request is sent. It can be used for final changes to the Request, or cancelling it prematurely. Returning `false` in any event listeners will cancel the request. +`ajaxFetchOptions` | No | `(Object) fetchOptions, (Request) request` | Called immediately when the `Fetch API` is initialised to make the request. It can be used to modify the Fetch options via a plugin. This event cannot be cancelled. +`ajaxStart` | No | `(Promise) callback, (Request) request` | Called when the Request is sent. This event cannot be cancelled. +`ajaxBeforeUpdate` | Yes | `(mixed) response, (Request) request` | Called when a successful response is returned and partials are going to be updated. It can be used to determine which partials will be updated, or can also be used to cancel the partial updates. If an event listener rejects the Promise, no partials will be updated. +`ajaxUpdate` | No | `(HTMLElement) element, (string) content, (Request) request` | Called when an individual partial is updated. It can be used to make further updates to an element, or handle updates. Note that this event is fired *after* the element is updated. This event cannot be cancelled. +`ajaxUpdateComplete` | No | `(array of HTMLElement) elements, (Request) request)` | Called when the partials are updated. It can be used to determine which partials have been updated. This event cannot be cancelled. +`ajaxSuccess` | No | `(Object) responseData, (Request) request` | Called when a successful response is returned and all partial updating is completed. It can be used to cancel further response handling (ie. redirects, flash messages). Returning `false` in any event listeners will prevent any further response handling from taking place. +`ajaxError` | No | `(Object) responseData, (Request) request` | Called when an unsuccessful response is returned from the AJAX request. It can be used to cancel further error handling. Returning `false` in any event listeners will prevent any further response handling from taking place. +`ajaxRedirect` | No | `(string) redirectUrl, (Request) request` | Called when a redirect is to take place, either from the response or through the `redirect` option. Returning `false` in any event listeners will prevent the redirect from executing. +`ajaxErrorMessage` | No | `(string) message, (Request) request` | Called when an error message is to be shown to the user. Returning `false` in any event listeners will prevent the default error handling (showing an alert to the user) from executing. +`ajaxFlashMessages` | No | `(array of Object) flashMessages, (Request) request` | Called when one or more flash messages are to be shown to the user. There is no default functionality for flash messages, so if no event listeners trigger for this event, no activity will occur. +`ajaxValidationErrors` | No | `(HTMLElement) form, (array) fieldMessages, (Request) request` | Called when a validation error is returned in the response. There is no default functionality for validation errors, so if no event listeners trigger for this event, no activity will occur. ## Element events From 04f23ba1182422f63f0a28546837272604d77f83 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 21 Jan 2022 17:02:22 -0600 Subject: [PATCH 20/36] More WIP on Snowboard docs --- snowboard-plugin-development.md | 2 ++ snowboard-request.md | 2 +- snowboard-utilities.md | 34 ++++++++++++++++++--------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md index 1cb3c401..b519c4b1 100644 --- a/snowboard-plugin-development.md +++ b/snowboard-plugin-development.md @@ -8,6 +8,8 @@ ... +**NOTE:** Document `Snowboard.debug()` method. + ## Framework Concepts diff --git a/snowboard-request.md b/snowboard-request.md index 04a17ce6..714fe878 100644 --- a/snowboard-request.md +++ b/snowboard-request.md @@ -187,7 +187,7 @@ Event | Promise? | Parameters | Description ## Element events -In addition to global events, local events are fired on elements that trigger an AJAX request. These events are treated as [DOM events](https://developer.mozilla.org/en-US/docs/Web/API/Event) and thus can be listened to by normal DOM event listeners or your framework of choice. The `Request` class will inject properties in the event depending on the type of event, however, the `event.request` property will always be the `Request` instance. +In addition to global events, local events are fired on elements that trigger an AJAX request. These events are treated as [DOM events](https://developer.mozilla.org/en-US/docs/Web/API/Event) and thus can be listened to by normal DOM event listeners or your framework of choice. The `Request` class will inject properties in the event depending on the type of event and the `event.request` property will always be the `Request` instance. ```js const element = document.getElementById('my-button'); diff --git a/snowboard-utilities.md b/snowboard-utilities.md index 80af2374..251d39d1 100644 --- a/snowboard-utilities.md +++ b/snowboard-utilities.md @@ -14,7 +14,6 @@ - [Converters](#cookie-converters) - [Read](#cookie-converters-read) - [Write](#cookie-converters-write) -- [Debounce](#debounce) - [JSON Parser](#json-parser) - [Sanitizer](#sanitizer) @@ -185,22 +184,29 @@ Snowboard.cookie().remove('name') #### Setting up defaults +In order to set global defaults that are used for every cookie that is created with the `Snowboard.cookie().set('name', 'value')` method you can call the `setDefaults(options)` method on the Cookie plugin and it will set the provided options as the global defaults. + +If you want to get the current defaults, call `Snowboard.cookie().getDefaults()`. + ```js -const Cookies = Snowboard.cookie().withAttributes({ path: '/', domain: '.example.com' }) -Cookies.set('example', 'value'); +Snowboard.cookie().setDefaults({ path: '/', domain: '.example.com' }); +Snowboard.cookie().set('example', 'value'); ``` - -### Converters + +### Events + +The Cookie plugin provides the ability to interact with cookies and modify their values during accessing or creating. - -#### Read + +#### `cookie.get` -Create a new instance that overrides the default decoding implementation. All get methods that rely in a proper decoding to work, such as `Snowboard.cookie().get()` and `Snowboard.cookie().get('name')`, will run the given converter for each cookie. The returned value will be used as the cookie value. +This event runs during `Snowboard.cookie().get()` and provides the `(string) name` & `(string) value` parameters. Returning a result from your listener will replace the original result in the results of `Snowboard.cookie().get()`. Example from reading one of the cookies that can only be decoded using the `escape` function: ```js +// TODO: ADJUST EXAMPLE HERE TO USE LISTENER document.cookie = 'escaped=%u5317'; document.cookie = 'default=%E5%8C%97'; var Cookies = Snowboard.cookie().withConverter({ @@ -217,12 +223,15 @@ Cookies.get('default') // 北 Cookies.get() // { escaped: '北', default: '北' } ``` - -#### Write + +#### `cookie.set` + +This event runs during `Snowboard.cookie().set()` and provides the `(string) name` & `(string) value` parameters. Returning a result from your listener will replace the original value in the call to `Snowboard.cookie().set()` and will be the value actually stored for the cookie. Create a new instance that overrides the default encoding implementation: ```js +// TODO: ADJUST EXAMPLE HERE TO USE LISTENER var Cookies = Snowboard.cookie().withConverter({ write: function (value, name) { return value.toUpperCase(); @@ -230,11 +239,6 @@ var Cookies = Snowboard.cookie().withConverter({ }); ``` - -## Debounce - -... - ## JSON Parser From 7daf2b32fdb51afca7114f3537534aaae0ea3966 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 21 Jan 2022 17:04:45 -0600 Subject: [PATCH 21/36] TOC fix --- snowboard-utilities.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/snowboard-utilities.md b/snowboard-utilities.md index 251d39d1..804d6de0 100644 --- a/snowboard-utilities.md +++ b/snowboard-utilities.md @@ -11,9 +11,9 @@ - [secure](#cookie-attributes-secure) - [sameSite](#cookie-attributes-sameSite) - [Setting Defaults](#cookie-attributes-defaults) - - [Converters](#cookie-converters) - - [Read](#cookie-converters-read) - - [Write](#cookie-converters-write) + - [Cookie Events](#cookie-events) + - [`cookie.get`](#cookie-event-get) + - [`cookie.set`](#cookie-event-set) - [JSON Parser](#json-parser) - [Sanitizer](#sanitizer) From 0894c8bb60cb935ebef8a8ab1b8267495bde699a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 28 Jan 2022 08:36:01 +0800 Subject: [PATCH 22/36] WIP Snowboard plugin development --- snowboard-introduction.md | 4 +- snowboard-plugin-development.md | 92 ++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/snowboard-introduction.md b/snowboard-introduction.md index 9e40ae87..a0ef7089 100644 --- a/snowboard-introduction.md +++ b/snowboard-introduction.md @@ -27,9 +27,9 @@ The framework takes advantage of the incredible enhancements made to the JavaScr Snowboard can be included in your [CMS theme](../cms/themes) by placing the `{% snowboard %}` tag anywhere inside your [page](../cms/pages) or [layout](../cms/layouts) where you would like the JavaScript assets to be loaded - generally, this should be at the bottom of the page before the closing `` tag. You must use this tag *before* you load any assets that rely on the framework, such as plugins or event listeners, and it should also be located before the `{% scripts %}` tag to allow third party code (i.e. [Winter Plugins](../plugin/registration#Introduction)) to provide [Snowboard plugins](plugin-development) if they wish. -By default, only the base Snowboard framework is included by the `{% snowboard %}` token in order to allow for complete control over which additional features (such as the AJAX framework) are desired to be included in your themes. +By default, only the base Snowboard framework and [necessary utilties](../snowboard/utilities) are included by the `{% snowboard %}` token in order to allow for complete control over which additional features (such as the AJAX framework) are desired to be included in your themes. -You can then specify further attributes to the tag to include optional additional functionality for the framework: +You can specify further attributes to the tag to include optional additional functionality for the framework: Attribute | Includes --------- | -------- diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md index b519c4b1..91edacca 100644 --- a/snowboard-plugin-development.md +++ b/snowboard-plugin-development.md @@ -2,29 +2,93 @@ - [Introduction](#introduction) - [Framework Concepts](#concepts) + - [The Snowboard class](#snowboard-class) + - [The PluginLoader class](#plugin-loader-class) + - [The PluginBase and Singleton abstracts](#plugin-base-singleton) + - [Global events](#global-events) ## Introduction -... +The Snowboard framework has been designed to be extensible and customisable for the needs of your project. To this end, the following documentation details the concepts of the framework, and how to develop your own functionality to extend or replace features within Snowboard. -**NOTE:** Document `Snowboard.debug()` method. - - + ## Framework Concepts -The four cornerstones of this framework are as follows: +Snowboard works on the concept of an encompassing application, which acts as the base of functionality and is then extended through plugins. The main method of communication and functionality is through the use of [JavaScript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) which offer instances and extendability, and global events - a feature in-built into Snowboard. + +The following classes and abstracts are included in the Snowboard framework. + + +### The Snowboard class + +The Snowboard class is the representation of the application. It is the main point of adding, managing and accessing functionality within the framework, synonymous to using `jQuery` or `Vue` in your scripts. It is injected into the global JavaScript scope, and can be accessed through the `Snowboard` variable anywhere within the application after the `{% snowboard %}` tag is used. + +In addition, Snowboard is injected into all plugin classes that are used as the entry point for a plugin, and can be accessed via `this.snowboard` inside an entry plugin class. + +```js +// All these should work to use Snowboard globally +Snowboard.getPlugins(); +snowboard.getPlugins(); +window.Snowboard.getPlugins(); + +// In your plugin, Snowboard can also be accessed as a property. +class MyPlugin extends PluginBase { + myMethod() { + this.snowboard.getPlugins(); + } +} +``` + +The Snowboard class provides the following public API for use in managing plugins and calling global events: + +Method | Parameters | Description +------ | ---------- | ----------- +`addPlugin` | name(`String`)
instance(`PluginBase`) | Adds a plugin to the Snowboard framework. The name should be a unique name, unless you intend to replace a pre-defined plugin. The instance should be either a [PluginBase or Singleton](#plugin-base-singleton) instance that represents the "entry" point to your plugin. +`removePlugin` | name(`String`) | Removes a plugin, if it exists. When a plugin is removed, all active instances of the plugin will be destroyed. +`hasPlugin` | name(`String`) | Returns `true` if a plugin with the given name has been added. +`getPlugin` | name(`String`) | Returns the [PluginLoader](#plugin-loader-class) instance for the given plugin, if it exists. If it does not exist, an error will be thrown. +`getPlugins` | | Returns an object of added plugins as [PluginLoader](#plugin-loader-class) instances, keyed by the name of the plugin. +`getPluginNames` | | Returns all added plugins by name as an array of strings. +`listensToEvent` | eventName(`String`) | Returns an array of plugin names as strings for all plugins that listen to the given event name. This works for both Promise and non-Promise [global events](#global-events). +`globalEvent` | eventName(`String`)
*...parameters* | Calls a non-Promise [global event](#global-event). This will trigger event callbacks for all plugins listening to the given event. This method can be provided additional parameters that will be forwarded through to the event callbacks. This method returns `false` if the event was cancelled by a plugin. +`globalPromiseEvent` | eventName(`String`)
*...parameters* | Calls a Promise [global event](#global-event). This will trigger event callbacks for all plugins listening to the given event. This method can be provided additional parameters that will be forwarded through to the event callbacks. This method returns a Promise that will either be resolved or rejected depending on the response from the event callbacks of the plugins. +`debug` | *...parameters* | When the application is in debug mode, this method logs debug messages to the console. Each log message can display one or more parameters at the same time, and includes a trace of the entire call stack up to when the debug call was made. + +#### Debugging in Snowboard + +As indicated just prior, the Snowboard class provides a `debug` method that allows developers to easily debug their Snowboard application and plugins. This method only works if the Winter application is in debug mode, by setting `debug` to `true` in the `config/app.php` file. + +Debugging can be called anywhere that the Snowboard class is accessible. + +```js +// Globally +Snowboard.debug('This is a debug message'); + +// Within a plugin +class MyPlugin extends PluginBase { + myMethod() { + this.snowboard.debug('Debugging my plugin', this); + } +} +``` + +In general, you would use the first parameter of the `debug` method to state the debug message. From there, additional parameters can be added to provide additional context. The method will print a collapsed debug message to your developer console on your browser. You may extend the debug message in your console to view a stack trace, showing the entire call stack up to when the debug message was triggered. -### The `Snowboard` class -Represents the global container for the application. This stores all activated plugins and provides instances of these modules as required. It is synonymous with the `Application` class of Laravel, or a `Vue` instance. + +### The PluginLoader class -### The `PluginLoader` class -The main conduit between a plugin and the Snowboard app. This provides the mechanism for delivering instances of each module (or a single instance of Singleton plugin) and resolves dependencies. +The PluginLoader class is the conduit between your application (ie. the [Snowboard class](#snowboard-class)) and the plugins. It acts similar to a "factory", providing and managing instances of the plugins and allowing the Snowboard application to communicate to those instances. It also provides a basic level of mocking, to allow for testing or overwriting individual methods of the plugin dynamically. -### The `PluginBase` class -A plugin is a specific extension to the Snowboard application. It can contain code that provides new functionality or augments other functionality already registered in the application. A plugin can be reused - in essence, each call to the plugin will create a new instance and will be independent from other instances. This would be used for scenarios such as Flash messages, AJAX requests and the like. +Each PluginLoader instance will be representative of one plugin. -### The `Singleton` class -An extension of the `PluginBase` class. It signifies to the application that this plugin should exist once, and all uses of this plugin should use the same instance. This would be useful for things like event handlers, extending functionality of another plugin or global functionality. +In general, you will not need to interact with this class directly - most developer-facing functionality should be done on the Snowboard class or the plugins themselves. Thus, we will only document the methods that *may* be accessed by developers. -A singleton is initialized automatically when the DOM is ready, if it is called directly or if it listens to an event that is fired. The singleton instance is then used no matter how many times it is called from the Snowboard class. +Method | Parameters | Description +------ | ---------- | ----------- +`hasMethod` | methodName(`String`) | Returns `true` if the plugin defines a method by the given name. +`getInstance` | *...parameters* | Returns an instance of the plugin. Please see the **Plugin instantiation** section below for more information. +`getInstances` | | Returns all current instances of the plugin. +`getDependencies` | | Returns an array of the names of all plugins that the current plugin depends on, as strings. +`dependenciesFulfilled` | | Returns `true` if the current plugin's dependencies have been fulfilled. +`mock` | methodName(`String`)
callback(`Function`) | Defines a mock for the current plugin, replacing the given method with the provided callback. \ No newline at end of file From 22f76d7e8899ddf92e33068915384f7b103144cb Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 7 Feb 2022 12:41:22 +0800 Subject: [PATCH 23/36] Add Mix command documentation --- console-asset-compilation.md | 230 +++++++++++++++++++++++++++++++++++ console-introduction.md | 5 + themes-development.md | 1 + 3 files changed, 236 insertions(+) create mode 100644 console-asset-compilation.md diff --git a/console-asset-compilation.md b/console-asset-compilation.md new file mode 100644 index 00000000..4a35f992 --- /dev/null +++ b/console-asset-compilation.md @@ -0,0 +1,230 @@ +# Asset Compilation (Mix) + +- [Introduction](#introduction) +- [Registering a package](#registering-packages) + - [First steps](#first-steps) + - [Plugins](#registering-plugins) + - [Themes](#registering-themes) + - [Mix configuration](#mix-configuration) + - [Examples](#examples) +- [Commands](#commands) + - [Install Node dependencies](#mix-install) + - [List registered Mix packages](#mix-list) + - [Compile a Mix package](#mix-compile) + - [Watch a Mix package](#mix-watch) + + +## Introduction + +Winter brings first-class support for handling Node-based compilation for front-end assets through the Mix commands. The comamnds use the [Laravel Mix](https://laravel-mix.com/) wrapper, a user-friendly and simple interface to setting up compilation of multiple types of front-end asset through Webpack and various libraries. + + +## Registering a package + +Registering for asset compilation through Mix is very easy, and only changes slightly depending on whether the asset compilation is for a plugin or a theme. + + +### First steps + +To take advantage of asset compilation, you must have Node and the Node package manager (NPM) installed in your development environment. This will be dependent on your operating system - please review the [Download NodeJS](https://nodejs.org/en/download/) page for more information on installing Node. + +You will need to create a package configuration file (`package.json`) in your plugin or theme that defines the dependencies that you require for your assets to compile. In the majority of cases, you will only need Laravel Mix as a dependency. + +We recommend that the `package.json` file is located in the root folder of your plugin or theme. You can use the `npm init` command in your CLI to quickly create this file: + +```bash +# For a plugin +cd plugins/author/myplugin +# For a theme +cd themes/mytheme + +# Run the initialization +npm init + +# You will be asked a series of questions: +# - package name: [your theme or plugin name] +# - version: [a version number, default 1.0.0] +# - description: [enter a description for your theme or plugin] +# - entry point: index.js +# - test command: *keep blank* +# - git repository: [enter in your git repository URL, or keep blank] +# - keywords: [enter in some keywords on what your theme or plugin does, or keep blank] +# - author: [enter in your name and email in the format "Your Name "] +# - license: [enter a license - see https://docs.npmjs.com/cli/v8/configuring-npm/package-json#license for licenses] +# - Press ENTER if OK +``` + +Once your `package.json` file is created, you can then add dependencies to it. One that is required for these commands is Laravel Mix: + +```bash +npm i --save-dev laravel-mix +``` + +If you edit the `package.json` file directly, you will need to ensure that the dependencies are installed before using the commands. You can use the [`mix:install`](#mix-install) command to install all dependencies. + +```bash +# In your project root folder +php artisan mix:install +``` + + +### Registering plugins + +To register front-end assets to be compiled through Mix in your plugin, you can use the `System\Classes\MixAssets` class to register the package. The following code can be added to your [`Plugin.php`](../plugin/registration) registration file's `boot()` method to register a callback method that registers the package when asset compilation is processed: + +```php +public function boot() +{ + \System\Classes\MixAssets::registerCallback(function ($mix) { + $mix->registerPackage('', ''); + }); +} +``` + +The `registerPackage()` method takes 3 arguments: the name of the package which should be a kebab-case string that represents your package name, the directory where to find the `package.json` file and the Mix configuration file and an optional third parameter which defines the filename of your Mix configuration file. By default, we use `winter-mix.js` as this filename, but you may rename it to any other filename that you wish and define it as the third argument. + +For example, if you want to register a package called `my-plugin`, and the `package.json` and `winter-mix.js` file can be located in the `plugins/author/myplugin/assets` folder, you would use the following code block: + +```php +public function boot() +{ + \System\Classes\MixAssets::registerCallback(function ($mix) { + $mix->registerPackage('my-plugin', '~/plugins/author/myplugin/assets'); + }); +} +``` + + +### Registering themes + +Registration of asset compilation of themes is even easier, and can be done by adding a `mix` definition to your [theme information file](../themes-development#version-file) (`theme.yaml`). + +```yaml +name: "Winter CMS Demo" +description: "Demonstrates the basic concepts of the frontend theming." +author: "Winter CMS" +homepage: "https://wintercms.com" +code: "demo" + +mix: + : winter-mix.js +``` + +The `mix` definition takes any number of registered packages as a YAML object, with the key being the name of the package as a kebab-case string and the location of your `winter-mix.js` file relative to the theme root directory. + +For example, if you want to register two packages called `demo-theme-style` and `demo-theme-shop` located in the assets folder, you would use the following definition: + +```yaml +name: "Winter CMS Demo" +description: "Demonstrates the basic concepts of the frontend theming." +author: "Winter CMS" +homepage: "https://wintercms.com" +code: "demo" + +mix: + demo-theme-style: assets/style/winter-mix.js + demo-theme-shop: assets/shop/winter-mix.js +``` + + +### Mix configuration + +The Mix configuration file (`winter-mix.js`) is a configuration file that manages the configuration of Laravel Mix itself. In conjunction with the `package.json` file that defines your dependencies, this file defines how Laravel Mix will compile your assets. + +You can [review examples](https://laravel-mix.com/docs/6.0/examples) or the [full Mix API](https://laravel-mix.com/docs/6.0/api) at the [Laravel Mix website](https://laravel-mix.com). + +Your `winter-mix.js` file must include Mix as a requirement, and must also define the public path to the current directory, as follows: + +```js +const mix = require('laravel-mix'); +mix.setPublicPath(__dirname); + +// Your mix configuration below +``` + + +### Examples + +Here are some examples of installing common front-end libraries for use with the asset compilation. + +#### Tailwind CSS + +For themes that wish to use Tailwind CSS, include the `tailwindcss`, `postcss` and `autoprefixer` dependencies in your `package.json` file. + +```bash +# Inside the project root folder +npm install --save-dev tailwindcss postcss autoprefixer + +# Run the Tailwind initialisation +npx taildwindcss init +``` + +This will create a Tailwind configuration file (`tailwind.config.js`) inside your theme that you may [configure](https://tailwindcss.com/docs/installation) to your specific theme's needs. + +Then, add a `winter-mix.js` configuration file that will compile Tailwind as needed: + +```js +const mix = require('laravel-mix'); +mix.setPublicPath(__dirname); + +// Render Tailwind style +mix.postCss('assets/css/base.css', 'assets/css/theme.css', [ + require('postcss-import'), + require('tailwindcss'), +]); +``` + +In the example above, we have a base CSS file that contains the Tailwind styling - `assets/css/base.css` - that will compile to a final compiled CSS file in `assets/css/theme.css`. + +Your theme will now be ready for Tailwind CSS development. + + +## Commands + +### Install Node dependencies + +```bash +php artisan mix:install [-p ] [--npm ] +``` + +The `mix:install` command will install Node dependencies for all registered Mix packages. This command will cycle through each registered package, running the `npm install` command and displaying the output of this command. + +You can optionally provide a `-p` flag to install dependencies for one or more packages. To define multiple packages, simply add more `-p` flags to the end of the command. + +The `--npm` flag can also be provided if you have a custom path to the `npm` program. If this is not provided, the system will try to guess where `npm` is located. + +### List registered Mix packages + +```bash +php artisan mix:list +``` + +The `mix:list` command will list all registered Mix packages found in the Winter installation. This is useful for determining if your plugin or theme is correctly registered. + +The command will list all packages, as well as the directory for the asset and the configuration file that has been defined during registration. + +### Compile a Mix packages + +```bash +php artisan mix:compile [-p ] [-f|--production] [-- ] +``` + +The `mix:compile` command compiles all registered Mix packages, running each package through Laravel Mix for compilation. + +By specifying the `-p` flag, you can compile one or more selected packages. To define multiple packages, simply add more `-p` flags to the end of the command. + +By default, all packages are built in "development" mode. If you wish to compile in "production" mode, which may include more optimisations for production sites, add the `-f` or `--production` flag to the command. + +The command will generate a report of all compiled files and their final size once complete. + +If you wish to pass extra options to the Webpack CLI, for special cases of compilation, you can add `--` to the end of the command, followed by [any parameters](https://webpack.js.org/api/cli/) as per the Webpack CLI options. + +### Watch a Mix package + +```bash +php artisan mix:watch [-f|--production] [-- ] +``` + +The `mix:watch` command is similar to the the `mix:compile` command, except that it remains active and watches for any changes made to files that would be affected by your compilation. When any changes are made, a compile is automatically executed. This is useful for development in allowing you to quickly make changes and review them in your browser. + +With this command, only one package can be provided and watched at any one time. \ No newline at end of file diff --git a/console-introduction.md b/console-introduction.md index 5a670e8d..73a6cdca 100644 --- a/console-introduction.md +++ b/console-introduction.md @@ -63,6 +63,11 @@ Command | Description [`theme:use`](../console/theme-management#theme-use) | Switches Winter to the given theme. [`theme:remove`](../console/theme-management#theme-install) | Removes a theme. [`theme:sync`](../console/theme-management#theme-sync) | Synchronises a theme between the filesystem and the database, if you use the [Database Templates](../cms/themes#database-driven-themes) feature. +**Asset compilation (Mix)** | +[`mix:install`](../console/asset-compilation#mix-install) | Install Node dependencies for registered Mix packages. +[`mix:list`](../console/asset-compilation#mix-list) | Lists all registered Mix packages. +[`mix:compile`](../console/asset-compilation#mix-compile) | Compiles one or more Mix packages. +[`mix:watch`](../console/asset-compilation#mix-watch) | Watches changes within a Mix package and automatically compiles the package on any change. **Scaffolding** | [`create:theme`](../console/scaffolding#create-theme) | Create a theme. [`create:plugin`](../console/scaffolding#create-plugin) | Create a plugin. diff --git a/themes-development.md b/themes-development.md index 4ae6c63d..f1ea27ce 100644 --- a/themes-development.md +++ b/themes-development.md @@ -40,6 +40,7 @@ Field | Description `code` | the theme code, optional. The value is used on the Winter CMS marketplace for initializing the theme code value. If the theme code is not provided, the theme directory name will be used as a code. When a theme is installed from the Marketplace, the code is used as the new theme directory name. `form` | a configuration array or reference to a form field definition file, used for [theme customization](#customization), optional. `require` | an array of plugin names used for [theme dependencies](#dependencies), optional. +`mix` | an object that defines Mix packages contained in your theme for [asset compilation](../console/asset-compilation). Example of the theme information file: From 2a8c24bd77b0f8335823f1eb6b747df0155a3c6c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 7 Feb 2022 12:45:05 +0800 Subject: [PATCH 24/36] Add Mix to docs nav --- config/toc-docs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/toc-docs.yaml b/config/toc-docs.yaml index e9e9ba1c..842efdbc 100644 --- a/config/toc-docs.yaml +++ b/config/toc-docs.yaml @@ -125,6 +125,7 @@ Console: console/setup-maintenance: "Setup & Maintenance" console/plugin-management: "Plugin Management" console/theme-management: "Theme Management" + console/asset-compilation: "Asset Compilation (Mix)" console/scaffolding: "Scaffolding" console/utilities: "Utilities" From 5dfe1d439ea35e28b5249c7f001a0c2f8b24eb43 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 8 Feb 2022 03:02:35 -0600 Subject: [PATCH 25/36] front-end -> frontend --- console-asset-compilation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/console-asset-compilation.md b/console-asset-compilation.md index 4a35f992..9615076b 100644 --- a/console-asset-compilation.md +++ b/console-asset-compilation.md @@ -16,7 +16,7 @@ ## Introduction -Winter brings first-class support for handling Node-based compilation for front-end assets through the Mix commands. The comamnds use the [Laravel Mix](https://laravel-mix.com/) wrapper, a user-friendly and simple interface to setting up compilation of multiple types of front-end asset through Webpack and various libraries. +Winter brings first-class support for handling Node-based compilation for frontend assets through the Mix commands. The comamnds use the [Laravel Mix](https://laravel-mix.com/) wrapper, a user-friendly and simple interface to setting up compilation of multiple types of frontend asset through Webpack and various libraries. ## Registering a package @@ -70,7 +70,7 @@ php artisan mix:install ### Registering plugins -To register front-end assets to be compiled through Mix in your plugin, you can use the `System\Classes\MixAssets` class to register the package. The following code can be added to your [`Plugin.php`](../plugin/registration) registration file's `boot()` method to register a callback method that registers the package when asset compilation is processed: +To register frontend assets to be compiled through Mix in your plugin, you can use the `System\Classes\MixAssets` class to register the package. The following code can be added to your [`Plugin.php`](../plugin/registration) registration file's `boot()` method to register a callback method that registers the package when asset compilation is processed: ```php public function boot() @@ -145,7 +145,7 @@ mix.setPublicPath(__dirname); ### Examples -Here are some examples of installing common front-end libraries for use with the asset compilation. +Here are some examples of installing common frontend libraries for use with the asset compilation. #### Tailwind CSS @@ -184,7 +184,7 @@ Your theme will now be ready for Tailwind CSS development. ### Install Node dependencies ```bash -php artisan mix:install [-p ] [--npm ] +php artisan mix:install [-p ] [--npm ] ``` The `mix:install` command will install Node dependencies for all registered Mix packages. This command will cycle through each registered package, running the `npm install` command and displaying the output of this command. From c11ac2f88c6b40048647522ec921971d0578c48f Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 11 Feb 2022 15:32:44 -0600 Subject: [PATCH 26/36] Document automatic registration for mix packages --- console-asset-compilation.md | 126 +++++++++++++---------------------- 1 file changed, 48 insertions(+), 78 deletions(-) diff --git a/console-asset-compilation.md b/console-asset-compilation.md index 9615076b..b0afce53 100644 --- a/console-asset-compilation.md +++ b/console-asset-compilation.md @@ -1,12 +1,13 @@ # Asset Compilation (Mix) - [Introduction](#introduction) +- [Requirements](#requirements) - [Registering a package](#registering-packages) - - [First steps](#first-steps) - - [Plugins](#registering-plugins) - - [Themes](#registering-themes) - - [Mix configuration](#mix-configuration) - - [Examples](#examples) + - [Automatic registration](#automatic-registration) + - [Registering plugin packages](#registering-plugins) + - [Registering theme packages](#registering-themes) +- [Mix configuration](#mix-configuration) +- [Examples](#examples) - [Commands](#commands) - [Install Node dependencies](#mix-install) - [List registered Mix packages](#mix-list) @@ -16,88 +17,48 @@ ## Introduction -Winter brings first-class support for handling Node-based compilation for frontend assets through the Mix commands. The comamnds use the [Laravel Mix](https://laravel-mix.com/) wrapper, a user-friendly and simple interface to setting up compilation of multiple types of frontend asset through Webpack and various libraries. +Winter brings first-class support for handling Node-based compilation for frontend assets through the Mix commands. The comamnds use the [Laravel Mix](https://laravel-mix.com/) wrapper, a user-friendly and simple interface for setting up compilation of multiple types of frontend assets through Webpack and various libraries. - -## Registering a package - -Registering for asset compilation through Mix is very easy, and only changes slightly depending on whether the asset compilation is for a plugin or a theme. + +### Requirements - -### First steps +To take advantage of Mix asset compilation, you must have Node and the Node package manager (NPM) installed in your development environment. This will be dependent on your operating system - please review the [Download NodeJS](https://nodejs.org/en/download/) page for more information on installing Node. -To take advantage of asset compilation, you must have Node and the Node package manager (NPM) installed in your development environment. This will be dependent on your operating system - please review the [Download NodeJS](https://nodejs.org/en/download/) page for more information on installing Node. +[Laravel Mix](https://laravel-mix.com/) should also be present in the `package.json` file for any packages that will be using it (either as a `dependency` or a `devDependency`) but if it is not specified in the project's `package.json` file then it can be optionally automatically added when running the [`mix:install`](#mix-install) command. -You will need to create a package configuration file (`package.json`) in your plugin or theme that defines the dependencies that you require for your assets to compile. In the majority of cases, you will only need Laravel Mix as a dependency. - -We recommend that the `package.json` file is located in the root folder of your plugin or theme. You can use the `npm init` command in your CLI to quickly create this file: - -```bash -# For a plugin -cd plugins/author/myplugin -# For a theme -cd themes/mytheme - -# Run the initialization -npm init - -# You will be asked a series of questions: -# - package name: [your theme or plugin name] -# - version: [a version number, default 1.0.0] -# - description: [enter a description for your theme or plugin] -# - entry point: index.js -# - test command: *keep blank* -# - git repository: [enter in your git repository URL, or keep blank] -# - keywords: [enter in some keywords on what your theme or plugin does, or keep blank] -# - author: [enter in your name and email in the format "Your Name "] -# - license: [enter a license - see https://docs.npmjs.com/cli/v8/configuring-npm/package-json#license for licenses] -# - Press ENTER if OK -``` + +## Registering a package -Once your `package.json` file is created, you can then add dependencies to it. One that is required for these commands is Laravel Mix: +Registering for asset compilation through Mix is very easy. Automatic registration should meet your needs most of the time, and if not there are several methods available to manually register Mix packages. -```bash -npm i --save-dev laravel-mix -``` + +### Automatic registration -If you edit the `package.json` file directly, you will need to ensure that the dependencies are installed before using the commands. You can use the [`mix:install`](#mix-install) command to install all dependencies. +By default, Winter will scan all available and enabled Modules, Plugins, & Themes for the presence of a `winter.mix.js` file under each extension's root folder (i.e. `modules/system/winter.mix.js`, `plugins/myauthor/myplugin/winter.mix.js`, or `themes/mytheme/winter.mix.js`). -```bash -# In your project root folder -php artisan mix:install -``` +If the `winter.mix.js` file is found it will be automatically registered as a package and will show up when running the Mix commands. Most of the time this should be all you need to do in order to get started with Laravel Mix based asset compilation in Winter CMS. ### Registering plugins -To register frontend assets to be compiled through Mix in your plugin, you can use the `System\Classes\MixAssets` class to register the package. The following code can be added to your [`Plugin.php`](../plugin/registration) registration file's `boot()` method to register a callback method that registers the package when asset compilation is processed: +To register frontend assets to be compiled through Mix in your plugin, simply return an array with the package names as the keys and the package paths relative to the plugin's directory as the values to register from your [`Plugin.php`](../plugin/registration) registration file's `registerMixPackages()` method. See below example. ```php -public function boot() +public function registerMixPackages() { - \System\Classes\MixAssets::registerCallback(function ($mix) { - $mix->registerPackage('', ''); - }); -} -``` + return [ + 'package-name-2' => 'assets/js/build.js', -The `registerPackage()` method takes 3 arguments: the name of the package which should be a kebab-case string that represents your package name, the directory where to find the `package.json` file and the Mix configuration file and an optional third parameter which defines the filename of your Mix configuration file. By default, we use `winter-mix.js` as this filename, but you may rename it to any other filename that you wish and define it as the third argument. - -For example, if you want to register a package called `my-plugin`, and the `package.json` and `winter-mix.js` file can be located in the `plugins/author/myplugin/assets` folder, you would use the following code block: - -```php -public function boot() -{ - \System\Classes\MixAssets::registerCallback(function ($mix) { - $mix->registerPackage('my-plugin', '~/plugins/author/myplugin/assets'); - }); + // winter.mix.js is assumed to be the config file in this path + 'package-name-3' => 'assets/css', + ]; } ``` ### Registering themes -Registration of asset compilation of themes is even easier, and can be done by adding a `mix` definition to your [theme information file](../themes-development#version-file) (`theme.yaml`). +Registration of asset compilation of themes is even easier, and can be done by adding a `mix` definition to your [theme information file](../themes/development#theme-information) (`theme.yaml`). ```yaml name: "Winter CMS Demo" @@ -107,10 +68,10 @@ homepage: "https://wintercms.com" code: "demo" mix: - : winter-mix.js + : winter.mix.js ``` -The `mix` definition takes any number of registered packages as a YAML object, with the key being the name of the package as a kebab-case string and the location of your `winter-mix.js` file relative to the theme root directory. +The `mix` definition takes any number of registered packages as a YAML object, with the key being the name of the package as a kebab-case string and the location of your `winter.mix.js` file relative to the theme's root directory. For example, if you want to register two packages called `demo-theme-style` and `demo-theme-shop` located in the assets folder, you would use the following definition: @@ -122,32 +83,37 @@ homepage: "https://wintercms.com" code: "demo" mix: - demo-theme-style: assets/style/winter-mix.js - demo-theme-shop: assets/shop/winter-mix.js + demo-theme-style: assets/style/winter.mix.js + demo-theme-shop: assets/shop/winter.mix.js ``` -### Mix configuration +## Mix configuration -The Mix configuration file (`winter-mix.js`) is a configuration file that manages the configuration of Laravel Mix itself. In conjunction with the `package.json` file that defines your dependencies, this file defines how Laravel Mix will compile your assets. +The Mix configuration file (`winter.mix.js`) is a configuration file that manages the configuration of Laravel Mix itself. In conjunction with the `package.json` file that defines your dependencies, this file defines how Laravel Mix will compile your assets. You can [review examples](https://laravel-mix.com/docs/6.0/examples) or the [full Mix API](https://laravel-mix.com/docs/6.0/api) at the [Laravel Mix website](https://laravel-mix.com). -Your `winter-mix.js` file must include Mix as a requirement, and must also define the public path to the current directory, as follows: +Your `winter.mix.js` file must include Mix as a requirement, and must also define the public path to the current directory, as follows: ```js const mix = require('laravel-mix'); -mix.setPublicPath(__dirname); + +// For assets in the current directory +// mix.setPublicPath(__dirname); + +// For assets in a /assets subdirectory +mix.setPublicPath(__dirname + '/assets'); // Your mix configuration below ``` -### Examples +## Examples Here are some examples of installing common frontend libraries for use with the asset compilation. -#### Tailwind CSS +### Tailwind CSS For themes that wish to use Tailwind CSS, include the `tailwindcss`, `postcss` and `autoprefixer` dependencies in your `package.json` file. @@ -161,7 +127,7 @@ npx taildwindcss init This will create a Tailwind configuration file (`tailwind.config.js`) inside your theme that you may [configure](https://tailwindcss.com/docs/installation) to your specific theme's needs. -Then, add a `winter-mix.js` configuration file that will compile Tailwind as needed: +Then, add a `winter.mix.js` configuration file that will compile Tailwind as needed: ```js const mix = require('laravel-mix'); @@ -187,9 +153,13 @@ Your theme will now be ready for Tailwind CSS development. php artisan mix:install [-p ] [--npm ] ``` -The `mix:install` command will install Node dependencies for all registered Mix packages. This command will cycle through each registered package, running the `npm install` command and displaying the output of this command. +The `mix:install` command will install Node dependencies for all registered Mix packages. + +This command will add each registered package to the `workspaces.packages` property of your root `package.json` file and then run and display the results of `npm install` from your project root to install all of the dependencies for all of the registered packages at once. + +You can optionally provide a `-p` or `--package` flag to install dependencies for one or more packages. To define multiple packages, simply add more `-p` flags to the end of the command. -You can optionally provide a `-p` flag to install dependencies for one or more packages. To define multiple packages, simply add more `-p` flags to the end of the command. +If the command is run with a `-p` or `--package` flag and the provided package name is not already registered and the name matches a valid module, plugin, or theme package name (modules are prefixed with `module-$moduleDirectory`, themes are prefixed with `theme-$themeDirectory`, and plugins are simply `Author.Plugin`) then a `winter.mix.js` file will be automatically generated for that package and will be included in future runs of any mix commands through the [automatic registration](#automatic-registration) feature. The `--npm` flag can also be provided if you have a custom path to the `npm` program. If this is not provided, the system will try to guess where `npm` is located. From fbfa1846f83b1fb32e5b03a9cd3db0e8dc7d9a0b Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 11 Feb 2022 15:41:44 -0600 Subject: [PATCH 27/36] Correct dependency -> dependecies to reflect what the properties actually are --- console-asset-compilation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console-asset-compilation.md b/console-asset-compilation.md index b0afce53..8f7a445b 100644 --- a/console-asset-compilation.md +++ b/console-asset-compilation.md @@ -24,7 +24,7 @@ Winter brings first-class support for handling Node-based compilation for fronte To take advantage of Mix asset compilation, you must have Node and the Node package manager (NPM) installed in your development environment. This will be dependent on your operating system - please review the [Download NodeJS](https://nodejs.org/en/download/) page for more information on installing Node. -[Laravel Mix](https://laravel-mix.com/) should also be present in the `package.json` file for any packages that will be using it (either as a `dependency` or a `devDependency`) but if it is not specified in the project's `package.json` file then it can be optionally automatically added when running the [`mix:install`](#mix-install) command. +[Laravel Mix](https://laravel-mix.com/) should also be present in the `package.json` file for any packages that will be using it (either in `dependencies` or a `devDependencies`) but if it is not specified in the project's `package.json` file then it can be optionally automatically added when running the [`mix:install`](#mix-install) command. ## Registering a package From 2c3c3b2bf49e96995e84242434f94aa12fbf84b0 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sat, 12 Feb 2022 10:01:04 -0600 Subject: [PATCH 28/36] Document use of path symbols in winter.mix.js --- console-asset-compilation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/console-asset-compilation.md b/console-asset-compilation.md index 8f7a445b..5a8370ce 100644 --- a/console-asset-compilation.md +++ b/console-asset-compilation.md @@ -108,6 +108,12 @@ mix.setPublicPath(__dirname + '/assets'); // Your mix configuration below ``` +### Paths + +When the `winter.mix.js` file is evaluated, regardless of where you ran `mix:compile` from, the working directory is set to the parent directory of the `winter.mix.js` file. That means that any relative paths used in the configuration will be relative to the current directory of the `winter.mix.js` file. + +>**NOTE:** Winter's [path symbols](../services/helpers#path-symbols) are also supported in the `winter.mix.js` file. + ## Examples From a849b796d0cc76ca3bf89db7a3c0b6f90e597dc8 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 13 Feb 2022 11:51:10 +0800 Subject: [PATCH 29/36] Small grammar tweaks --- console-asset-compilation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console-asset-compilation.md b/console-asset-compilation.md index 5a8370ce..d13161e5 100644 --- a/console-asset-compilation.md +++ b/console-asset-compilation.md @@ -34,9 +34,9 @@ Registering for asset compilation through Mix is very easy. Automatic registrati ### Automatic registration -By default, Winter will scan all available and enabled Modules, Plugins, & Themes for the presence of a `winter.mix.js` file under each extension's root folder (i.e. `modules/system/winter.mix.js`, `plugins/myauthor/myplugin/winter.mix.js`, or `themes/mytheme/winter.mix.js`). +By default, Winter will scan all available and enabled modules, plugins and themes for the presence of a `winter.mix.js` file under each extension's root folder (i.e. `modules/system/winter.mix.js`, `plugins/myauthor/myplugin/winter.mix.js`, or `themes/mytheme/winter.mix.js`). -If the `winter.mix.js` file is found it will be automatically registered as a package and will show up when running the Mix commands. Most of the time this should be all you need to do in order to get started with Laravel Mix based asset compilation in Winter CMS. +If the `winter.mix.js` file is found, it will be automatically registered as a package with an automatically generated package name, and will show up when running the Mix commands. Most of the time, this should be all you need to do in order to get started with Laravel Mix asset compilation in Winter CMS. ### Registering plugins From 2fb7c39b3ba7d6f213b893cd92995ee4213410c5 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 15 Feb 2022 16:39:06 +0800 Subject: [PATCH 30/36] WIP plugin development docs --- snowboard-plugin-development.md | 91 +++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md index 91edacca..81cdbde8 100644 --- a/snowboard-plugin-development.md +++ b/snowboard-plugin-development.md @@ -6,6 +6,7 @@ - [The PluginLoader class](#plugin-loader-class) - [The PluginBase and Singleton abstracts](#plugin-base-singleton) - [Global events](#global-events) + - [Mocking](#mocking) ## Introduction @@ -51,8 +52,8 @@ Method | Parameters | Description `getPlugins` | | Returns an object of added plugins as [PluginLoader](#plugin-loader-class) instances, keyed by the name of the plugin. `getPluginNames` | | Returns all added plugins by name as an array of strings. `listensToEvent` | eventName(`String`) | Returns an array of plugin names as strings for all plugins that listen to the given event name. This works for both Promise and non-Promise [global events](#global-events). -`globalEvent` | eventName(`String`)
*...parameters* | Calls a non-Promise [global event](#global-event). This will trigger event callbacks for all plugins listening to the given event. This method can be provided additional parameters that will be forwarded through to the event callbacks. This method returns `false` if the event was cancelled by a plugin. -`globalPromiseEvent` | eventName(`String`)
*...parameters* | Calls a Promise [global event](#global-event). This will trigger event callbacks for all plugins listening to the given event. This method can be provided additional parameters that will be forwarded through to the event callbacks. This method returns a Promise that will either be resolved or rejected depending on the response from the event callbacks of the plugins. +`globalEvent` | eventName(`String`)
*...parameters* | Calls a non-Promise [global event](#global-events). This will trigger event callbacks for all plugins listening to the given event. This method can be provided additional parameters that will be forwarded through to the event callbacks. This method returns `false` if the event was cancelled by a plugin. +`globalPromiseEvent` | eventName(`String`)
*...parameters* | Calls a Promise [global event](#global-events). This will trigger event callbacks for all plugins listening to the given event. This method can be provided additional parameters that will be forwarded through to the event callbacks. This method returns a Promise that will either be resolved or rejected depending on the response from the event callbacks of the plugins. `debug` | *...parameters* | When the application is in debug mode, this method logs debug messages to the console. Each log message can display one or more parameters at the same time, and includes a trace of the entire call stack up to when the debug call was made. #### Debugging in Snowboard @@ -91,4 +92,88 @@ Method | Parameters | Description `getInstances` | | Returns all current instances of the plugin. `getDependencies` | | Returns an array of the names of all plugins that the current plugin depends on, as strings. `dependenciesFulfilled` | | Returns `true` if the current plugin's dependencies have been fulfilled. -`mock` | methodName(`String`)
callback(`Function`) | Defines a mock for the current plugin, replacing the given method with the provided callback. \ No newline at end of file +`mock` | methodName(`String`)
callback(`Function`) | Defines a mock for the current plugin, replacing the given method with the provided callback. See the [Mocking](#mocking) section for more information. +`unmock` | methodName(`String`) | Restores the original functionality for a previously-mocked method. See the [Mocking](#mocking) section for more information. + + +### The `PluginBase` and `Singleton` abstracts + +These classes are the base of all plugins in Snowboard, and represent the base functionality that each plugin contains. When creating a plugin class, you will almost always extend one of these abstract classes. + +There are two key differences between these abstracts, based on the reusability of the plugin and how it is instantiated in the course of the JavaScript functionality of your project: + +Detail | `PluginBase` | `Singleton` +------ | ------------ | ----------- +Reusability | Each use of the plugin creates a new instance of the plugin | Each use of the plugin uses the same instance. +Instantiation | Must be instantiated manually when it is needed to be used | Instantiated automatically when the page is loaded + +The reason for the separation is to provide better definition on how your plugin is intended to be used. For `PluginBase`, you would use this when you want each instance to have its own scope and data. Contrarily, you would use `Singleton` if you want the same data and scope to be shared no matter how many times you use the plugin. + +Here are some examples of when you would use one or the other: + +- `PluginBase` + - AJAX requests + - Flash messages + - Reusable widgets with their own data +- `Singleton` + - Event listeners + - Global utilities + - Base user-interface handlers + + +### Global events + +Global events are an intrinsic feature of the Snowboard framework, allowing Snowboard plugins to respond to specific events in the course of certain functionality, similar in concept to DOM events or the Event functionality of Winter CMS. + +There are two entities that are involved in any global event: + +- The **triggering** class, which fires the global event with optional extra context, and, +- The **listening** class, which listens for when a global event is fired, and actions its own functionality. + +There are also two types of global events that can be triggered, they are: + +- A **standard** event, which fires and executes all listeners in listening classes, and, +- A **Promise** event, which fires and waits for the Promise to be resolved before triggering further functionality. + +In practice, you would generally use standard events for events in where you do not necessarily want to wait for a response. In all other cases, you would use a Promise event which allows all listening classes to respond to the event in due course. + +Firing either event is done by calling either the `globalEvent` or `globalPromiseEvent` method directly on the main Snowboard class. + +```js +// Standard event +snowboard.globalEvent('myEvent', context); + +// Promise event +snowboard.globalPromiseEvent('myPromiseEvent').then( + () => { + // functionality when the promise is resolved + }, + () => { + // functionality when the promise is rejected + } +); +``` + +For a plugin to register as a listening class, it must specify a `listens` method in the plugin class that returns an object. Each key should be the global event being listened for, and the value should be the name of a method inside the class that will handle the event when fired. This is the same whether the event is a standard event or a Promise event. + +```js +class MyPlugin extends PluginBase { + listens() { + return { + ready: 'ready', + eventName: 'myHandler', + }; + } + + ready() { + // This method is run when the `ready` global event is fired. + } + + myHandler(context) { + // This method is run when the `eventName` global event is fired. + } +} +``` + +Snowboard only has one in-built global event that is fired - the `ready` event, which is fired when the DOM is loaded and the page is ready. This event is synonymous with jQuery's `ready` event, and is mainly used to instantiate the Singletons that have been registered with Snowboard. + From ad426993b85c3c4ca2315af8e50d88a7726052fc Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 15 Feb 2022 02:54:49 -0600 Subject: [PATCH 31/36] Update console-asset-compilation.md --- console-asset-compilation.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/console-asset-compilation.md b/console-asset-compilation.md index d13161e5..471b5345 100644 --- a/console-asset-compilation.md +++ b/console-asset-compilation.md @@ -47,10 +47,7 @@ To register frontend assets to be compiled through Mix in your plugin, simply re public function registerMixPackages() { return [ - 'package-name-2' => 'assets/js/build.js', - - // winter.mix.js is assumed to be the config file in this path - 'package-name-3' => 'assets/css', + 'custom-package-name' => 'assets/js/build.mix.js', ]; } ``` From adebee808fdcc7008aac443ed868ae83a5f97706 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 15 Feb 2022 17:04:16 +0800 Subject: [PATCH 32/36] Add examples of cookie manipulation --- snowboard-utilities.md | 51 ++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/snowboard-utilities.md b/snowboard-utilities.md index 804d6de0..19bf2810 100644 --- a/snowboard-utilities.md +++ b/snowboard-utilities.md @@ -201,42 +201,45 @@ The Cookie plugin provides the ability to interact with cookies and modify their #### `cookie.get` -This event runs during `Snowboard.cookie().get()` and provides the `(string) name` & `(string) value` parameters. Returning a result from your listener will replace the original result in the results of `Snowboard.cookie().get()`. - -Example from reading one of the cookies that can only be decoded using the `escape` function: +This event runs during `Snowboard.cookie().get()` and provides the `(string) name` & `(string) value` parameters, along with a callback method that can be used by a plugin to override the cookie value programatically. This can be used to manipulate or decode cookie values. ```js -// TODO: ADJUST EXAMPLE HERE TO USE LISTENER -document.cookie = 'escaped=%u5317'; -document.cookie = 'default=%E5%8C%97'; -var Cookies = Snowboard.cookie().withConverter({ - read: function (value, name) { - if (name === 'escaped') { - return unescape(value); +class CookieDecryptor extends Singleton +{ + listens() { + return { + 'cookie.get': 'decryptCookie', } - // Fall back to default for all other cookies - return Snowboard.cookie().converter.read(value, name); } -}) -Cookies.get('escaped') // 北 -Cookies.get('default') // 北 -Cookies.get() // { escaped: '北', default: '北' } + + decryptCookie(name, value, setValue) { + if (name === 'secureCookie') { + setValue(decrypt(value)); + } + } +} ``` #### `cookie.set` -This event runs during `Snowboard.cookie().set()` and provides the `(string) name` & `(string) value` parameters. Returning a result from your listener will replace the original value in the call to `Snowboard.cookie().set()` and will be the value actually stored for the cookie. - -Create a new instance that overrides the default encoding implementation: +This event runs during `Snowboard.cookie().set()` and provides the `(string) name` & `(string) value` parameters, along with a callback method that can be used by a plugin to override the value saved to the cookie programatically. This will allow you to manipulate or encrypt cookie values before storing them with the browser. ```js -// TODO: ADJUST EXAMPLE HERE TO USE LISTENER -var Cookies = Snowboard.cookie().withConverter({ - write: function (value, name) { - return value.toUpperCase(); +class CookieEncryptor extends Singleton +{ + listens() { + return { + 'cookie.set': 'encryptCookie', + } } -}); + + encryptCookie(name, value, setValue) { + if (name === 'secureCookie') { + setValue(encrypt(value)); + } + } +} ``` From e471f6428621d3f6ac45839b352a3123b5759390 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 15 Feb 2022 17:05:23 +0800 Subject: [PATCH 33/36] Add semicolons --- snowboard-utilities.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snowboard-utilities.md b/snowboard-utilities.md index 19bf2810..f71be1cb 100644 --- a/snowboard-utilities.md +++ b/snowboard-utilities.md @@ -209,7 +209,7 @@ class CookieDecryptor extends Singleton listens() { return { 'cookie.get': 'decryptCookie', - } + }; } decryptCookie(name, value, setValue) { @@ -231,7 +231,7 @@ class CookieEncryptor extends Singleton listens() { return { 'cookie.set': 'encryptCookie', - } + }; } encryptCookie(name, value, setValue) { From c0dfb04290c04fc6fe9617b85cd0355d698d8b19 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 15 Feb 2022 03:10:28 -0600 Subject: [PATCH 34/36] Update snowboard-utilities.md --- snowboard-utilities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snowboard-utilities.md b/snowboard-utilities.md index f71be1cb..ecacdbd5 100644 --- a/snowboard-utilities.md +++ b/snowboard-utilities.md @@ -263,7 +263,7 @@ let object = Snowboard.jsonParser().parse(`{${data}}`); The Sanitizer utility is a client-side HTML sanitizer designed mostly to prevent self-XSS attacks. Such an attack could look like a user copying content from a website that uses clipboard injection to hijack the values actually stored in the clipboard and then having the user paste the content into an environment where the content would be treated as HTML, typically in richeditor / WYSIWYG fields. -The sanitizer utility will strip all attributes that start with `on` (usually JS event handlers as attributes, i.e. `onload` or `onerror`) or contain the `javascript:` pseudo protocol in their values. +The sanitizer utility will strip all attributes that start with `on` (usually JS event handlers as attributes, i.e. `onload` or `onerror`) or that contain the `javascript:` pseudo protocol in their values. It is available both as a global function (`wnSanitize(html)`) and as a Snowboard plugin. From 06c5cfdc81d25508060afce9dcf87d7684a46184 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 15 Feb 2022 03:11:37 -0600 Subject: [PATCH 35/36] Update snowboard-plugin-development.md --- snowboard-plugin-development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md index 81cdbde8..a34c6c1f 100644 --- a/snowboard-plugin-development.md +++ b/snowboard-plugin-development.md @@ -16,7 +16,7 @@ The Snowboard framework has been designed to be extensible and customisable for ## Framework Concepts -Snowboard works on the concept of an encompassing application, which acts as the base of functionality and is then extended through plugins. The main method of communication and functionality is through the use of [JavaScript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) which offer instances and extendability, and global events - a feature in-built into Snowboard. +Snowboard works on the concept of an encompassing application, which acts as the base of functionality and is then extended through plugins. The main method of communication and functionality is through the use of [JavaScript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) which offer instances and extendability, and global events - a feature built into Snowboard. The following classes and abstracts are included in the Snowboard framework. From ea7125c4e1efc38c1adc7d644870e44094993609 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 15 Feb 2022 03:15:40 -0600 Subject: [PATCH 36/36] Update snowboard-plugin-development.md --- snowboard-plugin-development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snowboard-plugin-development.md b/snowboard-plugin-development.md index a34c6c1f..ff8fcf4d 100644 --- a/snowboard-plugin-development.md +++ b/snowboard-plugin-development.md @@ -58,7 +58,7 @@ Method | Parameters | Description #### Debugging in Snowboard -As indicated just prior, the Snowboard class provides a `debug` method that allows developers to easily debug their Snowboard application and plugins. This method only works if the Winter application is in debug mode, by setting `debug` to `true` in the `config/app.php` file. +The Snowboard class provides a `debug` method that allows developers to easily debug their Snowboard application and plugins. This method only works if the Winter application is in debug mode (`'debug' => true` in the `config/app.php` file). Debugging can be called anywhere that the Snowboard class is accessible.