MediaWiki:Gadget-SettingsUI.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/**
* Simple user interface for managing settings
* This script only cares for the user interface.
* It remains your task to save the settings
* e.g. using 'ext.gadget.SettingsManager' or 'jquery.jStorage' or 'jquery.cookie'
*
* @rev 1 (2012-09-19)
* @author Rillke, 2012
* @license This software is quadruple-licensed under GFDL, LGPL, GPL and CC-By-SA 3.0
* Choose the license(s) you like best
*
* Usage instructions: See "smMembers = {"
*/
// List the global variables for jsHint-Validation. Please make sure that it passes http://jshint.com/
// Scheme: globalVariable:allowOverwriting[, globalVariable:allowOverwriting][, globalVariable:allowOverwriting]
/*global jQuery:false, mediaWiki:false*/
// Set jsHint-options. You should not set forin or undef to false if your script does not validate.
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:false, browser:true*/
( function ( $, mw, undefined ) {
'use strict';
/********************************
**
** Translation
**
********************************/
mw.messages.set({
'sui-button-save': "OK",
'sui-button-save-page': "Temporarily on this page only",
'sui-button-save-account-publicly': "Publicly visible into your account",
'sui-button-save-account-private': "Into your account (private memory)",
'sui-button-save-browser': "Into your browser (local storage)",
'sui-button-save-cookie': "Into your browser (a cookie)",
'sui-button-cancel': "Cancel",
'sui-button-defaults': "Fill-in defaults",
'sui-title': "Preferences for $1",
'sui-intro': "Customise $1 according to your wishes",
'sui-save-location': "Where would you like to save the configuration for $1?",
'sui-save-location-title': "Location to store the preferences"
});
var smMembers, smPrivate, nOEvents = 'input.numbersOnly keyup.numbersOnly';
$(document).off(nOEvents, '.numbersOnly').on(nOEvents, '.numbersOnly', function () {
var oldVal = this.value,
newVal = oldVal.replace(/[^0-9]/g,'');
if (oldVal !== newVal) this.value = newVal;
});
function SUI(settings, tool, saveoptions, title, intro) {
// Invoked as a constructor
if (this.settings) {
if (settings) this.settings = settings;
if (tool) this.tool = tool;
if (saveoptions) this.saveoptions = saveoptions;
if (title) this.title = title;
if (intro) this.intro = intro;
return this;
// Invoked as a function
} else {
return new SUI(settings, tool, saveoptions, title, intro);
}
}
smPrivate = {
getObjLen: function(obj) {
var i = 0;
for (var elem in obj) {
if (obj.hasOwnProperty(elem)) i++;
}
return i;
},
each: function(obj, cb) {
var i = 0;
for (var elem in obj) {
if (obj.hasOwnProperty(elem)) {
if (false === cb(i, elem, obj[elem])) break;
i++;
}
}
return obj;
},
$objToSelect: function(obj) {
var $sel = $('<select>');
this.each(obj, function(i, k, v) {
$('<option>').attr({
value: v
}).text(k).appendTo($sel);
});
return $sel;
}
};
smMembers = {
constructor: SUI,
version: '0.0.1.0',
// * - required
//
// An array of settings-objects. A settings-object consists of
// label*, value, select (in case you wish a select, pass an object with caption-value-pairs OR a function that creats a select),
// name* (must be also valid for a HTML id attribute), min, max, default*
// e.g. { label: "A sample property description", value: 1, name: 'sample_property', min:0, max:100 }
settings: {},
// * The tool's name that makes use of settings-UI
tool: '',
// Which options to show (c.f. "sui-button-save-" at mw.messages.set)
saveoptions: ['page', 'account-publicly'],
// Title of the settings-UI
title: "",
// Introductory text on top of the settings
intro: "",
/**
* Show a dialog that contains controls for adjusting settings.
*
* @example
* window.mw.libs
.SettingsUI([
{ label: "A test setting label", value: 5, name: 'optionName', min:0, max:100, default: 0 },
{ label: "A second label", value: "cba", name: 'collatingOrder', select: { "Ascending": 'abc', "Descending": 'cba' }, default: "abc" }
], "MyTool")
.show().progress(__myProgressCallback).done(function() { console.log("Selected> DONE!") });
*
* @context {window.mw.libs.SettingsUI}
*
* @return {Object} jQuery Deferred object (http://api.jquery.com/category/deferred-object/)
*/
show: function() {
var sui = this,
$progress = new $.Deferred();
mw.loader.using([
'jquery.ui.dialog',
'jquery.ui.slider',
'jquery.ui.button',
'mediawiki.user',
'ext.gadget.libJQuery'
], function() {
sui._show($progress);
});
return $progress;
},
_show: function($progress) {
var sui = this,
$dlg,
$saveButton;
$dlg = sui.$dlg = $('<div>').attr({
id: 'sui_' + sui.tool,
title: sui.title || sui._msg('title')
});
$('<p>').attr('class', 'sui-intro').text(sui.intro || sui._msg('intro')).appendTo($dlg);
$dlg.$c = $('<div>').css('padding', '3px').appendTo($dlg);
smPrivate.each(sui.settings, function(i, c, s) {
var $sDiv = $('<div>', { id: 'sui_cfg_' + s.name + '_wrap' }).data('cfg', s),
vl = s.value,
id = 'sui_cfg_' + s.name,
$label, $valEl, $valEl2;
$label = $('<label>').attr({ 'for': id }).text(s.label).appendTo($sDiv);
$('<br>').appendTo($sDiv);
switch (typeof s['default']) {
case 'number':
$valEl = $('<input>').attr({ type: 'text', size: (s.max+'').length, id: id, 'class': 'numbersOnly' })
.focus(function() { $(this).select(); })
.keyup(function(e) {
var val = this.value = this.value.replace(/[^0-9]/g,'');
if (val) {
if (val > s.max) {
this.value = s.max;
} else if (val < s.min) {
this.value = s.min;
}
$valEl2.slider('option', 'value', this.value);
}
}).val(vl).appendTo($sDiv);
$valEl2 = $('<div>', { style: 'margin-top:5px; margin-bottom:5px;' }).slider({
min: s.min,
max: s.max,
value: vl,
change: function(e, ui) {
$valEl.val( ui.value );
},
slide: function(e, ui) {
$valEl.val( ui.value );
}
}).appendTo($sDiv);
break;
case 'string':
if ('function' === typeof s.select) {
$sDiv.append(s.select().attr('id', id).val(vl));
} else if ('object' === typeof s.select) {
$sDiv.append(smPrivate.$objToSelect(s.select).attr('id', id).val(vl));
} else {
$('<input>', { type: 'text', id: id, style: 'width:99%' }).val(vl).appendTo($sDiv);
}
break;
case 'boolean':
$valEl = $('<input>', { type: 'checkbox', id: id }).appendTo($sDiv);
if (vl) $valEl[0].checked = true;
break;
}
$sDiv.find('input,select').keyup(function(e) {
if (13 === Number(e.which)) {
$saveButton.click();
}
});
$sDiv.append('<hr/>', '<br/>').appendTo($dlg.$c);
});
var btns = {},
_saveSelectionDlg,
_onSave;
_onSave = function($el) {
var loc = $el.data('loc');
smPrivate.each(sui.settings, function(i, c, s) {
var val,
name = s.name,
id = '#sui_cfg_' + name,
$ctrl = $(id);
if ('boolean' === typeof s['default']) {
val = $ctrl[0].checked;
} else if ('number' === typeof s['default']) {
val = Number($ctrl.val());
} else {
val = $ctrl.val();
}
s.value = val;
});
$progress.resolve( "save", "User selected save location.", loc, sui.settings, $dlg );
};
_saveSelectionDlg = function() {
var $saveDlg = $('<div>').append($('<p>').text(sui._msg('save-location'))),
$btnOuter = $('<div>').attr({
'class': 'sui-cfg-saveselect-buttons'
}).appendTo($saveDlg),
$btnInner;
smPrivate.each(sui.saveoptions, function(i, c, o) {
if (o.indexOf('account') > 0 && mw.user.isAnon()) return;
var $btnInner = $('<div>').css('text-align', 'center').appendTo($btnOuter);
$('<button>', { role: 'button', 'class': 'ui-button-large', data: { loc: o } })
.text(sui._msg('button-save-' + o))
.click(function() {
// First resolve the Deferred
_onSave($(this));
// Then, close the dialog
$saveDlg.dialog('close');
})
.button()
.appendTo($btnInner);
});
$saveDlg.dialog({
title: sui._msg('save-location-title'),
modal: true,
width: Math.min(670, $(window).width()),
close: function () {
$(this).remove();
$progress.reject( "usercanceled", "User canceled." );
}
});
$progress.notify( "savedlg", "Save location select.", $dlg );
};
btns[sui._msg('button-save')] = _saveSelectionDlg;
btns[sui._msg('button-cancel')] = function() {
$(this).dialog('close');
};
btns[sui._msg('button-defaults')] = function() {
smPrivate.each(sui.settings, function(i, c, s) {
var $ctr = $('#sui_cfg_'+s.name);
if ('boolean' === typeof s['default']) {
$ctr[0].checked = s['default'];
} else {
$ctr.val(s['default']);
}
$ctr.keyup();
});
$progress.notify( "defaultsin", "Defaults set.", $dlg );
};
$dlg.dialog({
modal: true,
resizable: false,
width: Math.min(670, $(window).width()),
close: function () {
$(this).remove();
$progress.reject( "usercanceled", "User canceled." );
},
buttons: btns,
open: function() {
var $dlg = $(this),
$parent = $dlg.dialog('widget'),
$buttons = $parent.find('.ui-dialog-buttonpane button');
$saveButton = $buttons.eq(0).specialButton('proceed');
$buttons.eq(1).specialButton('cancel');
$buttons.eq(2).button({ icons: { primary: 'ui-icon-refresh' } });
$dlg.dialog('option', 'height', Math.min($parent.height(), $(window).height()));
if ($(window).scrollTop() > $parent.position().top) $dlg.dialog('option', 'position', 'top');
}
});
$progress.notify( "dlgup", "Dialog created.", $dlg );
},
hide: function() {
// TODO: Implement
},
_msg: function(key) {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = 'sui-' + args[0];
args[args.length] = this.tool;
return mw.message.apply(this, args).parse();
}
};
// Add members to prototype
SUI.fn = SUI.prototype = smMembers;
// Expose globally
window.mw.libs.SettingsUI = SUI;
}( jQuery, mediaWiki ));