closedquestion-8.x-3.x-dev/assets/js/libraries/jquery/jquery.soEditor.js
assets/js/libraries/jquery/jquery.soEditor.js
/**
* soEditor
* based on boilerplate version 1.3
* @param {object} $ A jQuery object
**/
(function ($) {
"use strict"; //ECMA5 strict modus
$.soEditor = function (element, settings) {
/* define vars
*/
/* this object will be exposed to other objects */
var publicObj = this;
//the version number of the plugin
publicObj.version = "1.0";
/* this object holds functions used by the plugin boilerplate */
var _helper = {
/**
* Call hooks, additinal parameters will be passed on to registered plugins
* @param {string} name
*/
doHook: function (name) {
var i;
var pluginFunctionArgs = [];
/* remove first two arguments */
for (i = 1; i < arguments.length; i++) {
pluginFunctionArgs.push(arguments[i]);
}
/* call plugin functions */
if (_globals.plugins !== undefined) {
/* call plugins */
$.each(_globals.plugins, function (soEditor, extPlugin) {
if (
extPlugin.__hooks !== undefined &&
extPlugin.__hooks[name] !== undefined
) {
extPlugin.__hooks[name].apply(publicObj, pluginFunctionArgs);
}
});
}
/* trigger event on main element */
_globals.$element.trigger(name, pluginFunctionArgs);
},
/**
* Initializes the plugin
*/
doInit: function () {
_helper.doHook(
"soEditor.beforeInit",
publicObj,
element,
settings
);
publicObj.init();
_helper.doHook("soEditor.init", publicObj);
},
/**
* Loads an external script
* @param {string} libName
* @param {string} errorMessage
*/
loadScript: function (libName, errorMessage) {
/* remember libname */
_cdnFilesToBeLoaded.push(libName);
/* load script */
$.ajax({
type: "GET",
url: _globals.dependencies[libName].cdnUrl,
success: function () {
/* forget libname */
_cdnFilesToBeLoaded.splice(_cdnFilesToBeLoaded.indexOf(libName), 1); //remove element from _cdnFilesToBeLoaded array
/* call init function when all scripts are loaded */
if (_cdnFilesToBeLoaded.length === 0) {
_helper.doInit();
}
},
fail: function () {
console.error(errorMessage);
},
dataType: "script",
cache: "cache"
});
},
/**
* Checks dependencies based on the _globals.dependencies object
* @returns {boolean}
*/
checkDependencies: function () {
var dependenciesPresent = true;
for (var libName in _globals.dependencies) {
var errorMessage =
"jquery.soEditor: Library " +
libName +
" not found! This may give unexpected results or errors.";
var doesExist = $.isFunction(_globals.dependencies[libName])
? _globals.dependencies[libName]
: _globals.dependencies[libName].doesExist;
if (doesExist.call() === false) {
if (
$.isFunction(_globals.dependencies[libName]) === false &&
_globals.dependencies[libName].cdnUrl !== undefined
) {
/* cdn url provided: Load script from external source */
_helper.loadScript(libName, errorMessage);
}
else {
console.error(errorMessage);
dependenciesPresent = false;
}
}
}
return dependenciesPresent;
}
};
/* keeps track of external libs loaded via their CDN */
var _cdnFilesToBeLoaded = [];
/* this object holds all global variables */
var _globals = {};
/* handle settings */
var defaultSettings = {
"input": $(''),
"question_options": [],
"allowDuplicates": false,
"horizontalAlignment": false,
"onlyOrder": -1
};
_globals.settings = {};
if ($.isPlainObject(settings) === true) {
_globals.settings = $.extend(true, {}, defaultSettings, settings);
}
else {
_globals.settings = defaultSettings;
}
/* this object contains a number of functions to test for dependencies,
* doesExist function should return TRUE if the library/browser/etc is present
*/
_globals.dependencies = {
/* check for jQuery 1.6+ to be present */
"jquery1.6+": {
doesExist: function () {
var jqv, jqv_main, jqv_sub;
if (window.jQuery) {
jqv = jQuery().jquery.split(".");
jqv_main = parseInt(jqv[0], 10);
jqv_sub = parseInt(jqv[1], 10);
if (jqv_main > 1 || (jqv_main === 1 && jqv_sub >= 6)) {
return true;
}
}
return false;
},
cdnUrl: "http://code.jquery.com/jquery-git1.js"
},
"jquery.ui.sortable": {
doesExist: function () {
return (jQuery.ui && jQuery.ui.sortable);
}
}
};
_helper.checkDependencies();
//this object holds all plugins
_globals.plugins = {};
/* register DOM elements
* jQuerified elements start with $
*/
_globals.$element = $(element);
_globals.$attributeWrapper = _globals.$element.closest('.xmlJsonEditor_attribute_container');
_globals.template = '<div class="soEditor_source soEditor_box">Available items:<ul class="soEditor_sortable" id="soEditor_sortable_source"></ul></div><div class="soEditor_target soEditor_box"></div>';
/**
* Init function
**/
publicObj.init = function () {
_globals.$element.append(_globals.template);
_globals.$element.after('<p class="xmlEditorAttributeFeedback" style="margin-bottom: 0;">The condition as it is stored in the database:</p>');
_globals.$sourceSortable = _globals.$element.find('#soEditor_sortable_source');
_globals.$targetSortables = [];
_globals.$sourceBox = _globals.$element.find('.soEditor_source.soEditor_box');
_globals.$targetBox = _globals.$element.find('.soEditor_target.soEditor_box');
_globals.$input = $(cfg('input'));
/* Add options and target lists */
$.each(cfg('question_options'), function () {
if (!this.id) {
disableEditor();
}
if (this.id.match('[a-zA-Z]')) {
_globals.$sourceSortable.append(getOptionElement(this.id, this.label, ''));
}
else if (this.id.match('[0-9]')) {
var $ul = $('<ul class="soEditor_sortable soEditor_target_sortable" data-id="soEditor_' + this.id + '"></ul>');
_globals.$targetBox.append('<span>' + formatHTML(this.label) + '</span>');
_globals.$targetBox.append($ul);
}
});
// Make sure there is always one target box.
if (_globals.$element.find('.soEditor_target_sortable').length === 0) {
var $ul = $('<ul class="soEditor_sortable soEditor_target_sortable" data-id="soEditor"></ul>');
_globals.$targetBox.append('<span>Selected items:</span>');
_globals.$targetBox.append($ul);
}
// Define regular expression items.
_globals.specialItems = {
"__dot__": {"re": '.', "label": "One arbitrary item"},
"__dot____star__": {"re": '.*', "label": "Zero or more arbitrary items"},
"__star__": {"re": '*', "label": "Repeat previous item zero or more times", "hiddenByDefault": true}
};
_globals.idToRe = $.extend({}, _globals.specialItems);
_globals.reToId = {};
$.each(_globals.idToRe, function (key, config) {
config = $.extend({}, config);
var re = config.re;
delete config.re;
config.id = key;
_globals.reToId[re] = config;
});
// Add regular expression items to list.
if (cfg('onlyOrder') !== 1) {
$.each(_globals.idToRe, function (key, config) {
var hiddenClass = (config.hiddenByDefault === true || cfg('allowSpecialElements') === false) ? ' soEditor_hiddenByDefault' : '';
_globals.$sourceSortable.append(getOptionElement(key, config.label, 'soEditor_special' + hiddenClass));
});
}
// Provide for only order questions
if (cfg('onlyOrder') === 1) {
if (_globals.$input.val() === '') {
$(".soEditor_target_sortable").append(_globals.$sourceSortable.children());
updateAnswerInput();
}
_globals.$sourceBox.hide();
}
// Provide for horizontal questions
if (cfg('horizontalAlignment') === true) {
_globals.$element.addClass('soEditor_horizontal');
}
// Init the drag/drop functionality.
var copyHelper;
_globals.$sourceSortable.sortable({
"connectWith": ".soEditor_sortable",
"forcePlaceholderSize": false,
"helper": function (e, li) {
if (li.hasClass('soEditor_special') || cfg('allowDuplicates')) {
copyHelper = li.clone().insertAfter(li);
return li.clone();
}
return li;
},
"stop": function () {
if (copyHelper) {
copyHelper.remove();
}
formatAvailableItemsSortable();
formatTargetItemsSortable();
updateAnswerInput();
}
});
$(".soEditor_target_sortable").sortable({
"connectWith": ".soEditor_sortable",
"receive": function () {
copyHelper = null;
},
"stop": function () {
formatAvailableItemsSortable();
formatTargetItemsSortable();
updateAnswerInput();
}
});
// Keep input and editor in sync
_globals.$input.on('keyup paste', function () {
window.clearTimeout(_globals.$input.data('soEditor_timeout'));
var to = window.setTimeout(function () {
updateQuestionGUI();
}, 1000);
_globals.$input.data('soEditor_timeout', to);
});
// Hide preset select.
$('#xmlEditor_presets_select', _globals.$element.parent()).hide();
updateQuestionGUI();
};
/**
* Disables the editor. *
*/
function disableEditor() {
$('#soEditor_feedback').remove();
_globals.$element.addClass('xmlEditorAttributeFeedback_error');
_globals.$element.after('<p id="soEditor_feedback" class="xmlJsonEditor_feedback_description" style="color:red;">This advanced condition is not (yet) supported by the editor. The editor might not display the condition correctly. Please use below input.</p>');
$('.xmlEditorAttributeFeedback', _globals.$attributeWrapper).addClass('soEditor_visible');
$('#xmlEditor_presets_select', _globals.$attributeWrapper).addClass('soEditor_visible');
_globals.$input.addClass('soEditor_visible');
}
/**
* Enables the editor.
*/
function enableEditor() {
$('#soEditor_feedback').remove();
_globals.$element.removeClass('xmlEditorAttributeFeedback_error');
$('.xmlEditorAttributeFeedback', _globals.$attributeWrapper).removeClass('soEditor_visible');
$('#xmlEditor_presets_select', _globals.$attributeWrapper).removeClass('soEditor_visible');
_globals.$input.removeClass('soEditor_visible');
}
/**
* Creates an option jQuery DOM element.
*
* @param {string} dataId
* @param {string} label
* @param {string} classAttr
* @returns {object}
*/
function getOptionElement(dataId, label, classAttr) {
classAttr = classAttr ? ' ' + classAttr : '';
return $('<li data-id="soEditor_' + dataId + '" class="soEditor_option' + classAttr + '">' + formatHTML(label) + '</li>');
}
/**
* Return all anagrams of a number of characters.
*
* @param {array} chars
* E.g. ['a', 'b', 'c']
* @returns {array}
* E.g. ['abc', 'acb', 'bca', 'bac', 'cab', 'cba']
*/
function getAnagrams(chars) {
function swap(chars, i, j) {
var tmp = chars[i];
chars[i] = chars[j];
chars[j] = tmp;
}
var counter = [],
anagrams = [],
length = chars.length,
i;
for (i = 0; i < length; i++) {
counter[i] = 0;
}
anagrams.push(chars.join(""));
i = 0;
while (i < length) {
if (counter[i] < i) {
swap(chars, i % 2 === 1 ? counter[i] : 0, i);
counter[i]++;
i = 0;
anagrams.push(chars.join(''));
}
else {
counter[i] = 0;
i++;
}
}
return anagrams;
}
/**
* Updates the answer input based on the question GUI.
*/
function updateAnswerInput() {
var re_items_raw = [];
var re_items = [];
//Collect regular expression items.
_globals.$element.find('.soEditor_target_sortable').each(function () {
var $target_sortable = $(this);
if ($target_sortable.attr('data-id') !== undefined) {
re_items_raw.push($target_sortable.attr('data-id'));
}
$target_sortable.find('li').each(function () {
re_items_raw.push($(this).attr('data-id'));
});
});
if (cfg('onlyOrder') === 2) {
/* 1. Deal with 'Only select, order of options does not matter' */
var currentTargetListId = '';
var tl_re_items = {}; // This will hold the items per target box.
var targetListIds = [""]; // This will remember the order of the target lists.
// Get items per target list.
$.each(re_items_raw, function (i, re_item) {
re_item = re_item.replace(/soEditor_?/g, '');
tl_re_items[currentTargetListId] = $.isArray(tl_re_items[currentTargetListId]) ? tl_re_items[currentTargetListId] : [];
var isSpecialItem = typeof _globals.specialItems[re_item] !== 'undefined';
// Change target?
if (re_item.match('[0-9]')) {
currentTargetListId = re_item;
targetListIds.push(currentTargetListId);
return;
}
else {
// Get items per target.
if (_globals.idToRe[re_item]) {
re_item = _globals.idToRe[re_item].re;
}
if (re_item !== '' && (cfg('allowDuplicates') === true || tl_re_items[currentTargetListId].indexOf(re_item) < 0 || isSpecialItem)) {
tl_re_items[currentTargetListId].push(re_item);
}
}
});
$.each(targetListIds, function (i, targetListId) {
if (targetListId !== '') {
re_items.push(targetListId);
}
tl_re_items[targetListId] = $.isArray(tl_re_items[targetListId]) ? tl_re_items[targetListId] : [];
if (tl_re_items[targetListId].length > 0) {
re_items.push('(');
re_items.push('(' + getAnagrams(tl_re_items[targetListId]).join(')|(') + ')');
re_items.push(')');
}
});
}
else {
/* 2. Deal with 'Select & order' and 'Only order': get a flat list of reg exp items. */
$.each(re_items_raw, function (i, re_item) {
re_item = re_item.replace(/soEditor_?/g, '');
if (_globals.idToRe[re_item] !== undefined) {
re_item = _globals.idToRe[re_item].re;
}
re_items[i] = re_item;
});
}
// Fill input
_globals.$input.val('^' + re_items.join('') + '$');
// Keep input and editor in sync
_globals.$input.trigger('change');
}
/**
* Updates the question GUI based on the value of the input.
*/
function updateQuestionGUI() {
/* Process regular expression */
var val = _globals.$input.val();
var re_targets;
var re_items_raw;
var re_items = [];
var shouldDisableEditor = false;
if (cfg('onlyOrder') === 2) {
/* 1. Deal with 'Only select, order of options does not matter' */
// Get items per target.
re_items_raw = val.split(/[\d]/).filter(function (str) {
return (str === "" || str === "^" || str === "$") ? false : true;
});
// Get target ids.
re_targets = val.split(/[^\d]/).filter(function (str) {
return str === "" ? false : true;
});
if (re_targets.length === 0) {
re_targets.push(""); // In case there is 1 target, fake its id.
}
// Rebuild val with unique items per target box.
val = "";
$.each(re_items_raw, function (i, re_item_raw) {
var re_item = re_item_raw.split("|")[0];
val += re_targets[i] + re_item;
});
}
/* 2. Get a flat list of reg exp items. */
re_items_raw = val.split('');
$.each(re_items_raw, function (i, re_item) {
var removed_item;
if (_globals.reToId[re_item] !== undefined) {
re_item = _globals.reToId[re_item].id;
}
switch (re_item) {
case '__negate__':
if (i > 0) {
re_items.push(re_item);
}
break;
case '$':
break;
case '__star__':
// Replace '__dot__'-element followed by '__star__'-element
// by '__dot____star__'-element
if (re_items[re_items.length - 1] === '__dot__') {
removed_item = re_items.pop();
re_items.push(removed_item + re_item);
}
break;
case '__opencurlybrace__':
case '__closecurlybrace__':
case '__opensquarebracket__':
case '__closequarebracket__':
shouldDisableEditor = true;
break;
default:
re_items.push(re_item);
break;
}
});
/* Update question */
// Get a list with unique items in the 'available items' box
_globals.$sourceSortable.append($(".soEditor_target_sortable").children());
formatAvailableItemsSortable();
var $target = $('.soEditor_target_sortable').eq(0);
$.each(re_items, function (i, re_item) {
// Change target?
if (re_item.match('[0-9]')) {
$target = $('[data-id="soEditor_' + re_item + '"]');
return;
}
// Prevent all strange DOM ids from being generated.
if (cfg('onlyOrder') !== 2 && re_item.match('[^a-zA-z0-9]')) {
shouldDisableEditor = true;
}
else if (re_item.match('[^a-zA-z0-9_\(\)\|]')) { // Only select can contain more items.
shouldDisableEditor = true;
return;
}
// Move item.
var $move_item = $('[data-id="soEditor_' + re_item + '"]', _globals.$sourceSortable);
if ($move_item.hasClass('soEditor_special') || cfg('allowDuplicates')) {
$move_item = $move_item.clone(); // Clone if special item or allowDuplicates
}
// Append item to target
$target.append($move_item);
});
if (shouldDisableEditor === true) {
disableEditor();
}
else {
enableEditor();
}
formatTargetItemsSortable();
}
/**
* Returns a setting
*
* @param {string} paramName
* @returns {mixed}
*/
function cfg(paramName) {
var returnVal;
if (paramName === 'onlyOrder') {
returnVal = parseInt(_globals.settings.onlyOrder, 10);
returnVal = isNaN(returnVal) ? -1 : returnVal;
}
else {
returnVal = _globals.settings[paramName];
}
return returnVal;
}
/**
* Remove duplicates from a list.
*
* @param {object} $ul
* A jQuery DOM list object.
*/
function sortableUniqueItems($ul, ignoreSpecialItems) {
ignoreSpecialItems = typeof ignoreSpecialItems === 'undefined' ? false : ignoreSpecialItems;
var seen = {};
$ul.find('li').each(function () {
// Remove duplicates.
var $li = $(this);
var id = $li.attr('data-id');
var re_item = id.replace(/soEditor_?/g, '');
var isSpecialItem = typeof _globals.specialItems[re_item] !== 'undefined';
if (seen[id] === true && ((isSpecialItem === true && ignoreSpecialItems === false) || isSpecialItem === false)) {
$li.remove();
}
else {
seen[id] = true;
}
});
}
/**
* Format 'available items' sortable.
*/
function formatAvailableItemsSortable() {
// Make sure sortable has unique items.
sortableUniqueItems(_globals.$sourceSortable);
// Unset margin left set by formatTargetItemsSortable.
_globals.$sourceSortable.find('li').css({"margin-left": ''});
}
/**
* Format target item sortables.
*/
function formatTargetItemsSortable() {
// Deal with 'Only select, order of options does not matter'
if (cfg('onlyOrder') === 2 && cfg('allowDuplicates') === false) {
$(".soEditor_target_sortable").each(function () {
sortableUniqueItems($(this), true); // Make sure each target sortable has unique items.
});
}
}
/**
* Formats HTML
*
* @param {string} html
* @returns {string}
* The formatted contents.
*/
function formatHTML(html) {
/* Handle Media tags */
var inlineTag = '', imgHTML = 'img', mediaObj, i;
var html, styleAttr;
var matches = html.match(/\[\[.*?\]\]/g);
if (matches) {
for (i = 0; i < matches.length; i++) {
inlineTag = matches[i];
inlineTag = inlineTag.replace('[[', '').replace(']]', '');
mediaObj = JSON.parse(inlineTag);
if (mediaObj && mediaObj.attributes && (mediaObj.attributes['data-src'] || mediaObj.attributes['src'])) {
mediaObj.attributes['data-src'] = mediaObj.attributes['data-src'] ? mediaObj.attributes['data-src'] : mediaObj.attributes['src'];
styleAttr = mediaObj.attributes.style ? ' style="' + mediaObj.attributes.style + '"' : '';
imgHTML = '<img src="' + mediaObj.attributes['data-src'] + '" width="' + mediaObj.attributes['width'] + '" height="' + mediaObj.attributes['height'] + '"' + styleAttr + ' />';
}
html = html.replace(matches[i], imgHTML);
}
}
return html;
}
/* initialize soEditor
*/
if (_cdnFilesToBeLoaded.length === 0) {
_helper.doInit();
}
};
$.fn.soEditor = function (settings) {
return this.each(function () {
if (undefined === $(this).data("soEditor")) {
var plugin = new $.soEditor(this, settings);
$(this).data("soEditor", plugin);
}
});
};
})(jQuery);
