From: Matthias Schmidt Date: Sun, 27 Dec 2020 15:08:22 +0000 (+0100) Subject: Move documentation pages to `docs/` folder X-Git-Tag: 5.6.final~328^2~20 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=4ae901b4b7cb7cce2b056768530afb667a8f8e79;p=GitHub%2FWoltLab%2Fwoltlab.github.io.git Move documentation pages to `docs/` folder --- diff --git a/docs/getting-started_quick-start.md b/docs/getting-started_quick-start.md new file mode 100644 index 00000000..ef45ef9a --- /dev/null +++ b/docs/getting-started_quick-start.md @@ -0,0 +1,235 @@ +--- +title: Creating a simple package +sidebar: sidebar +permalink: getting-started_quick-start.html +folder: getting-started +--- + +## Setup and Requirements + +This guide will help you to create a simple package that provides a simple test +page. It is nothing too fancy, but you can use it as the foundation for your +next project. + +There are some requirements you should met before starting: + +- Text editor with syntax highlighting for PHP, [Notepad++](https://notepad-plus-plus.org/) is a solid pick + - `*.php` and `*.tpl` should be encoded with ANSI/ASCII + - `*.xml` are always encoded with UTF-8, but omit the BOM (byte-order-mark) + - Use tabs instead of spaces to indent lines + - It is recommended to set the tab width to `8` spaces, this is used in the entire software and will ease reading the source files +- An active installation of WoltLab Suite 3 +- An application to create `*.tar` archives, e.g. [7-Zip](http://www.7-zip.org/) on Windows + +## The package.xml File + +We want to create a simple page that will display the sentence "Hello World" embedded +into the application frame. Create an empty directory in the workspace of your choice +to start with. + +Create a new file called `package.xml` and insert the code below: + +```xml + + + + + Simple Package + A simple package to demonstrate the package system of WoltLab Suite Core + 1.0.0 + 2019-04-28 + + + Your Name + http://www.example.com + + + com.woltlab.wcf + + + + + + + +``` + +There is an [entire chapter][package_package-xml] on the package system that explains what the code above +does and how you can adjust it to fit your needs. For now we'll keep it as it is. + +## The PHP Class + +The next step is to create the PHP class which will serve our page: + +1. Create the directory `files` in the same directory where `package.xml` is located +2. Open `files` and create the directory `lib` +3. Open `lib` and create the directory `page` +4. Within the directory `page`, please create the file `TestPage.class.php` + +Copy and paste the following code into the `TestPage.class.php`: + +```php + + */ +class TestPage extends AbstractPage { + /** + * @var string + */ + protected $greet = ''; + + /** + * @inheritDoc + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_GET['greet'])) $this->greet = $_GET['greet']; + } + + /** + * @inheritDoc + */ + public function readData() { + parent::readData(); + + if (empty($this->greet)) { + $this->greet = 'World'; + } + } + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'greet' => $this->greet + ]); + } +} + +``` + +The class inherits from [wcf\page\AbstractPage](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/page/AbstractPage.class.php), the default implementation of pages without form controls. It +defines quite a few methods that will be automatically invoked in a specific order, for example `readParameters()` before `readData()` and finally `assignVariables()` to pass arbitrary values to the template. + +The property `$greet` is defined as `World`, but can optionally be populated through a GET variable (`index.php?test/&greet=You` would output `Hello You!`). This extra code illustrates the separation of data +processing that takes place within all sort of pages, where all user-supplied data is read from within a single method. It helps organizing the code, but most of all it enforces a clean class logic that does not +start reading user input at random places, including the risk to only escape the input of variable `$_GET['foo']` 4 out of 5 times. + +Reading and processing the data is only half the story, now we need a template to display the actual content for our page. You don't need to specify it yourself, it will be automatically guessed based on your +namespace and class name, you can [read more about it later](#appendixTemplateGuessing). + +Last but not least, you must not include the closing PHP tag `?>` at the end, it can cause PHP to break on whitespaces and is not required at all. + +## The Template + +Navigate back to the root directory of your package until you see both the `files` directory and the `package.xml`. Now create a directory called `templates`, open it and create the file `test.tpl`. + +```smarty +{include file='header'} + +
+ Hello {$greet}! +
+ +{include file='footer'} +``` + +Templates are a mixture of HTML and Smarty-like template scripting to overcome the static nature of raw HTML. The above code will display the phrase `Hello World!` in the application frame, just as any other +page would render. The included templates `header` and `footer` are responsible for the majority of the overall page functionality, but offer a whole lot of customization abilities to influence their behavior and appearance. + +## The Page Definition + +The package now contains the PHP class and the matching template, but it is still missing the page definition. Please create the file `page.xml` in your project's root directory, thus on the same level as the `package.xml`. + +```xml + + + + + wcf\page\TestPage + Test Page + system + + + +``` + +You can provide a lot more data for a page, including logical nesting and dedicated handler classes for display in menus. + +## Building the Package + +If you have followed the above guidelines carefully, your package directory should now look like this: + +``` +├── files +│   └── lib +│   ├── page +│   │   ├── TestPage.class.php +├── package.xml +├── page.xml +├── templates +│   └── test.tpl +``` + +Both files and templates are archive-based package components, that deploy their payload using tar archives rather than adding the raw files to the package file. Please create the archive `files.tar` and add the contents of the `files/*` directory, but not the directory `files/` itself. Repeat the same process for the `templates` directory, but this time with the file name `templates.tar`. Place both files in the root of your project. + +Last but not least, create the package archive `com.example.test.tar` and add all the files listed below. + +- `files.tar` +- `package.xml` +- `page.xml` +- `templates.tar` + +The archive's filename can be anything you want, all though it is the general convention to use the package name itself for easier recognition. + +## Installation + +Open the Administration Control Panel and navigate to `Configuration > Packages > Install Package`, click on `Upload Package` and select the file `com.example.test.tar` from your disk. Follow the on-screen instructions until it has been successfully installed. + +Open a new browser tab and navigate to your newly created page. If WoltLab Suite is installed at `https://example.com/wsc/`, then the URL should read `https://example.com/wsc/index.php?test/`. + +Congratulations, you have just created your first package! + +## Developer Tools + +{% include callout.html content="This feature is available with WoltLab Suite 3.1 or newer only." type="warning" %} + +The developer tools provide an interface to synchronize the data of an installed package with a bare repository on the local disk. You can re-import most PIPs at any time and have the changes applied without crafting a manual update. This process simulates a regular package update with a single PIP only, and resets the cache after the import has been completed. + +### Registering a Project + +Projects require the absolute path to the package directory, that is, the directory where it can find the `package.xml`. It is not required to install an package to register it as a project, but you have to install it in order to work with it. It does not install the package by itself! + +There is a special button on the project list that allows for a mass-import of projects based on a search path. Each direct child directory of the provided path will be tested and projects created this way will use the identifier extracted from the `package.xml`. + +### Synchronizing + +The install instructions in the `package.xml` are ignored when offering the PIP imports, the detection works entirely based on the default filename for each PIP. On top of that, only PIPs that implement the interface `wcf\system\devtools\pip\IIdempotentPackageInstallationPlugin` are valid for import, as it indicates that importing the PIP multiple times will have no side-effects and that the result is deterministic regardless of the number of times it has been imported. + +Some built-in PIPs, such as `sql` or `script`, do not qualify for this step and remain unavailable at all times. However, you can still craft and perform an actual package update to have these PIPs executed. + +## Appendix + +### Template Guessing {#appendixTemplateGuessing} + +The class name including the namespace is used to automatically determine the path to the template and its name. The example above used the page class name `wcf\page\TestPage` that is then split into four distinct parts: + +1. `wcf`, the internal abbreviation of WoltLab Suite Core (previously known as WoltLab Community Framework) +2. `\page\` (ignored) +3. `Test`, the actual name that is used for both the template and the URL +4. `Page` (page type, ignored) + +The fragments `1.` and `3.` from above are used to construct the path to the template: `/templates/test.tpl` (the first letter of `Test` is being converted to lower-case). + +{% include links.html %} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..3d4901a2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,20 @@ +--- +title: WoltLab Suite 5.3 Documentation +sidebar: sidebar +permalink: index.html +toc: false +--- + +## Introduction + +This documentation explains the basic API functionality and the creation of own packages. It is expected that you are somewhat experienced with [PHP](https://en.wikipedia.org/wiki/PHP), [Object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming) and [MySQL](https://en.wikipedia.org/wiki/MySQL). + +Head over to the [quick start tutorial][getting-started_quick-start] to learn more. + +## About WoltLab Suite 5.3 + +The [WoltLab Suite Core](https://github.com/WoltLab/WCF) as well as most of the other packages are available on [github.com/WoltLab/](https://github.com/WoltLab) and are licensed under the terms of the [GNU Lesser General Public License 2.1](https://github.com/WoltLab/WCF/blob/master/LICENSE). + +You can edit this documentation by visiting the edit link on each page, it is also available on [GitHub](https://github.com/WoltLab/woltlab.github.io). + +{% include links.html %} diff --git a/docs/javascript_code-snippets.md b/docs/javascript_code-snippets.md new file mode 100644 index 00000000..e1047e32 --- /dev/null +++ b/docs/javascript_code-snippets.md @@ -0,0 +1,24 @@ +--- +title: Code Snippets - JavaScript API +sidebar: sidebar +permalink: javascript_code-snippets.html +folder: javascript +--- + +This is a list of code snippets that do not fit into any of the other articles +and merely describe how to achieve something very specific, rather than explaining +the inner workings of a function. + +## ImageViewer + +The ImageViewer is available on all frontend pages by default, you can easily +add images to the viewer by wrapping the thumbnails with a link with the CSS +class `jsImageViewer` that points to the full version. + +```html + + + +``` + +{% include links.html %} diff --git a/docs/javascript_general-usage.md b/docs/javascript_general-usage.md new file mode 100644 index 00000000..4c57f4c3 --- /dev/null +++ b/docs/javascript_general-usage.md @@ -0,0 +1,114 @@ +--- +title: General JavaScript Usage +sidebar: sidebar +permalink: javascript_general-usage.html +folder: javascript +--- + +## The History of the Legacy API + +The WoltLab Suite 3.0 [introduced a new API][javascript_new-api_writing-a-module] based on AMD-Modules +with ES5-JavaScript that was designed with high performance and visible dependencies +in mind. This was a fundamental change in comparison to [the legacy API][javascript_legacy-api] +that was build many years before while jQuery was still a thing and we had to deal +with ancient browsers such as Internet Explorer 9 that felt short in both CSS and +JavaScript capabilities. + +Fast forward a few years, the old API is still around and most important, it is +actively being used by some components that have not been rewritten yet. This +has been done to preserve the backwards-compatibility and to avoid the +significant amount of work that it requires to rewrite a component. The components +invoked on page initialization have all been rewritten to use the modern API, but +some deferred objects that are invoked later during the page runtime may still +use the old API. + +However, the legacy API is deprecated and you should not rely on it for new +components at all. It slowly but steadily gets replaced up until a point where its +last bits are finally removed from the code base. + +## Embedding JavaScript inside Templates + +The ` + + + + +``` + +## Including External JavaScript Files + +The AMD-Modules used in the new API are automatically recognized and lazy-loaded +on demand, so unless you have a rather large and pre-compiled code-base, there +is nothing else to worry about. + +### Debug-Variants and Cache-Buster + +Your JavaScript files may change over time and you would want the users' browsers +to always load and use the latest version of your files. This can be achieved by +appending the special `LAST_UPDATE_TIME` constant to your file path. It contains +the unix timestamp of the last time any package was installed, updated or removed +and thus avoid outdated caches by relying on a unique value, without invalidating +the cache more often that it needs to be. + +```html + +``` + +For small scripts you can simply serve the full, non-minified version to the user +at all times, the differences in size and execution speed are insignificant and +are very unlikely to offer any benefits. They might even yield a worse performance, +because you'll have to include them statically in the template, even if the code +is never called. + +However, if you're including a minified build in your app or plugin, you should +include a switch to load the uncompressed version in the debug mode, while serving +the minified and optimized file to the average visitor. You should use the +`ENABLE_DEBUG_MODE` constant to decide which version should be loaded. + +```html + +``` + +### The Accelerated Guest View ("Tiny Builds") + +{% include callout.html content="You can learn more on the [Accelerated Guest View][migration_wsc-30_javascript] in the migration docs." type="info" %} + +The "Accelerated Guest View" was introduced in WoltLab Suite 3.1 and aims to +decrease page size and to improve responsiveness by enabling a read-only mode +for visitors. If you are providing a separate compiled build for this mode, you'll +need to include yet another switch to serve the right version to the visitor. + +```html + +``` + +### The `{js}` Template Plugin + +The `{js}` template plugin exists solely to provide a much easier and less error-prone +method to include external JavaScript files. + +```html +{js application='app' file='App' hasTiny=true} +``` + +The `hasTiny` attribute is optional, you can set it to `false` or just omit it +entirely if you do not provide a tiny build for your file. + +{% include links.html %} diff --git a/docs/javascript_helper-functions.md b/docs/javascript_helper-functions.md new file mode 100644 index 00000000..fc55bd0a --- /dev/null +++ b/docs/javascript_helper-functions.md @@ -0,0 +1,276 @@ +--- +title: JavaScript Helper Functions +sidebar: sidebar +permalink: javascript_helper-functions.html +folder: javascript +--- + +## Introduction + +Since version 3.0, WoltLab Suite ships with a set of global helper functions that are +exposed on the `window`-object and thus are available regardless of the context. +They are meant to reduce code repetition and to increase readability by moving +potentially relevant parts to the front of an instruction. + +## Elements + +### `elCreate(tagName: string): Element` + +Creates a new element with the provided tag name. + +```js +var element = elCreate("div"); +// equals +var element = document.createElement("div"); +``` + +### `elRemove(element: Element)` + +Removes an element from its parent without returning it. This function will throw +an error if the `element` doesn't have a parent node. + +```js +elRemove(element); +// equals +element.parentNode.removeChild(element); +``` + +### `elShow(element: Element)` + +Attempts to show an element by removing the `display` CSS-property, usually used +in conjunction with the `elHide()` function. + +```js +elShow(element); +// equals +element.style.removeProperty("display"); +``` + +### `elHide(element: Element)` + +Attempts to hide an element by setting the `display` CSS-property to `none`, this +is intended to be used with `elShow()` that relies on this behavior. + +```js +elHide(element); +// equals +element.style.setProperty("display", "none", ""); +``` + +### `elToggle(element: Element)` + +Attempts to toggle the visibility of an element by examining the value of the +`display` CSS-property and calls either `elShow()` or `elHide()`. + +## Attributes + +### `elAttr(element: Element, attribute: string, value?: string): string` + +Sets or reads an attribute value, value are implicitly casted into strings and +reading non-existing attributes will always yield an empty string. If you want +to test for attribute existence, you'll have to fall-back to the native +[`Element.hasAttribute()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute) +method. + +You should read and set native attributes directly, such as `img.src` rather +than `img.getAttribute("src");`. + +```js +var value = elAttr(element, "some-attribute"); +// equals +var value = element.getAttribute("some-attribute"); + +elAttr(element, "some-attribute", "some value"); +// equals +element.setAttribute("some-attribute", "some value"); +``` + +### `elAttrBool(element: Element, attribute: string): boolean` + +Reads an attribute and converts it value into a boolean value, the strings `"1"` +and `"true"` will evaluate to `true`. All other values, including a missing attribute, +will return `false`. + +```js +if (elAttrBool(element, "some-attribute")) { + // attribute is true-ish +} +``` + +### `elData(element: Element, attribute: string, value?: string): string` + +Short-hand function to read or set HTML5 `data-*`-attributes, it essentially +prepends the `data-` prefix before forwarding the call to `elAttr()`. + +```js +var value = elData(element, "some-attribute"); +// equals +var value = elAttr(element, "data-some-attribute"); + +elData(element, "some-attribute", "some value"); +// equals +elAttr(element, "data-some-attribute", "some value"); +``` + +### `elDataBool(element: Element, attribute: string): boolean` + +Short-hand function to convert a HTML5 `data-*`-attribute into a boolean value. It +prepends the `data-` prefix before forwarding the call to `elAttrBool()`. + +```js +if (elDataBool(element, "some-attribute")) { + // attribute is true-ish +} +// equals +if (elAttrBool(element, "data-some-attribute")) { + // attribute is true-ish +} +``` + +## Selecting Elements + +{% include callout.html content="Unlike libraries like jQuery, these functions will return `null` if an element is not found. You are responsible to validate if the element exist and to branch accordingly, invoking methods on the return value without checking for `null` will yield an error." type="warning" %} + +### `elById(id: string): Element | null` + +Selects an element by its `id`-attribute value. + +```js +var element = elById("my-awesome-element"); +// equals +var element = document.getElementById("my-awesome-element"); +``` + +### `elBySel(selector: string, context?: Element): Element | null` + +{% include callout.html content="The underlying `querySelector()`-method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on [`Element.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector#The_entire_hierarchy_counts) to learn more about this." type="danger" %} + +Select a single element based on a CSS selector, optionally limiting the results +to be a direct or indirect children of the context element. + +```js +var element = elBySel(".some-element"); +// equals +var element = document.querySelector(".some-element"); + +// limiting the scope to a context element: +var element = elBySel(".some-element", context); +// equals +var element = context.querySelector(".some-element"); +``` + +### `elBySelAll(selector: string, context?: Element, callback: (element: Element) => void): NodeList` + +{% include callout.html content="The underlying `querySelector()`-method works on the entire DOM hierarchy and can yield results outside of your context element! Please read and understand the MDN article on [`Element.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector#The_entire_hierarchy_counts) to learn more about this." type="danger" %} + +Finds and returns a `NodeList` containing all elements that match the provided +CSS selector. Although `NodeList` is an array-like structure, it is not possible +to iterate over it using array functions, including `.forEach()` which is not +available in Internet Explorer 11. + +```js +var elements = elBySelAll(".some-element"); +// equals +var elements = document.querySelectorAll(".some-element"); + +// limiting the scope to a context element: +var elements = elBySelAll(".some-element", context); +// equals +var elements = context.querySelectorAll(".some-element"); +``` + +#### Callback to Iterate Over Elements + +`elBySelAll()` supports an optional third parameter that expects a callback function +that is invoked for every element in the list. + +```js +// set the 2nd parameter to `undefined` or `null` to query the whole document +elBySelAll(".some-element", undefined, function(element) { + // is called for each element +}); + +// limiting the scope to a context element: +elBySelAll(".some-element", context, function(element) { + // is called for each element +}); +``` + +### `elClosest(element: Element, selector: string): Element | null` + +Returns the first `Element` that matches the provided CSS selector, this will +return the provided element itself if it matches the selector. + +```js +var element = elClosest(context, ".some-element"); +// equals +var element = context.closest(".some-element"); +``` + +#### Text Nodes + +If the provided context is a `Text`-node, the function will move the context to +the parent element before applying the CSS selector. If the `Text` has no parent, +`null` is returned without evaluating the selector. + +### `elByClass(className: string, context?: Element): NodeList` + +Returns a live `NodeList` containing all elements that match the provided CSS +class now _and_ in the future! The collection is automatically updated whenever +an element with that class is added or removed from the DOM, it will also include +elements that get dynamically assigned or removed this CSS class. + +You absolutely need to understand that this collection is dynamic, that means that +elements can and will be added and removed from the collection _even while_ you +iterate over it. There are only very few cases where you would need such a collection, +almost always `elBySelAll()` is what you're looking for. + +```js +// no leading dot! +var elements = elByClass("some-element"); +// equals +var elements = document.getElementsByClassName("some-element"); + +// limiting the scope to a context element: +var elements = elByClass("some-element", context); +// equals +var elements = context.getElementsByClassName(".some-element"); +``` + +### `elByTag(tagName: string, context?: Element): NodeList` + +Returns a live `NodeList` containing all elements with the provided tag name now +_and_ in the future! Please read the remarks on `elByClass()` above to understand +the implications of this. + +```js +var elements = elByTag("div"); +// equals +var elements = document.getElementsByTagName("div"); + +// limiting the scope to a context element: +var elements = elByTag("div", context); +// equals +var elements = context.getElementsByTagName("div"); +``` + +## Utility Functions + +### `elInnerError(element: Element, errorMessage?: string, isHtml?: boolean): Element | null`` + +Unified function to display and remove inline error messages for input elements, +please read the [section in the migration docs](migration_wsc-30_javascript.html#helper-function-for-inline-error-messages) +to learn more about this function. + +## String Extensions + +### `hashCode(): string` + +Computes a numeric hash value of a string similar to Java's `String.hashCode()` method. + +```js +console.log("Hello World".hashCode()); +// outputs: -862545276 +``` + +{% include links.html %} diff --git a/docs/javascript_legacy-api.md b/docs/javascript_legacy-api.md new file mode 100644 index 00000000..79200003 --- /dev/null +++ b/docs/javascript_legacy-api.md @@ -0,0 +1,232 @@ +--- +title: Legacy JavaScript API +sidebar: sidebar +permalink: javascript_legacy-api.html +folder: javascript +--- + +## Introduction + +The legacy JavaScript API is the original code that was part of the 2.x series +of WoltLab Suite, formerly known as WoltLab Community Framework. It has been +superseded for the most part by the [ES5/AMD-modules API][javascript_new-api_writing-a-module] +introduced with WoltLab Suite 3.0. + +Some parts still exist to this day for backwards-compatibility and because some +less important components have not been rewritten yet. The old API is still +supported, but marked as deprecated and will continue to be replaced parts by +part in future releases, up until their entire removal, including jQuery support. + +This guide does not provide any explanation on the usage of those legacy components, +but instead serves as a cheat sheet to convert code to use the new API. + +## Classes + +### Singletons + +Singleton instances are designed to provide a unique "instance" of an object +regardless of when its first instance was created. Due to the lack of a `class` +construct in ES5, they are represented by mere objects that act as an instance. + +```js +// App.js +window.App = {}; +App.Foo = { + bar: function() {} +}; + +// --- NEW API --- + +// App/Foo.js +define([], function() { + "use strict"; + + return { + bar: function() {} + }; +}); +``` + +### Regular Classes + +```js +// App.js +window.App = {}; +App.Foo = Class.extend({ + bar: function() {} +}); + +// --- NEW API --- + +// App/Foo.js +define([], function() { + "use strict"; + + function Foo() {}; + Foo.prototype = { + bar: function() {} + }; + + return Foo; +}); +``` + +#### Inheritance + +```js +// App.js +window.App = {}; +App.Foo = Class.extend({ + bar: function() {} +}); +App.Baz = App.Foo.extend({ + makeSnafucated: function() {} +}); + +// --- NEW API --- + +// App/Foo.js +define([], function() { + "use strict"; + + function Foo() {}; + Foo.prototype = { + bar: function() {} + }; + + return Foo; +}); + +// App/Baz.js +define(["Core", "./Foo"], function(Core, Foo) { + "use strict"; + + function Baz() {}; + Core.inherit(Baz, Foo, { + makeSnafucated: function() {} + }); + + return Baz; +}); +``` + +## Ajax Requests + +```js +// App.js +App.Foo = Class.extend({ + _proxy: null, + + init: function() { + this._proxy = new WCF.Action.Proxy({ + success: $.proxy(this._success, this) + }); + }, + + bar: function() { + this._proxy.setOption("data", { + actionName: "baz", + className: "app\\foo\\FooAction", + objectIDs: [1, 2, 3], + parameters: { + foo: "bar", + baz: true + } + }); + this._proxy.sendRequest(); + }, + + _success: function(data) { + // ajax request result + } +}); + +// --- NEW API --- + +// App/Foo.js +define(["Ajax"], function(Ajax) { + "use strict"; + + function Foo() {} + Foo.prototype = { + bar: function() { + Ajax.api(this, { + objectIDs: [1, 2, 3], + parameters: { + foo: "bar", + baz: true + } + }); + }, + + // magic method! + _ajaxSuccess: function(data) { + // ajax request result + }, + + // magic method! + _ajaxSetup: function() { + return { + actionName: "baz", + className: "app\\foo\\FooAction" + } + } + } + + return Foo; +}); +``` + +## Phrases + +```html + + + + + +``` + +## Event-Listener + +```html + + + + + +``` + +{% include links.html %} diff --git a/docs/javascript_new-api_ajax.md b/docs/javascript_new-api_ajax.md new file mode 100644 index 00000000..50fd7daa --- /dev/null +++ b/docs/javascript_new-api_ajax.md @@ -0,0 +1,245 @@ +--- +title: Ajax Requests - JavaScript API +sidebar: sidebar +permalink: javascript_new-api_ajax.html +folder: javascript +--- + +## Ajax inside Modules + +The Ajax component was designed to be used from inside modules where an object +reference is used to delegate request callbacks. This is acomplished through +a set of magic methods that are automatically called when the request is created +or its state has changed. + +### `_ajaxSetup()` + +The lazy initialization is performed upon the first invocation from the callee, +using the magic `_ajaxSetup()` method to retrieve the basic configuration for +this and any future requests. + +The data returned by `_ajaxSetup()` is cached and the data will be used to +pre-populate the request data before sending it. The callee can overwrite any of +these properties. It is intended to reduce the overhead when issuing request +when these requests share the same properties, such as accessing the same endpoint. + +```js +// App/Foo.js +define(["Ajax"], function(Ajax) { + "use strict"; + + function Foo() {}; + Foo.prototype = { + one: function() { + // this will issue an ajax request with the parameter `value` set to `1` + Ajax.api(this); + }, + + two: function() { + // this request is almost identical to the one issued with `.one()`, but + // the value is now set to `2` for this invocation only. + Ajax.api(this, { + parameters: { + value: 2 + } + }); + }, + + _ajaxSetup: function() { + return { + data: { + actionName: "makeSnafucated", + className: "app\\data\\foo\\FooAction", + parameters: { + value: 1 + } + } + } + } + }; + + return Foo; +}); +``` + +### Request Settings + +The object returned by the aforementioned `_ajaxSetup()` callback can contain these +values: + +#### `data` + +_Defaults to `{}`._ + +A plain JavaScript object that contains the request data that represents the form +data of the request. The `parameters` key is recognized by the PHP Ajax API and +becomes accessible through `$this->parameters`. + +#### `contentType` + +_Defaults to `application/x-www-form-urlencoded; charset=UTF-8`._ + +The request content type, sets the `Content-Type` HTTP header if it is not empty. + +#### `responseType` + +_Defaults to `application/json`._ + +The server must respond with the `Content-Type` HTTP header set to this value, +otherwise the request will be treated as failed. Requests for `application/json` +will have the return body attempted to be evaluated as JSON. + +Other content types will only be validated based on the HTTP header, but no +additional transformation is performed. For example, setting the `responseType` +to `application/xml` will check the HTTP header, but will not transform the +`data` parameter, you'll still receive a string in `_ajaxSuccess`! + +#### `type` + +_Defaults to `POST`._ + +The HTTP Verb used for this request. + +#### `url` + +_Defaults to an empty string._ + +Manual override for the request endpoint, it will be automatically set to the +Core API endpoint if left empty. If the Core API endpoint is used, the options +`includeRequestedWith` and `withCredentials` will be force-set to true. + +#### `withCredentials` + +{% include callout.html content="Enabling this parameter for any domain other than the current will trigger a CORS preflight request." type="warning" %} + +_Defaults to `false`._ + +Include cookies with this requested, is always true when `url` is (implicitly) +set to the Core API endpoint. + +#### `autoAbort` + +_Defaults to `false`._ + +When set to `true`, any pending responses to earlier requests will be silently +discarded when issuing a new request. This only makes sense if the new request +is meant to completely replace the result of the previous one, regardless of its +reponse body. + +Typical use-cases include input field with suggestions, where possible values +are requested from the server, but the input changed faster than the server was +able to reply. In this particular case the client is not interested in the result +for an earlier value, auto-aborting these requests avoids implementing this logic +in the requesting code. + +#### `ignoreError` + +_Defaults to `false`._ + +Any failing request will invoke the `failure`-callback to check if an error +message should be displayed. Enabling this option will suppress the general +error overlay that reports a failed request. + +You can achieve the same result by returning `false` in the `failure`-callback. + +#### `silent` + +_Defaults to `false`._ + +Enabling this option will suppress the loading indicator overlay for this request, +other non-"silent" requests will still trigger the loading indicator. + +#### `includeRequestedWith` + +{% include callout.html content="Enabling this parameter for any domain other than the current will trigger a CORS preflight request." type="warning" %} + +_Defaults to `true`._ + +Sets the custom HTTP header `X-Requested-With: XMLHttpRequest` for the request, +it is automatically set to `true` when `url` is pointing at the WSC API endpoint. + +#### `failure` + +_Defaults to `null`._ + +Optional callback function that will be invoked for requests that have failed +for one of these reasons: + 1. The request timed out. + 2. The HTTP status is not `2xx` or `304`. + 3. A `responseType` was set, but the response HTTP header `Content-Type` did not match the expected value. + 4. The `responseType` was set to `application/json`, but the response body was not valid JSON. + +The callback function receives the parameter `xhr` (the `XMLHttpRequest` object) +and `options` (deep clone of the request parameters). If the callback returns +`false`, the general error overlay for failed requests will be suppressed. + +There will be no error overlay if `ignoreError` is set to `true` or if the +request failed while attempting to evaluate the response body as JSON. + +#### `finalize` + +_Defaults to `null`._ + +Optional callback function that will be invoked once the request has completed, +regardless if it succeeded or failed. The only parameter it receives is +`options` (the request parameters object), but it does not receive the request's +`XMLHttpRequest`. + +#### `success` + +_Defaults to `null`._ + +This semi-optional callback function will always be set to `_ajaxSuccess()` when +invoking `Ajax.api()`. It receives four parameters: + 1. `data` - The request's response body as a string, or a JavaScript object if + `contentType` was set to `application/json`. + 2. `responseText` - The unmodified response body, it equals the value for `data` + for non-JSON requests. + 3. `xhr` - The underlying `XMLHttpRequest` object. + 4. `requestData` - The request parameters that were supplied when the request + was issued. + +### `_ajaxSuccess()` + +This callback method is automatically called for successful AJAX requests, it +receives four parameters, with the first one containing either the response body +as a string, or a JavaScript object for JSON requests. + +### `_ajaxFailure()` + +Optional callback function that is invoked for failed requests, it will be +automatically called if the callee implements it, otherwise the global error +handler will be executed. + +## Single Requests Without a Module + +The `Ajax.api()` method expects an object that is used to extract the request +configuration as well as providing the callback functions when the request state +changes. + +You can issue a simple Ajax request without object binding through `Ajax.apiOnce()` +that will destroy the instance after the request was finalized. This method is +significantly more expensive for repeated requests and does not offer deriving +modules from altering the behavior. It is strongly recommended to always use +`Ajax.api()` for requests to the WSC API endpoint. + +```html + +``` + +{% include links.html %} diff --git a/docs/javascript_new-api_browser.md b/docs/javascript_new-api_browser.md new file mode 100644 index 00000000..60d767c6 --- /dev/null +++ b/docs/javascript_new-api_browser.md @@ -0,0 +1,94 @@ +--- +title: Browser and Screen Sizes - JavaScript API +sidebar: sidebar +permalink: javascript_new-api_browser.html +folder: javascript +--- + +## `Ui/Screen` + +CSS offers powerful media queries that alter the layout depending on the screen +sizes, including but not limited to changes between landscape and portrait mode +on mobile devices. + +The `Ui/Screen` module exposes a consistent interface to execute JavaScript code +based on the same media queries that are available in the CSS code already. It +features support for unmatching and executing code when a rule matches for the +first time during the page lifecycle. + +### Supported Aliases + +You can pass in custom media queries, but it is strongly recommended to use the +built-in media queries that match the same dimensions as your CSS. + +| Alias | Media Query | +|---|---| +| `screen-xs` | `(max-width: 544px)` | +| `screen-sm` | `(min-width: 545px) and (max-width: 768px)` | +| `screen-sm-down` | `(max-width: 768px)` | +| `screen-sm-up` | `(min-width: 545px)` | +| `screen-sm-md` | `(min-width: 545px) and (max-width: 1024px)` | +| `screen-md` | `(min-width: 769px) and (max-width: 1024px)` | +| `screen-md-down` | `(max-width: 1024px)` | +| `screen-md-up` | `(min-width: 769px)` | +| `screen-lg` | `(min-width: 1025px)` + +### `on(query: string, callbacks: Object): string` + +Registers a set of callback functions for the provided media query, the possible +keys are `match`, `unmatch` and `setup`. The method returns a randomly generated +UUIDv4 that is used to identify these callbacks and allows them to be removed +via `.remove()`. + +### `remove(query: string, uuid: string)` + +Removes all callbacks for a media query that match the UUIDv4 that was previously +obtained from the call to `.on()`. + +### `is(query: string): boolean` + +Tests if the provided media query currently matches and returns true on match. + +### `scrollDisable()` + +Temporarily prevents the page from being scrolled, until `.scrollEnable()` is +called. + +### `scrollEnable()` + +Enables page scrolling again, unless another pending action has also prevented +the page scrolling. + +## `Environment` + +{% include callout.html content="The `Environment` module uses a mixture of feature detection and user agent sniffing to determine the browser and platform. In general, its results have proven to be very accurate, but it should be taken with a grain of salt regardless. Especially the browser checks are designed to be your last resort, please use feature detection instead whenever it is possible!" type="warning" %} + +Sometimes it may be necessary to alter the behavior of your code depending on +the browser platform (e. g. mobile devices) or based on a specific browser in +order to work-around some quirks. + +### `browser(): string` + +Attempts to detect browsers based on their technology and supported CSS vendor +prefixes, and although somewhat reliable for major browsers, it is highly +recommended to use feature detection instead. + +Possible values: + - `chrome` (includes Opera 15+ and Vivaldi) + - `firefox` + - `safari` + - `microsoft` (Internet Explorer and Edge) + - `other` (default) + +### `platform(): string` + +Attempts to detect the browser platform using user agent sniffing. + +Possible values: + - `ios` + - `android` + - `windows` (IE Mobile) + - `mobile` (generic mobile device) + - `desktop` (default) + +{% include links.html %} diff --git a/docs/javascript_new-api_core.md b/docs/javascript_new-api_core.md new file mode 100644 index 00000000..56702f6a --- /dev/null +++ b/docs/javascript_new-api_core.md @@ -0,0 +1,193 @@ +--- +title: Core Modules and Functions - JavaScript API +sidebar: sidebar +permalink: javascript_new-api_core.html +folder: javascript +--- + +A brief overview of common methods that may be useful when writing any module. + +## `Core` + +### `clone(object: Object): Object` + +Creates a deep-clone of the provided object by value, removing any references on +the original element, including arrays. However, this does not clone references +to non-plain objects, these instances will be copied by reference. + +```js +require(["Core"], function(Core) { + var obj1 = { a: 1 }; + var obj2 = Core.clone(obj1); + + console.log(obj1 === obj2); // output: false + console.log(obj2.hasOwnProperty("a") && obj2.a === 1); // output: true +}); +``` + +### `extend(base: Object, ...merge: Object[]): Object` + +Accepts an infinite amount of plain objects as parameters, values will be copied +from the 2nd...nth object into the first object. The first parameter will be +cloned and the resulting object is returned. + +```js +require(["Core"], function(Core) { + var obj1 = { a: 2 }; + var obj2 = { a: 1, b: 2 }; + var obj = Core.extend({ + b: 1 + }, obj1, obj2); + + console.log(obj.b === 2); // output: true + console.log(obj.hasOwnProperty("a") && obj.a === 2); // output: false +}); +``` + +### `inherit(base: Object, target: Object, merge?: Object)` + +Derives the second object's prototype from the first object, afterwards the +derived class will pass the `instanceof` check against the original class. + +```js +// App.js +window.App = {}; +App.Foo = Class.extend({ + bar: function() {} +}); +App.Baz = App.Foo.extend({ + makeSnafucated: function() {} +}); + +// --- NEW API --- + +// App/Foo.js +define([], function() { + "use strict"; + + function Foo() {}; + Foo.prototype = { + bar: function() {} + }; + + return Foo; +}); + +// App/Baz.js +define(["Core", "./Foo"], function(Core, Foo) { + "use strict"; + + function Baz() {}; + Core.inherit(Baz, Foo, { + makeSnafucated: function() {} + }); + + return Baz; +}); +``` + +### `isPlainObject(object: Object): boolean` + +Verifies if an object is a plain JavaScript object and not an object instance. + +```js +require(["Core"], function(Core) { + function Foo() {} + Foo.prototype = { + hello: "world"; + }; + + var obj1 = { hello: "world" }; + var obj2 = new Foo(); + + console.log(Core.isPlainObject(obj1)); // output: true + console.log(obj1.hello === obj2.hello); // output: true + console.log(Core.isPlainObject(obj2)); // output: false +}); +``` + +### `triggerEvent(element: Element, eventName: string)` + +Creates and dispatches a synthetic JavaScript event on an element. + +```js +require(["Core"], function(Core) { + var element = elBySel(".some-element"); + Core.triggerEvent(element, "click"); +}); +``` + +## `Language` + +### `add(key: string, value: string)` + +Registers a new phrase. + +```html + +``` + +### `addObject(object: Object)` + +Registers a list of phrases using a plain object. + +```html + +``` + +### `get(key: string, parameters?: Object): string` + +Retrieves a phrase by its key, optionally supporting basic template scripting +with dynamic variables passed using the `parameters` object. + +```js +require(["Language"], function(Language) { + var title = Language.get("app.foo.title"); + var content = Language.get("app.foo.content", { + some: "value" + }); +}); +``` + +## `StringUtil` + +### `escapeHTML(str: string): string` + +Escapes special HTML characters by converting them into an HTML entity. + +| Character | Replacement | +|---|---| +| `&` | `&` | +| `"` | `"` | +| `<` | `<` | +| `>` | `>` | + +### `escapeRegExp(str: string): string` + +Escapes a list of characters that have a special meaning in regular expressions +and could alter the behavior when embedded into regular expressions. + +### `lcfirst(str: string): string` + +Makes a string's first character lowercase. + +### `ucfirst(str: string): string` + +Makes a string's first character uppercase. + +### `unescapeHTML(str: string): string` + +Converts some HTML entities into their original character. This is the reverse +function of `escapeHTML()`. + +{% include links.html %} diff --git a/docs/javascript_new-api_data-structures.md b/docs/javascript_new-api_data-structures.md new file mode 100644 index 00000000..c2e50111 --- /dev/null +++ b/docs/javascript_new-api_data-structures.md @@ -0,0 +1,114 @@ +--- +title: Data Structures - JavaScript API +sidebar: sidebar +permalink: javascript_new-api_data-structures.html +folder: javascript +--- + +## Introduction + +JavaScript offers only limited types of collections to hold and iterate over +data. Despite the ongoing efforts in ES6 and newer, these new data structures +and access methods, such as `for … of`, are not available in the still supported +Internet Explorer 11. + +## `Dictionary` + +Represents a simple key-value map, but unlike the use of plain objects, will +always to guarantee to iterate over directly set values only. + +_In supported browsers this will use a native `Map` internally, otherwise a plain object._ + +### `set(key: string, value: any)` + +Adds or updates an item using the provided key. Numeric keys will be converted +into strings. + +### `delete(key: string)` + +Removes an item from the collection. + +### `has(key: string): boolean` + +Returns true if the key is contained in the collection. + +### `get(key: string): any` + +Returns the value for the provided key, or `undefined` if the key was not found. +Use `.has()` to check for key existence. + +### `forEach(callback: (value: any, key: string) => void)` + +Iterates over all items in the collection in an arbitrary order and invokes the +supplied callback with the value and the key. + +### `size: number` + +This read-only property counts the number of items in the collection. + +## `List` + +Represents a list of unique values. + +_In supported browsers this will use a native `Set` internally, otherwise an array._ + +### `add(value: any)` + +Adds a value to the list. If the value is already part of the list, this method +will silently abort. + +### `clear()` + +Resets the collection. + +### `delete(value: any): boolean` + +Attempts to remove a value from the list, it returns true if the value has been +part of the list. + +### `forEach(callback: (value: any) => void)` + +Iterates over all values in the list in an arbitrary order and invokes the +supplied callback for each value. + +### `has(value: any): boolean` + +Returns true if the provided value is part of this list. + +### `size: number` + +This read-only property counts the number of items in the list. + +## `ObjectMap` + +{% include callout.html content="This class uses a `WeakMap` internally, the keys are only weakly referenced and do not prevent garbage collection." type="info" %} + +Represents a collection where any kind of objects, such as class instances or +DOM elements, can be used as key. These keys are weakly referenced and will not +prevent garbage collection from happening, but this also means that it is not +possible to enumerate or iterate over the stored keys and values. + +This class is especially useful when you want to store additional data for +objects that may get disposed on runtime, such as DOM elements. Using any regular +data collections will cause the object to be referenced indefinitely, preventing +the garbage collection from removing orphaned objects. + +### `set(key: Object, value: Object)` + +Adds the key with the provided value to the map, if the key was already part +of the collection, its value is overwritten. + +### `delete(key: Object)` + +Attempts to remove a key from the collection. The method will abort silently if +the key is not part of the collection. + +### `has(key: Object): boolean` + +Returns true if there is a value for the provided key in this collection. + +### `get(key: Object): Object | undefined` + +Retrieves the value of the provided key, or `undefined` if the key was not found. + +{% include links.html %} diff --git a/docs/javascript_new-api_dialogs.md b/docs/javascript_new-api_dialogs.md new file mode 100644 index 00000000..cab29902 --- /dev/null +++ b/docs/javascript_new-api_dialogs.md @@ -0,0 +1,179 @@ +--- +title: Dialogs - JavaScript API +sidebar: sidebar +permalink: javascript_new-api_dialogs.html +folder: javascript +--- + +## Introduction + +Dialogs are full screen overlays that cover the currently visible window area +using a semi-opague backdrop and a prominently placed dialog window in the +foreground. They shift the attention away from the original content towards the +dialog and usually contain additional details and/or dedicated form inputs. + +## `_dialogSetup()` + +The lazy initialization is performed upon the first invocation from the callee, +using the magic `_dialogSetup()` method to retrieve the basic configuration for +the dialog construction and any event callbacks. + +```js +// App/Foo.js +define(["Ui/Dialog"], function(UiDialog) { + "use strict"; + + function Foo() {}; + Foo.prototype = { + bar: function() { + // this will open the dialog constructed by _dialogSetup + UiDialog.open(this); + }, + + _dialogSetup: function() { + return { + id: "myDialog", + source: "

Hello World!

", + options: { + onClose: function() { + // the fancy dialog was closed! + } + } + } + } + }; + + return Foo; +}); +``` + +### `id: string` + +The `id` is used to identify a dialog on runtime, but is also part of the first- +time setup when the dialog has not been opened before. If `source` is `undefined`, +the module attempts to construct the dialog using an element with the same id. + +### `source: any` + +There are six different types of value that `source` does allow and each of them +changes how the initial dialog is constructed: + +1. `undefined` + The dialog exists already and the value of `id` should be used to identify the + element. +2. `null` + The HTML is provided using the second argument of `.open()`. +3. `() => void` + If the `source` is a function, it is executed and is expected to start the + dialog initialization itself. +4. `Object` + Plain objects are interpreted as parameters for an Ajax request, in particular + `source.data` will be used to issue the request. It is possible to specify the + key `source.after` as a callback `(content: Element, responseData: Object) => void` + that is executed after the dialog was opened. +5. `string` + The string is expected to be plain HTML that should be used to construct the + dialog. +6. `DocumentFragment` + A new container `
` with the provided `id` is created and the contents of + the `DocumentFragment` is appended to it. This container is then used for the + dialog. + +### `options: Object` + +All configuration options and callbacks are handled through this object. + +#### `options.backdropCloseOnClick: boolean` + +_Defaults to `true`._ + +Clicks on the dialog backdrop will close the top-most dialog. This option will +be force-disabled if the option `closeable` is set to `false`. + +#### `options.closable: boolean` + +_Defaults to `true`._ + +Enables the close button in the dialog title, when disabled the dialog can be +closed through the `.close()` API call only. + +#### `options.closeButtonLabel: string` + +_Defaults to `Language.get("wcf.global.button.close")`._ + +The phrase that is displayed in the tooltip for the close button. + +#### `options.closeConfirmMessage: string` + +_Defaults to `""`._ + +Shows a [confirmation dialog][javascript_new-api_ui] using the configured message +before closing the dialog. The dialog will not be closed if the dialog is +rejected by the user. + +#### `options.title: string` + +_Defaults to `""`._ + +The phrase that is displayed in the dialog title. + +#### `options.onBeforeClose: (id: string) => void` + +_Defaults to `null`._ + +The callback is executed when the user clicks on the close button or, if enabled, +on the backdrop. The callback is responsible to close the dialog by itself, the +default close behavior is automatically prevented. + +#### `options.onClose: (id: string) => void` + +_Defaults to `null`._ + +The callback is notified once the dialog is about to be closed, but is still +visible at this point. It is not possible to abort the close operation at this +point. + +#### `options.onShow: (content: Element) => void` + +_Defaults to `null`._ + +Receives the dialog content element as its only argument, allowing the callback +to modify the DOM or to register event listeners before the dialog is presented +to the user. The dialog is already visible at call time, but the dialog has not +been finalized yet. + +## `setTitle(id: string | Object, title: string)` + +Sets the title of a dialog. + +## `setCallback(id: string | Object, key: string, value: (data: any) => void | null)` + +Sets a callback function after the dialog initialization, the special value +`null` will remove a previously set callback. Valid values for `key` are +`onBeforeClose`, `onClose` and `onShow`. + +## `rebuild(id: string | Object)` + +Rebuilds a dialog by performing various calculations on the maximum dialog +height in regards to the overflow handling and adjustments for embedded forms. +This method is automatically invoked whenever a dialog is shown, after invoking +the `options.onShow` callback. + +## `close(id: string | Object)` + +Closes an open dialog, this will neither trigger a confirmation dialog, nor does +it invoke the `options.onBeforeClose` callback. The `options.onClose` callback +will always be invoked, but it cannot abort the close operation. + +## `getDialog(id: string | Object): Object` + +{% include callout.html content="This method returns an internal data object by reference, any modifications made do have an effect on the dialogs behavior and in particular no validation is performed on the modification. It is strongly recommended to use the `.set*()` methods only." type="warning" %} + +Returns the internal dialog data that is attached to a dialog. The most important +key is `.content` which holds a reference to the dialog's inner content element. + +## `isOpen(id: string | Object): boolean` + +Returns true if the dialog exists and is open. + +{% include links.html %} diff --git a/docs/javascript_new-api_dom.md b/docs/javascript_new-api_dom.md new file mode 100644 index 00000000..b456d866 --- /dev/null +++ b/docs/javascript_new-api_dom.md @@ -0,0 +1,77 @@ +--- +title: Working with the DOM - JavaScript API +sidebar: sidebar +permalink: javascript_new-api_dom.html +folder: javascript +--- + +## Helper Functions + +There is large set of [helper functions][javascript_helper-functions] that assist +you when working with the DOM tree and its elements. These functions are globally +available and do not require explicit module imports. + +## `Dom/Util` + +### `createFragmentFromHtml(html: string): DocumentFragment` + +Parses a HTML string and creates a `DocumentFragment` object that holds the +resulting nodes. + +### `identify(element: Element): string` + +Retrieves the unique identifier (`id`) of an element. If it does not currently +have an id assigned, a generic identifier is used instead. + +### `outerHeight(element: Element, styles?: CSSStyleDeclaration): number` + +Computes the outer height of an element using the element's `offsetHeight` and +the sum of the rounded down values for `margin-top` and `margin-bottom`. + +### `outerWidth(element: Element, styles?: CSSStyleDeclaration): number` + +Computes the outer width of an element using the element's `offsetWidth` and +the sum of the rounded down values for `margin-left` and `margin-right`. + +### `outerDimensions(element: Element): { height: number, width: number }` + +Computes the outer dimensions of an element including its margins. + +### `offset(element: Element): { top: number, left: number }` + +Computes the element's offset relative to the top left corner of the document. + +### `setInnerHtml(element: Element, innerHtml: string)` + +Sets the inner HTML of an element via `element.innerHTML = innerHtml`. Browsers +do not evaluate any embedded ` +``` + +### Module Aliases + +Some common modules have short-hand aliases that can be used to include them +without writing out their full name. You can still use their original path, but +it is strongly recommended to use the aliases for consistency. + +| Alias | Full Path | +|---|---| +| [Ajax][javascript_new-api_ajax] | WoltLabSuite/Core/Ajax | +| AjaxJsonp | WoltLabSuite/Core/Ajax/Jsonp | +| AjaxRequest | WoltLabSuite/Core/Ajax/Request | +| CallbackList | WoltLabSuite/Core/CallbackList | +| ColorUtil | WoltLabSuite/Core/ColorUtil | +| [Core][javascript_new-api_core] | WoltLabSuite/Core/Core | +| DateUtil | WoltLabSuite/Core/Date/Util | +| Devtools | WoltLabSuite/Core/Devtools | +| [Dictionary][javascript_new-api_data-structures] | WoltLabSuite/Core/Dictionary | +| [Dom/ChangeListener][javascript_new-api_dom] | WoltLabSuite/Core/Dom/Change/Listener | +| Dom/Traverse | WoltLabSuite/Core/Dom/Traverse | +| [Dom/Util][javascript_new-api_dom] | WoltLabSuite/Core/Dom/Util | +| [Environment][javascript_new-api_browser] | WoltLabSuite/Core/Environment | +| [EventHandler][javascript_new-api_events] | WoltLabSuite/Core/Event/Handler | +| [EventKey][javascript_new-api_events] | WoltLabSuite/Core/Event/Key | +| [Language][javascript_new-api_core] | WoltLabSuite/Core/Language | +| [List][javascript_new-api_data-structures] | WoltLabSuite/Core/List | +| [ObjectMap][javascript_new-api_data-structures] | WoltLabSuite/Core/ObjectMap | +| Permission | WoltLabSuite/Core/Permission | +| [StringUtil][javascript_new-api_core] | WoltLabSuite/Core/StringUtil | +| [Ui/Alignment][javascript_new-api_ui] | WoltLabSuite/Core/Ui/Alignment | +| [Ui/CloseOverlay][javascript_new-api_ui] | WoltLabSuite/Core/Ui/CloseOverlay | +| [Ui/Confirmation][javascript_new-api_ui] | WoltLabSuite/Core/Ui/Confirmation | +| [Ui/Dialog][javascript_new-api_dialogs] | WoltLabSuite/Core/Ui/Dialog | +| [Ui/Notification][javascript_new-api_ui] | WoltLabSuite/Core/Ui/Notification | +| Ui/ReusableDropdown | WoltLabSuite/Core/Ui/Dropdown/Reusable | +| [Ui/Screen][javascript_new-api_browser] | WoltLabSuite/Core/Ui/Screen | +| Ui/Scroll | WoltLabSuite/Core/Ui/Scroll | +| Ui/SimpleDropdown | WoltLabSuite/Core/Ui/Dropdown/Simple | +| Ui/TabMenu | WoltLabSuite/Core/Ui/TabMenu | +| Upload | WoltLabSuite/Core/Upload | +| User | WoltLabSuite/Core/User | + +{% include links.html %} diff --git a/docs/migration_wcf-21_css.md b/docs/migration_wcf-21_css.md new file mode 100644 index 00000000..511dae80 --- /dev/null +++ b/docs/migration_wcf-21_css.md @@ -0,0 +1,12 @@ +--- +title: WCF 2.1.x - CSS +sidebar: sidebar +permalink: migration_wcf-21_css.html +folder: migration/wcf-21 +--- + +The LESS compiler has been in use since WoltLab Community Framework 2.0, but was replaced with a SCSS compiler in WoltLab Suite 3.0. This change was motivated by SCSS becoming the de facto standard for CSS pre-processing and some really annoying shortcomings in the old LESS compiler. + +The entire CSS has been rewritten from scratch, please read the [docs on CSS][view_css] to learn what has changed. + +{% include links.html %} diff --git a/docs/migration_wcf-21_package.md b/docs/migration_wcf-21_package.md new file mode 100644 index 00000000..e3c28efd --- /dev/null +++ b/docs/migration_wcf-21_package.md @@ -0,0 +1,143 @@ +--- +title: WCF 2.1.x - Package Components +sidebar: sidebar +permalink: migration_wcf-21_package.html +folder: migration/wcf-21 +--- + +## package.xml + +### Short Instructions + +Instructions can now omit the filename, causing them to use the default filename if defined by the package installation plugin (in short: `PIP`). Unless overridden it will default to the PIP's class name with the first letter being lower-cased, e.g. `EventListenerPackageInstallationPlugin` implies the filename `eventListener.xml`. The file is always assumed to be in the archive's root, files located in subdirectories need to be explicitly stated, just as it worked before. + +Every PIP can define a custom filename if the default value cannot be properly derived. For example the `ACPMenu`-pip would default to `aCPMenu.xml`, requiring the class to explicitly override the default filename with `acpMenu.xml` for readability. + +### Example + +```xml + + + + + + + + + + + + + + acp/install_com.woltlab.wcf_3.0.php + +``` + +### Exceptions + +{% include callout.html content="These exceptions represent the built-in PIPs only, 3rd party plugins and apps may define their own exceptions." type="info" %} + +| PIP | Default Value | +|-------|-------| +| `acpTemplate` | `acptemplates.tar` | +| `file` | `files.tar` | +| `language` | `language/*.xml` | +| `script` | (No default value) | +| `sql` | `install.sql` | +| `template` | `templates.tar` | + +## acpMenu.xml + +### Renamed Categories + +The following categories have been renamed, menu items need to be adjusted to reflect the new names: + +| Old Value | New Value | +|-------|-------| +| `wcf.acp.menu.link.system` | `wcf.acp.menu.link.configuration` | +| `wcf.acp.menu.link.display` | `wcf.acp.menu.link.customization` | +| `wcf.acp.menu.link.community` | `wcf.acp.menu.link.application` | + +### Submenu Items + +Menu items can now offer additional actions to be accessed from within the menu using an icon-based navigation. This step avoids filling the menu with dozens of `Add …` links, shifting the focus on to actual items. Adding more than one action is not recommended and you should at maximum specify two actions per item. + +### Example + +```xml + + + wcf.acp.menu.link.user + 2 + + + + + wcf\acp\page\UserGroupListPage + wcf.acp.menu.link.group + admin.user.canEditGroup,admin.user.canDeleteGroup + + + + wcf\acp\form\UserGroupAddForm + + wcf.acp.menu.link.group.list + admin.user.canAddGroup + + fa-plus + +``` + +### Common Icon Names + +You should use the same icon names for the (logically) same task, unifying the meaning of items and making the actions predictable. + +| Meaning | Icon Name | Result | +|-------|-------|-------| +| Add or create | `fa-plus` | | +| Search | `fa-search` | | +| Upload | `fa-upload` | | + +## box.xml + +The [box][package_pip_box] PIP has been added. + +## cronjob.xml + +{% include callout.html content="Legacy cronjobs are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again." type="warning" %} + +Cronjobs can now be assigned a name using the name attribute as in ``, it will be used to identify cronjobs during an update or delete. + +## eventListener.xml + +{% include callout.html content="Legacy event listeners are assigned a non-deterministic generic name, the only way to assign them a name is removing them and then adding them again." type="warning" %} + +Event listeners can now be assigned a name using the name attribute as in ``, it will be used to identify event listeners during an update or delete. + +## menu.xml + +The [menu][package_pip_menu] PIP has been added. + +## menuItem.xml + +The [menuItem][package_pip_menu-item] PIP has been added. + +## objectType.xml + +The definition `com.woltlab.wcf.user.dashboardContainer` has been removed, it was previously used to register pages that qualify for dashboard boxes. Since WoltLab Suite 3.0, all pages registered through the `page.xml` are valid containers and therefore there is no need for this definition anymore. + +The definitions `com.woltlab.wcf.page` and `com.woltlab.wcf.user.online.location` have been superseded by the `page.xml`, they're no longer supported. + +## option.xml + +The `module.display` category has been renamed into `module.customization`. + +## page.xml + +The [page][package_pip_page] PIP has been added. + +## pageMenu.xml + +The `pageMenu.xml` has been superseded by the `page.xml` and is no longer available. + +{% include links.html %} diff --git a/docs/migration_wcf-21_php.md b/docs/migration_wcf-21_php.md new file mode 100644 index 00000000..cc9febf8 --- /dev/null +++ b/docs/migration_wcf-21_php.md @@ -0,0 +1,135 @@ +--- +title: WCF 2.1.x - PHP +sidebar: sidebar +permalink: migration_wcf-21_php.html +folder: migration/wcf-21 +--- + +## Message Processing + +WoltLab Suite 3.0 finally made the transition from raw bbcode to bbcode-flavored HTML, with many new features related to message processing being added. This change impacts both message validation and storing, requiring slightly different APIs to get the job done. + +### Input Processing for Storage + +The returned HTML is an intermediate representation with a maximum of meta data embedded into it, designed to be stored in the database. Some bbcodes are replaced during this process, for example `[b]…[/b]` becomes `…`, while others are converted into a metacode tag for later processing. + +```php +process($message, $messageObjectType, $messageObjectID); +$html = $processor->getHtml(); +``` + +The `$messageObjectID` can be zero if the element did not exist before, but it should be non-zero when saving an edited message. + +### Embedded Objects + +Embedded objects need to be registered after saving the message, but once again you can use the processor instance to do the job. + +```php +process($message, $messageObjectType, $messageObjectID); +$html = $processor->getHtml(); + +// at this point the message is saved to database and the created object +// `$example` is a `DatabaseObject` with the id column `$exampleID` + +$processor->setObjectID($example->exampleID); +if (\wcf\system\message\embedded\object\MessageEmbeddedObjectManager::getInstance()->registerObjects($processor)) { + // there is at least one embedded object, this is also the point at which you + // would set `hasEmbeddedObjects` to true (if implemented by your type) + (new \wcf\data\example\ExampleEditor($example))->update(['hasEmbeddedObjects' => 1]); +} +``` + +### Rendering the Message + +The output processor will parse the intermediate HTML and finalize the output for display. This step is highly dynamic and allows for bbcode evaluation and contextual output based on the viewer's permissions. + +```php +process($html, $messageObjectType, $messageObjectID); +$renderedHtml = $processor->getHtml(); +``` + +#### Simplified Output + +At some point there can be the need of a simplified output HTML that includes only basic HTML formatting and reduces more sophisticated bbcodes into a simpler representation. + +```php +setOutputType('text/simplified-html'); +$processor->process(…); +``` + +#### Plaintext Output + +The `text/plain` output type will strip down the simplified HTML into pure text, suitable for text-only output such as the plaintext representation of an email. + +```php +setOutputType('text/plain'); +$processor->process(…); +``` + +### Rebuilding Data + +#### Converting from BBCode + +{% include callout.html content="Enabling message conversion for HTML messages is undefined and yields unexpected results." type="warning" %} + +Legacy message that still use raw bbcodes must be converted to be properly parsed by the html processors. This process is enabled by setting the fourth parameter of `process()` to `true`. + +```php +process($html, $messageObjectType, $messageObjectID, true); +$renderedHtml = $processor->getHtml(); +``` + +#### Extracting Embedded Objects + +The `process()` method of the input processor is quite expensive, as it runs through the full message validation including the invocation of HTMLPurifier. This is perfectly fine when dealing with single messages, but when you're handling messages in bulk to extract their embedded objects, you're better of with `processEmbeddedContent()`. This method deconstructs the message, but skips all validation and expects the input to be perfectly valid, that is the output of a previous run of `process()` saved to storage. + +```php +processEmbeddedContent($html, $messageObjectType, $messageObjectID); + +// invoke `MessageEmbeddedObjectManager::registerObjects` here +``` + +## Breadcrumbs / Page Location + +{% include callout.html content="Breadcrumbs used to be added left to right, but parent locations are added from the bottom to the top, starting with the first ancestor and going upwards. In most cases you simply need to reverse the order." type="warning" %} + +Breadcrumbs used to be a lose collection of arbitrary links, but are now represented by actual page objects and the control has shifted over to the `PageLocationManager`. + +```php +add(new \wcf\system\breadcrumb\Breadcrumb('title', 'link')); + +// after +\wcf\system\page\PageLocationManager::getInstance()->addParentLocation($pageIdentifier, $pageObjectID, $object); +``` + +## Pages and Forms + +The property `$activeMenuItem` has been deprecated for the front end and is no longer evaluated at runtime. Recognition of the active item is entirely based around the invoked controller class name and its definition in the page table. You need to properly [register your pages](package_pip_page.html) for this feature to work. + +## Search + +### ISearchableObjectType + +Added the `setLocation()` method that is used to set the current page location based on the search result. + +### SearchIndexManager + +The methods `SearchIndexManager::add()` and `SearchIndexManager::update()` have been deprecated and forward their call to the new method `SearchIndexManager::set()`. + +{% include links.html %} diff --git a/docs/migration_wcf-21_templates.md b/docs/migration_wcf-21_templates.md new file mode 100644 index 00000000..6666780b --- /dev/null +++ b/docs/migration_wcf-21_templates.md @@ -0,0 +1,215 @@ +--- +title: WCF 2.1.x - Templates +sidebar: sidebar +permalink: migration_wcf-21_templates.html +folder: migration/wcf-21 +--- + +## Page Layout + +The template structure has been overhauled and it is no longer required nor recommended to include internal templates, such as `documentHeader`, `headInclude` or `userNotice`. Instead use a simple `{include file='header'}` that now takes care of of the entire application frame. + +* Templates must not include a trailing `` after including the `footer` template. +* The `documentHeader`, `headInclude` and `userNotice` template should no longer be included manually, the same goes with the `` element, please use `{include file='header'}` instead. +* The `sidebarOrientation` variable for the `header` template has been removed and no longer works. +* `header.boxHeadline` has been unified and now reads `header.contentHeader` + +Please see the full example at the end of this page for more information. + +## Sidebars + +Sidebars are now dynamically populated by the box system, this requires a small change to unify the markup. Additionally the usage of `
` has been deprecated due to browser inconsistencies and bugs and should be replaced with `section.box`. + +Previous markup used in WoltLab Community Framework 2.1 and earlier: + +```html +
+ + +
+ +
+
+``` + +The new markup since WoltLab Suite 3.0: + +```html +
+

+ +
+ +
+
+``` + +## Forms + +The input tag for session ids `SID_INPUT_TAG` has been deprecated and no longer yields any content, it can be safely removed. In previous versions forms have been wrapped in `
…
` which no longer has any effect and should be removed. + +If you're using the preview feature for WYSIWYG-powered input fields, you need to alter the preview button include instruction: + +```smarty +{include file='messageFormPreviewButton' previewMessageObjectType='com.example.foo.bar' previewMessageObjectID=0} +``` + +*The message object id should be non-zero when editing.* + +## Icons + +The old `.icon-` classes have been removed, you are required to use the official `.fa-` class names from FontAwesome. This does not affect the generic classes `.icon` (indicates an icon) and `.icon` (e.g. `.icon16` that sets the dimensions), these are still required and have not been deprecated. + +Before: + +```html + +``` + +Now: + +```html + +``` + +### Changed Icon Names + +Quite a few icon names have been renamed, the official wiki lists the [new icon names](https://github.com/FortAwesome/Font-Awesome/wiki/Upgrading-from-3.2.1-to-4) in FontAwesome 4. + +## Changed Classes + +* `.dataList` has been replaced and should now read `
    ` (same applies to `
      `) +* `.framedIconList` has been changed into `.userAvatarList` + +## Removed Elements and Classes + +* `
]]> + + + + + + + +``` + +{% include links.html %} diff --git a/docs/package_pip_menu-item.md b/docs/package_pip_menu-item.md new file mode 100644 index 00000000..1dc822fc --- /dev/null +++ b/docs/package_pip_menu-item.md @@ -0,0 +1,49 @@ +--- +title: Menu Item Package Installation Plugin +sidebar: sidebar +permalink: package_pip_menu-item.html +folder: package/pip +parent: package_pip +--- + +Adds menu items to existing menus. + +## Components + +Each item is described as an `` element with the mandatory attribute `identifier` that should follow the naming pattern `.`, e.g. `com.woltlab.wcf.Dashboard`. + +### `` + +The target menu that the item should be added to, requires the internal identifier set by creating a menu through the [menu.xml][package_pip_menu]. + +### `` + +{% include languageCode.html %} + +The title is displayed as the link title of the menu item and can be fully customized by the administrator, thus is immutable after deployment. Supports multiple `<title>` elements to provide localized values. + +### `<page>` + +The page that the link should point to, requires the internal identifier set by creating a page through the [page.xml][package_pip_page]. + +## Example + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/menuItem.xsd"> + <import> + <item identifier="com.woltlab.wcf.Dashboard"> + <menu>com.woltlab.wcf.MainMenu</menu> + <title language="de">Dashboard + Dashboard + com.woltlab.wcf.Dashboard + + + + + + + +``` + +{% include links.html %} diff --git a/docs/package_pip_menu.md b/docs/package_pip_menu.md new file mode 100644 index 00000000..ea0a3f2f --- /dev/null +++ b/docs/package_pip_menu.md @@ -0,0 +1,54 @@ +--- +title: Menu Package Installation Plugin +sidebar: sidebar +permalink: package_pip_menu.html +folder: package/pip +parent: package_pip +--- + +Deploy and manage menus that can be placed anywhere on the site. + +## Components + +Each item is described as a `` element with the mandatory attribute `identifier` that should follow the naming pattern `.`, e.g. `com.woltlab.wcf.MainMenu`. + +### `` + +{% include languageCode.html %} + +The internal name displayed in the admin panel only, can be fully customized by the administrator and is immutable. Only one value is accepted and will be picked based on the site's default language, but you can provide localized values by including multiple `<title>` elements. + +### `<box>` + +The following elements of the [box PIP](package_pip_box.html) are supported, please refer to the documentation to learn more about them: + +* `<position>` +* `<showHeader>` +* `<visibleEverywhere>` +* `<visibilityExceptions>` +* `cssClassName` + +## Example + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/2019/menu.xsd"> + <import> + <menu identifier="com.woltlab.wcf.FooterLinks"> + <title language="de">Footer-Links + Footer Links + + + footer + boxMenuLinkGroup + 0 + 1 + + + + + + + + +``` diff --git a/docs/package_pip_object-type-definition.md b/docs/package_pip_object-type-definition.md new file mode 100644 index 00000000..07a6a04c --- /dev/null +++ b/docs/package_pip_object-type-definition.md @@ -0,0 +1,41 @@ +--- +title: Object Type Definition Package Installation Plugin +sidebar: sidebar +permalink: package_pip_object-type-definition.html +folder: package/pip +parent: package_pip +--- + +Registers an object type definition. +An object type definition is a blueprint for a certain behaviour that is particularized by [objectTypes](package_pip_object-type.html). +As an example: Tags can be attached to different types of content (such as forum posts or gallery images). +The bulk of the work is implemented in a generalized fashion, with all the tags stored in a single database table. +Certain things, such as permission checking, need to be particularized for the specific type of content, though. +Thus tags (or rather “taggable content”) are registered as an object type definition. +Posts are then registered as an object type, implementing the “taggable content” behaviour. + +Other types of object type definitions include attachments, likes, polls, subscriptions, or even the category system. + +## Components + +Each item is described as a `` element with the mandatory child `` that should follow the naming pattern `.`, e.g. `com.woltlab.wcf.example`. + +### `` + +Optional + +The name of the PHP interface [objectTypes](package_pip_object-type.html) have to implement. + +## Example + +```xml + + + + + com.woltlab.wcf.example + wcf\system\example\IExampleObjectType + + + +``` diff --git a/docs/package_pip_object-type.md b/docs/package_pip_object-type.md new file mode 100644 index 00000000..19548620 --- /dev/null +++ b/docs/package_pip_object-type.md @@ -0,0 +1,46 @@ +--- +title: Object Type Package Installation Plugin +sidebar: sidebar +permalink: package_pip_object-type.html +folder: package/pip +parent: package_pip +--- + +Registers an object type. +Read about object types in the [objectTypeDefinition](package_pip_object-type-definition.html) PIP. + +## Components + +Each item is described as a `` element with the mandatory child `` that should follow the naming pattern `.`, e.g. `com.woltlab.wcf.example`. + +### `` + +The `` of the [objectTypeDefinition](package_pip_object-type-definition.html). + +### `` + +The name of the class providing the object types's behaviour, +the class has to implement the `` interface of the object type definition. + +### `<*>` + +Optional + +Additional fields may be defined for specific definitions of object types. +Refer to the documentation of these for further explanation. + +## Example + +```xml + + + + + com.woltlab.wcf.example + com.woltlab.wcf.rebuildData + wcf\system\worker\ExampleRebuildWorker + 130 + + + +``` diff --git a/docs/package_pip_option.md b/docs/package_pip_option.md new file mode 100644 index 00000000..d6346e49 --- /dev/null +++ b/docs/package_pip_option.md @@ -0,0 +1,177 @@ +--- +title: Option Package Installation Plugin +sidebar: sidebar +permalink: package_pip_option.html +folder: package/pip +parent: package_pip +--- + +Registers new options. +Options allow the administrator to configure the behaviour of installed packages. +The specified values are exposed as PHP constants. + +## Category Components + +Each category is described as an `` element with the mandatory attribute `name`. + +### `` + +Optional + +The category’s parent category. + +### `` + +Optional + +Specifies the order of this option within the parent category. + +### `` + +Optional + +The options element can contain a comma-separated list of options of which at least one needs to be enabled for the category to be shown to the administrator. + +## Option Components + +Each option is described as an `