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 ` ``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

My Plugin


Settings

A boolean:
An array of checkboxes that are selected by default:

A simple input-field of any common type:
A simple textarea:
Array of textareas:

2D-Array of numbers that persist even when empty (but not empty rows):

Same with persisting empty rows, but not empty numbers, if no row is given null will get saved:

Array of numbers (new: 42, step: 21):

Select with dynamic options:
Select that loads faster: Array of Key-shortcuts (new: Ctrl+Shift+7):

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 `_. You should also take a look at the helper-functions within `Settings `_ in order to create your own plugins. There are a few methods that take response to call the methods of other plugins when fittingly.