Settings Framework
==========================
If you want to make your plugin customizable you may use the Settings Framework NodeBB offers.
Server-Side Access
------------------
First you need some default settings, just create a new object for this:
.. code :: javascript
var defaultSettings = {
booleans: {
someBool: true,
moreBools: [false, false, true]
},
strings: {
someString: 'hello world',
multiLineString: 'some\nlong\ntext',
arrayOfStrings: ['some\nlong\ntexts', 'and another one']
},
numbers: {
multiArrayDimensions: [[42,42],[21,21]],
multiArrayDimensions2: [[42,42],[]],
justSomeNumbers: [],
oneNumber: 3,
anotherNumber: 2
},
someKeys: ['C+S+#13'] // Ctrl+Shift+Enter
};
Now you can use the server-side settings-module to access the saved settings like this:
.. code :: javascript
var Settings = module.parent.require('./settings');
var mySettings = new Settings('myPlugin', '0.1', defaultSettings, function() {
// the settings are ready and can accessed.
console.log(mySettings === this); // true
console.log(this.get('strings.someString') === mySettings.get().strings.someString); // true
});
The second parameter should change at least every time the structure of default settings changes. Because of this it's
recommended to use your plugins version.
To use the settings client-side you need to create a WebSocket that delivers the result of `` mySettings.get() `` .
The mySettings-object will cache the settings, so be sure to use methods like `` mySettings.sync(callback) `` when the
settings got changed from somewhere else and `` mySettings.persist(callback) `` when you finished
`` mySettings.set(key, value) `` calls.
You need to create a socket-listener like following to allow the admin to initiate a synchronization with the settings
stored within database:
.. code :: javascript
var SocketAdmin = module.parent.require('./socket.io/admin');
SocketAdmin.settings.syncMyPlugin = function() {
mySettings.sync();
};
If you want to add a reset-functionality you need to create another socket-listener:
.. code :: javascript
SocketAdmin.settings.getMyPluginDefaults = function (socket, data, callback) {
callback(null, mySettings.createDefaultWrapper());
};
The methods of the `` mySettings `` object you probably want to use:
+ `` constructor() ``
+ `` sync([callback]) ``
Reloads the settings from database, overrides local changes.
+ `` persist([callback]) ``
Saves the local changes within database.
+ `` get([key]) ``
Returns the setting(s) identified by given key. If no key is provided the whole settings-object gets returned. If no
such setting is saved the default value gets returned.
+ `` set([key, ]value) ``
Sets the setting of given key to given value. Remember that it's just a local change, you need to call `` persist ``
in order to save the changes.
+ `` reset([callback]) ``
Persists the default settings.
+ `` getWrapper() ``
Returns the local object as it would get saved within database.
+ `` createWrapper(version, settings) ``
Creates an object like it would get saved within database containing given information and settings.
+ `` createDefaultWrapper() ``
Creates an object like it would get saved within database containing the default settings.
Client-Side Access
------------------
The next step is making the settings available to the admin.
You need to use the :doc: `hooks <hooks>` `` filter:admin.header.build `` (to display a link to your page within ACP) and
`` action:app.load `` (to create the needed route).
Within your page you can access the client-side Settings API via
.. code :: javascript
require(['settings'], function (settings) {
var wrapper = $('#my_form_id');
// [1]
settings.sync('myPlugin', wrapper);
// [2]
});
To make a button with the id `` save `` actually save the settings you can add the following at `` [2] `` :
.. code :: javascript
$('#save').click(function(event) {
event.preventDefault();
settings.persist('myPlugin', wrapper, function(){
socket.emit('admin.settings.syncMyPlugin');
});
});
As said before the server-side settings-object caches the settings, so we emit a WebSocket to notify the server to
synchronize the settings after they got persisted.
To use a reset-button you can add the following at `` [2] `` :
.. code :: javascript
$('#reset').click(function(event) {
event.preventDefault();
socket.emit('admin.settings.getMyPluginDefaults', null, function (err, data) {
settings.set('myPlugin', data, wrapper, function(){
socket.emit('admin.settings.syncMyPlugin');
});
});
});
There you go, the basic structure is done.
Now you need to add the form-fields.
Each field needs an attribute `` data-key `` to reference its position within the settings.
The Framework does support any fields whose jQuery-object provides the value via the `` val() `` method.
The plugin to use for a field gets determined by its `` data-type `` , `` type `` or tag-name in this order.
Additionally the following plugins are registered by default:
* array (types: div, array)
An Array of any other fields.
Uses the object within `` data-attributes `` to define the array-elements.
Uses `` data-new `` to define the value of new created elements.
* key (types: key)
A field to input keyboard-combinations.
* checkbox, number, select, textarea
Handle appropriate fields.
A full list of all attributes that may influence the behavior of the default Framework:
* data-key: the key to save/load the value within configuration-object
* data-type: highest priority type-definition to determine what kind of element it is or which plugin to associate
* type: normal priority type-definition
* data-empty: if `` false `` or `` 0 `` then values that are assumed as empty turn into null. data-empty of arrays affect their child-elements
* data-trim: if not `` false `` or `` 0 `` then values will get trimmed as defined by the elements type
* data-split: if set and the element doesn't belong to any plugin, it's value will get split and joined by its value into the field
* array-elements:
+ data-split: separator (HTML allowed) between the elements, defaults to `` ', ' ``
+ data-new: value to insert into new created elements
+ data-attributes: an object to set the attributes of the child HTML-elements. tagName as special key will set the tag-name of the child HTML-elements
* key-fields:
+ data-trim: if `` false `` or `` 0 `` then the value will get saved as string else as object providing following properties: `` ctrl `` , `` alt `` , `` shift `` , `` meta `` , `` code `` , `` char ``
+ data-split: separator between different modifiers and the key-code of the value that gets saved (only takes effect if trimming)
+ data-short: if not `` false `` or `` 0 `` then modifier-keys get saved as first uppercase character (only takes effect if trimming)
* select:
+ data-options: an array of objects containing `` text `` and `` value `` attributes.
The methods of the `` settings `` module:
+ `` registerPlugin(plugin[, types]) ``
Registers the given plugin and associates it to the given types if any, otherwise the plugins default types will get
used.
+ `` get() ``
Returns the saved object.
+ `` set(hash, settings[, wrapper[, callback[, notify]]]) ``
Refills the fields with given settings and persists them.
`` hash `` Identifies your plugins settings.
`` settings `` The object to save in database (settings-wrapper if you use server-side Settings Framework).
`` wrapper `` (default: 'form') The DOM-Element that contains all fields to fill.
`` callback `` (default: null) Gets called when done.
`` notify `` (default: true) Whether to display saved- and fail-notifications.
+ `` sync(hash[, wrapper[, callback]]) ``
Resets the settings to saved ones and refills the fields.
+ `` persist(hash[, wrapper[, callback[, notify]]]) ``
Reads the settings from given wrapper (default: 'form') and saves them within database.
For Settings 2.0 support the methods `` load `` and `` save `` are still available but not recommended.
Client-Side Example Template
------------------
An example template-file to use the same settings we already used server-side:
.. code :: html
<h1>My Plugin</h1>
<hr />
<form id="my_form_id">
<div class="row">
<p>
<h2>Settings</h2>
A boolean: <input type="checkbox" data-key="booleans.someBool"></input><br>
An array of checkboxes that are selected by default:
<div data-key="booleans.moreBools" data-attributes='{"data-type":"checkbox"}' data-new='true'></div><br>
A simple input-field of any common type: <input type="password" data-key="strings.someString"></input><br>
A simple textarea: <textarea data-key="strings.multiLineString"></textarea><br>
Array of textareas:
<div data-key="strings.arrayOfStrings" data-attributes='{"data-type":"textarea"}' data-new='Hello Kitty, ahem... World!'></div><br>
2D-Array of numbers that persist even when empty (but not empty rows):
<div data-key="numbers.multiArrayDimensions" data-split="<br>"
data-attributes='{"data-type":"array","data-attributes":{"type":"number"}}' data-new='[42,21]'></div><br>
Same with persisting empty rows, but not empty numbers, if no row is given null will get saved:
<div data-key="numbers.multiArrayDimensions2" data-split="<br>" data-empty="false"
data-attributes='{"data-type":"array","data-empty":true,"data-attributes":{"type":"number","data-empty":false}}' data-new='[42,21]'></div><br>
Array of numbers (new: 42, step: 21):
<div data-key="numbers.justSomeNumbers" data-attributes='{"data-type":"number","step":21}' data-new='42'></div><br>
Select with dynamic options:
<select data-key="numbers.oneNumber" data-options='[{"value":"2","text":"2"},{"value":"3","text":"3"}]'></select><br>
Select that loads faster:
<select data-key="numbers.anotherNumber"><br>
<option value="2">2</option>
<option value="3">3</option>
</select>
Array of Key-shortcuts (new: Ctrl+Shift+7):
<div data-key="someKeys" data-attributes='{"data-type":"key"}' data-new='Ctrl+Shift+#55'></div><br>
</p>
</div>
<button class="btn btn-lg btn-warning" id="reset">Reset</button>
<button class="btn btn-lg btn-primary" id="save">Save</button>
</form>
<script>
require(['settings'], function (settings) {
var wrapper = $('#my_form_id');
// [1]
settings.sync('myPlugin', wrapper);
$('#save').click(function(event) {
event.preventDefault();
settings.persist('myPlugin', wrapper, function(){
socket.emit('admin.settings.syncMyPlugin');
});
});
$('#reset').click(function(event) {
event.preventDefault();
socket.emit('admin.settings.getMyPluginDefaults', null, function (err, data) {
settings.set('myPlugin', data, wrapper, function(){
socket.emit('admin.settings.syncMyPlugin');
});
});
});
});
</script>
Custom Settings-Elements
------------------
If you want do define your own element-structure you can create a **plugin** for the Settings Framework.
This allows you to use a whole object like a single field which - besides comfort in using multiple similar objects -
allows you to use them within arrays.
A plugin is basically an object that contains at least an attribute `` types `` that contains an array of strings that
associate DOM-elements with your plugin.
You can add a plugin at `` [1] `` using the method `` settings.registerPlugin `` .
To customize the way the associated fields get interpreted you may add the following methods to your plugin-object:
All given elements are instances of JQuery.
All methods get called within Settings-scope.
+ `` use() ``
Gets called when the plugin gets registered.
+ `` [HTML-Element|JQuery] create(type, tagName, data) ``
Gets called when a new element should get created (eg. by expansion of an array).
+ `` destruct(element) ``
Gets called when the given element got removed from DOM (eg. by array-splice).
+ `` init(element) ``
Gets called when an element should get initialized (eg. after creation).
+ `` [value] get(element, trim, empty) ``
Gets called whenever the value of the given element is requested.
`` trim `` Whether the result should get trimmed.
`` empty `` Whether considered as empty values should get saved too.
+ `` set(element, value, trim) ``
Gets called whenever the value of the given element should be set to given one.
`` trim `` Whether the value is assumed as trimmed.
For further impression take a look at the
`default plugins <https://github.com/designcreateplay/NodeBB/tree/master/public/src/modules/settings> `_ .
You should also take a look at the helper-functions within
`Settings <https://github.com/designcreateplay/NodeBB/tree/master/public/src/modules/settings.js> `_ in order to create
your own plugins. There are a few methods that take response to call the methods of other plugins when fittingly.