var DomainInfo = null; var viewHelpers = { getFormGroup: function(keypath, setting, values, isAdvanced) { if (setting.hidden) { return ""; } form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + " " + (setting.deprecated ? Settings.DEPRECATED_CLASS : "" ) + "' " + "data-keypath='" + keypath + "'>"; setting_value = _(values).valueForKeyPath(keypath); if (_.isUndefined(setting_value) || _.isNull(setting_value)) { if (_.has(setting, 'default')) { setting_value = setting.default; } else { setting_value = ""; } } label_class = 'control-label'; function common_attrs(extra_classes) { extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" + setting.name + "' name='" + keypath + "' " + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; } if (setting.type === 'checkbox') { if (setting.label) { form_group += "<label class='" + label_class + "'>" + setting.label + "</label>" } form_group += "<div class='toggle-checkbox-container'>" form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") + "/>" if (setting.help) { form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>"; } form_group += "</div>" } else { input_type = _.has(setting, 'type') ? setting.type : "text" if (setting.label) { form_group += "<label for='" + keypath + "' class='" + label_class + "'>" + setting.label + "</label>"; } if (input_type === 'table') { form_group += makeTable(setting, keypath, setting_value) } else { if (input_type === 'select') { form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'" _.each(setting.options, function(option) { form_group += "<option value='" + option.value + "'" + (option.value == setting_value ? 'selected' : '') + ">" + option.label + "</option>" }); form_group += "</select>" form_group += "<input type='hidden'" + common_attrs() + "value='" + setting_value + "'>" } else if (input_type === 'button') { // Is this a button that should link to something directly? // If so, we use an anchor tag instead of a button tag if (setting.href) { form_group += "<a href='" + setting.href + "'style='display: block;' role='button'" + common_attrs("btn " + setting.classes) + " target='_blank'>" + setting.button_label + "</a>"; } else { form_group += "<button " + common_attrs("btn " + setting.classes) + ">" + setting.button_label + "</button>"; } } else { if (input_type == 'integer') { input_type = "text" } form_group += "<input type='" + input_type + "'" + common_attrs() + "placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") + "' value='" + (_.has(setting, 'password_placeholder') ? setting.password_placeholder : setting_value) + "'/>" } if (setting.help) { form_group += "<span class='help-block'>" + setting.help + "</span>" } } } form_group += "</div>" return form_group } } function showSpinnerAlert(title) { swal({ title: title, text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>', html: true, showConfirmButton: false, allowEscapeKey: false }); } function reloadSettings(callback) { $.getJSON(Settings.endpoint, function(data){ _.extend(data, viewHelpers); for (var spliceIndex in Settings.extraGroupsAtIndex) { data.descriptions.splice(spliceIndex, 0, Settings.extraGroupsAtIndex[spliceIndex]); } for (var endGroupIndex in Settings.extraGroupsAtEnd) { data.descriptions.push(Settings.extraGroupsAtEnd[endGroupIndex]); } data.descriptions = data.descriptions.map(function(x) { x.hidden = x.hidden || (x.show_on_enable && data.values[x.name] && !data.values[x.name].enable); return x; }); $('#panels').html(Settings.panelsTemplate(data)); Settings.data = data; Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); Settings.afterReloadActions(data); // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); $('[data-toggle="tooltip"]').tooltip(); Settings.pendingChanges = 0; // call the callback now that settings are loaded if (callback) { callback(true); } }).fail(function() { // call the failure object since settings load faild if (callback) { callback(false); } }); } function postSettings(jsonSettings) { // POST the form JSON to the domain-server settings.json endpoint so the settings are saved $.ajax(Settings.endpoint, { data: JSON.stringify(jsonSettings), contentType: 'application/json', type: 'POST' }).done(function(data){ if (data.status == "success") { if ($(".save-button-text").html() === SAVE_BUTTON_LABEL_RESTART) { showRestartModal(); } else { location.reload(true); } } else { showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) reloadSettings(); } }).fail(function(){ showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) reloadSettings(); }); } $(document).ready(function(){ $(document).on('click', '.save-button', function(e){ saveSettings(); e.preventDefault(); }); $.ajaxSetup({ timeout: 20000, }); reloadSettings(function(success){ if (success) { // if there was a hash in the URL, jump to it now if (location.hash) { location.href = location.hash; } } else { swal({ title: '', type: 'error', text: Strings.LOADING_SETTINGS_ERROR }); } }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ addTableRow($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ deleteTableRow($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ addTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ deleteTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ toggleTableCategory($(this).closest('tr')); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ moveTableRow($(this).closest('tr'), true); }); $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ moveTableRow($(this).closest('tr'), false); }); $('#' + Settings.FORM_ID).on('keyup', function(e){ var $target = $(e.target); if (e.keyCode == 13) { if ($target.is('table input')) { // capture enter in table input // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click sibling = $target.parent('td').next(); if (sibling.hasClass(Settings.DATA_COL_CLASS)) { // set focus to next input sibling.find('input').focus(); } else { // jump over the re-order row, if that's what we're on if (sibling.hasClass(Settings.REORDER_BUTTONS_CLASS)) { sibling = sibling.next(); } // for tables with categories we add the entry and setup the new row on enter if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); // set focus to the first input in the new row $target.closest('table').find('tr.inputs input:first').focus(); } else { var tableRows = sibling.parent(); var tableBody = tableRows.parent(); // if theres no more siblings, we should jump to a new row if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); } } } } else if ($target.is('input')) { $target.change().blur(); } e.preventDefault(); } }); $('#' + Settings.FORM_ID).on('keypress', function(e){ if (e.keyCode == 13) { e.preventDefault(); } }); $('#' + Settings.FORM_ID).on('change input propertychange', '.' + Settings.TRIGGER_CHANGE_CLASS , function(e){ // this input was changed, add the changed data attribute to it $(this).attr('data-changed', true); badgeForDifferences($(this)); }); $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){ // this checkbox was changed, add the changed data attribute to it $(this).attr('data-changed', true); badgeForDifferences($(this)); }); // Bootstrap switch in table $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { // Bootstrap switches in table: set the changed data attribute for all rows in table. var row = $(this).closest('tr'); if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); updateDataChangedForSiblingRows(row, true); badgeForDifferences($(this)); } }); $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { // Bootstrap switches in table: set the changed data attribute for all rows in table. var row = $(this).closest('tr'); if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); updateDataChangedForSiblingRows(row, true); badgeForDifferences($(this)); } }); $('#' + Settings.FORM_ID).on('change', 'select', function(){ $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change(); }); var panelsSource = $('#panels-template').html(); Settings.panelsTemplate = _.template(panelsSource); }); function dynamicButton(button_id, text) { return $("<button type='button' id='" + button_id + "' class='btn btn-primary'>" + text + "</button>"); } function showSpinnerAlert(title) { swal({ title: title, text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>', html: true, showConfirmButton: false, allowEscapeKey: false }); } function parseJSONResponse(xhr) { try { return JSON.parse(xhr.responseText); } catch (e) { } return null; } function validateInputs() { // check if any new values are bad var tables = $('table'); var inputsValid = true; var tables = $('table'); // clear any current invalid rows $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); function markParentRowInvalid(rowChild) { $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); } _.each(tables, function(table) { // validate keys specificially for spaces and equality to an existing key var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); var keyWithSpaces = false; var empty = false; var duplicateKey = false; _.each(newKeys, function(keyCell) { var keyVal = $(keyCell).children('input').val(); if (keyVal.indexOf(' ') !== -1) { keyWithSpaces = true; markParentRowInvalid(keyCell); return; } // make sure the key isn't empty if (keyVal.length === 0) { empty = true markParentRowInvalid(input) return; } // make sure we don't have duplicate keys in the table var otherKeys = $(table).find('td.key').not(keyCell); _.each(otherKeys, function(otherKeyCell) { var keyInput = $(otherKeyCell).children('input'); var lowerNewValue = keyVal.toLowerCase(); if (keyInput.length) { if ($(keyInput).val().toLowerCase() == lowerNewValue) { duplicateKey = true; } } else if ($(otherKeyCell).html().toLowerCase() == lowerNewValue) { duplicateKey = true; } if (duplicateKey) { markParentRowInvalid(keyCell); return; } }); }); if (keyWithSpaces) { showErrorMessage("Error", "Key contains spaces"); inputsValid = false; return } if (empty) { showErrorMessage("Error", "Empty field(s)"); inputsValid = false; return } if (duplicateKey) { showErrorMessage("Error", "Two keys cannot be identical"); inputsValid = false; return; } }); return inputsValid; } var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; function saveSettings() { if (validateInputs()) { // POST the form JSON to the domain-server settings.json endpoint so the settings are saved var canPost = true; // disable any inputs not changed $("input:not([data-changed])").each(function () { $(this).prop('disabled', true); }); // grab a JSON representation of the form via form2js var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); // re-enable all inputs $("input").each(function () { $(this).prop('disabled', false); }); // remove focus from the button $(this).blur(); if (Settings.handlePostSettings === undefined) { console.log("----- saveSettings() called ------"); console.log(formJSON); // POST the form JSON to the domain-server settings.json endpoint so the settings are saved postSettings(formJSON); } else { Settings.handlePostSettings(formJSON) } } } function makeTable(setting, keypath, setting_value) { var isArray = !_.has(setting, 'key'); var categoryKey = setting.categorize_by_key; var isCategorized = !!categoryKey && isArray; if (!isArray && setting.can_order) { setting.can_order = false; } var html = ""; if (setting.help) { html += "<span class='help-block'>" + setting.help + "</span>" } var nonDeletableRowKey = setting["non-deletable-row-key"]; var nonDeletableRowValues = setting["non-deletable-row-values"]; html += "<table class='table table-bordered' " + "data-short-name='" + setting.name + "' name='" + keypath + "' " + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " + "data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"; if (setting.caption) { html += "<caption>" + setting.caption + "</caption>" } // Column groups if (setting.groups) { html += "<tr class='headers'>" _.each(setting.groups, function (group) { html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>" }) if (!setting.read_only) { if (setting.can_order) { html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>"; } html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>" } html += "</tr>" } // Column names html += "<tr class='headers'>" if (setting.numbered === true) { html += "<td class='number'><strong>#</strong></td>" // Row number } if (setting.key) { html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key } var numVisibleColumns = 0; _.each(setting.columns, function(col) { if (!col.hidden) numVisibleColumns++; html += "<td " + (col.hidden ? "style='display: none;'" : "") + "class='data " + (col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data }) if (!setting.read_only) { if (setting.can_order) { numVisibleColumns++; html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>"; } numVisibleColumns++; html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>"; } // populate rows in the table from existing values var row_num = 1; if (keypath.length > 0 && _.size(setting_value) > 0) { var rowIsObject = setting.columns.length > 1; _.each(setting_value, function(row, rowIndexOrName) { var categoryPair = {}; var categoryValue = ""; if (isCategorized) { categoryValue = rowIsObject ? row[categoryKey] : row; categoryPair[categoryKey] = categoryValue; if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); } } html += "<tr class='" + Settings.DATA_ROW_CLASS + "' " + (isCategorized ? ("data-category='" + categoryValue + "'") : "") + " " + (isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + (isArray ? Settings.DATA_ROW_INDEX + "='" + (row_num - 1) + "'" : "" ) + ">"; if (setting.numbered === true) { html += "<td class='numbered'>" + row_num + "</td>" } if (setting.key) { html += "<td class='key'>" + rowIndexOrName + "</td>" } var isNonDeletableRow = !setting.can_add_new_rows; _.each(setting.columns, function(col) { var colValue, colName; if (isArray) { colValue = rowIsObject ? row[col.name] : row; colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); } else { colValue = row[col.name]; colName = keypath + "." + rowIndexOrName + "." + col.name; } isNonDeletableRow = isNonDeletableRow || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); if (isArray && col.type === "checkbox" && col.editable) { html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" + "<input type='checkbox' class='form-control table-checkbox' " + "name='" + colName + "'" + (colValue ? " checked" : "") + "/>" + "</td>"; } else if (isArray && col.type === "time" && col.editable) { html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" + "<input type='time' class='form-control table-time' name='" + colName + "' " + "value='" + (colValue || col.default || "00:00") + "'/>" + "</td>"; } else { // Use a hidden input so that the values are posted. html += "<td class='" + Settings.DATA_COL_CLASS + "' " + (col.hidden ? "style='display: none;'" : "") + "name='" + colName + "'>" + colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/>" + "</td>"; } }); if (!setting.read_only) { if (setting.can_order) { html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+ "'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>" + "<a href='javascript:void(0);' class='" + Settings.MOVE_DOWN_SPAN_CLASSES + "'></a></td>" } if (isNonDeletableRow) { html += "<td></td>"; } else { html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'><a href='javascript:void(0);' class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></a></td>"; } } html += "</tr>" if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { html += makeTableInputs(setting, categoryPair, categoryValue); } row_num++ }); } // populate inputs in the table for new values if (!setting.read_only) { if (setting.can_add_new_categories) { html += makeTableCategoryInput(setting, numVisibleColumns); } if (setting.can_add_new_rows || setting.can_add_new_categories) { html += makeTableHiddenInputs(setting, {}, ""); } } html += "</table>" return html; } function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { var html = "<tr class='" + Settings.DATA_CATEGORY_CLASS + "' data-key='" + categoryKey + "' data-category='" + categoryValue + "'>" + "<td colspan='" + (numVisibleColumns - 1) + "' class='" + Settings.TOGGLE_CATEGORY_COLUMN_CLASS + "'>" + "<span class='" + Settings.TOGGLE_CATEGORY_SPAN_CLASSES + " " + Settings.TOGGLE_CATEGORY_EXPANDED_CLASS + "'></span>" + "<span message='" + message + "'>" + categoryValue + "</span>" + "</td>" + ((canRemove) ? ( "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" + "<a href='javascript:void(0);' class='" + Settings.DEL_CATEGORY_SPAN_CLASSES + "'></a>" + "</td>" ) : ( "<td></td>" )) + "</tr>"; return html; } function makeTableHiddenInputs(setting, initialValues, categoryValue) { var html = "<tr class='inputs'" + (setting.can_add_new_categories && !categoryValue ? " hidden" : "") + " " + (categoryValue ? ("data-category='" + categoryValue + "'") : "") + " " + (setting.categorize_by_key ? ("data-keep-field='" + setting.categorize_by_key + "'") : "") + ">"; if (setting.numbered === true) { html += "<td class='numbered'></td>"; } if (setting.key) { html += "<td class='key' name='" + setting.key.name + "'>\ <input type='text' style='display: none;' class='form-control' placeholder='" + (_.has(setting.key, 'placeholder') ? setting.key.placeholder : "") + "' value=''>\ </td>" } _.each(setting.columns, function(col) { var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; if (col.type === "checkbox") { html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" + "<input type='checkbox' style='display: none;' class='form-control table-checkbox' " + "name='" + col.name + "'" + (defaultValue ? " checked" : "") + "/>" + "</td>"; } else if (col.type === "select") { html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" html += "<select style='display: none;' class='form-control' data-hidden-input='" + col.name + "'>'" for (var i in col.options) { var option = col.options[i]; html += "<option value='" + option.value + "' " + (option.value == defaultValue ? 'selected' : '') + ">" + option.label + "</option>"; } html += "</select>"; html += "<input type='hidden' class='table-dropdown form-control trigger-change' name='" + col.name + "' value='" + defaultValue + "'></td>"; } else { html += "<td " + (col.hidden ? "style='display: none;'" : "") + " class='" + Settings.DATA_COL_CLASS + "' " + "name='" + col.name + "'>" + "<input type='text' style='display: none;' class='form-control " + Settings.TRIGGER_CHANGE_CLASS + "' placeholder='" + (col.placeholder ? col.placeholder : "") + "' " + "value='" + (defaultValue || "") + "' data-default='" + (defaultValue || "") + "'" + (col.readonly ? " readonly" : "") + ">" + "</td>"; } }) if (setting.can_order) { html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>" } html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'><a href='javascript:void(0);' class='" + Settings.ADD_ROW_SPAN_CLASSES + "'></a></td>" html += "</tr>" return html } function makeTableCategoryInput(setting, numVisibleColumns) { var canAddRows = setting.can_add_new_rows; var categoryKey = setting.categorize_by_key; var placeholder = setting.new_category_placeholder || ""; var message = setting.new_category_message || ""; var html = "<tr class='" + Settings.DATA_CATEGORY_CLASS + " inputs' data-can-add-rows='" + canAddRows + "' " + "data-key='" + categoryKey + "' data-message='" + message + "'>" + "<td colspan='" + (numVisibleColumns - 1) + "'>" + "<input type='text' class='form-control' placeholder='" + placeholder + "'/>" + "</td>" + "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" + "<a href='javascript:void(0);' class='" + Settings.ADD_CATEGORY_SPAN_CLASSES + "'></a>" + "</td>" + "</tr>"; return html; } function getDescriptionForKey(key) { for (var i in Settings.data.descriptions) { if (Settings.data.descriptions[i].name === key) { return Settings.data.descriptions[i]; } } } var SAVE_BUTTON_LABEL_SAVE = "Save"; var SAVE_BUTTON_LABEL_RESTART = "Save and restart"; var reasonsForRestart = []; var numChangesBySection = {}; function badgeForDifferences(changedElement) { // figure out which group this input is in var panelParentID = changedElement.closest('.panel').attr('id'); // if the panel contains non-grouped settings, the initial value is Settings.initialValues var isGrouped = $('#' + panelParentID).hasClass('grouped'); if (isGrouped) { var initialPanelJSON = Settings.initialValues[panelParentID] ? Settings.initialValues[panelParentID] : {}; // get a JSON representation of that section var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]; } else { var initialPanelJSON = Settings.initialValues; // get a JSON representation of that section var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); } var badgeValue = 0 var description = getDescriptionForKey(panelParentID); // badge for any settings we have that are not the same or are not present in initialValues for (var setting in panelJSON) { if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") || (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) { badgeValue += 1; // add a reason to restart if (description && description.restart != false) { reasonsForRestart.push(setting); } } else { // remove a reason to restart if (description && description.restart != false) { reasonsForRestart = $.grep(reasonsForRestart, function(v) { return v != setting; }); } } } // update the list-group-item badge to have the new value if (badgeValue == 0) { badgeValue = "" } numChangesBySection[panelParentID] = badgeValue; var hasChanges = badgeValue > 0; if (!hasChanges) { for (var key in numChangesBySection) { if (numChangesBySection[key] > 0) { hasChanges = true; break; } } } $('.save-button').prop("disabled", !hasChanges); $('.save-button-text').html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); // add the badge to the navbar item and the panel header $("a[href='" + settingsGroupAnchor(Settings.path, panelParentID) + "'] .badge").html(badgeValue); $("#" + panelParentID + " .panel-heading .badge").html(badgeValue); // make the navbar dropdown show a badge that is the total of the badges of all groups var totalChanges = 0; $('.panel-heading .badge').each(function(index){ if (this.innerHTML.length > 0) { totalChanges += parseInt(this.innerHTML); } }); Settings.pendingChanges = totalChanges; if (totalChanges == 0) { totalChanges = "" } var totalBadgeClass = Settings.content_settings ? '.content-settings-badge' : '.domain-settings-badge'; $(totalBadgeClass).html(totalChanges); $('#hamburger-badge').html(totalChanges); } function addTableRow(row) { var table = row.parents('table'); var isArray = table.data('setting-type') === 'array'; var keepField = row.data("keep-field"); var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); var input_clone = row.clone(); // Change input row to data row var table = row.parents("table"); var setting_name = table.attr("name"); row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); if (!isArray) { // show the key input var keyInput = row.children(".key").children("input"); // whenever the keyInput changes, re-badge for differences keyInput.on('change input propertychange', function(e){ // update siblings in the row to have the correct name var currentKey = $(this).val(); $(this).closest('tr').find('.value-col input').each(function(index){ var input = $(this); if (currentKey.length > 0) { input.attr("name", setting_name + "." + currentKey + "." + input.parent().attr('name')); } else { input.removeAttr("name"); } }); badgeForDifferences($(this)); }); } // if this is an array, add the row index (which is the index of the last row + 1) // as a data attribute to the row var row_index = 0; if (isArray) { var previous_row = row.siblings('.' + Settings.DATA_ROW_CLASS + ':last'); if (previous_row.length > 0) { row_index = parseInt(previous_row.attr(Settings.DATA_ROW_INDEX), 10) + 1; } else { row_index = 0; } row.attr(Settings.DATA_ROW_INDEX, row_index); } var focusChanged = false; _.each(row.children(), function(element) { if ($(element).hasClass("numbered")) { // Index row var numbers = columns.children(".numbered") if (numbers.length > 0) { $(element).html(parseInt(numbers.last().text()) + 1) } else { $(element).html(1) } } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { $(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><a href='javascript:void(0);'" + " class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a><a href='javascript:void(0);' class='" + Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>") } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { // Change buttons var anchor = $(element).children("a") anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) } else if ($(element).hasClass("key")) { var input = $(element).children("input") input.show(); } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { // show inputs var input = $(element).find("input"); input.show(); var isCheckbox = input.hasClass("table-checkbox"); var isDropdown = input.hasClass("table-dropdown"); if (isArray) { var key = $(element).attr('name'); // are there multiple columns or just one? // with multiple we have an array of Objects, with one we have an array of whatever the value type is var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length var newName = setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""); input.attr("name", newName); if (isDropdown) { // default values for hidden inputs inside child selects gets cleared so we need to remind it var selectElement = $(element).children("select"); selectElement.attr("data-hidden-input", newName); $(element).children("input").val(selectElement.val()); } } if (isArray && !focusChanged) { input.focus(); focusChanged = true; } // if we are adding a dropdown, we should go ahead and make its select // element is visible if (isDropdown) { $(element).children("select").attr("style", ""); } if (isCheckbox) { $(input).find("input").attr("data-changed", "true"); } else { input.attr("data-changed", "true"); } } else { console.log("Unknown table element"); } }); if (!isArray) { keyInput.focus(); } input_clone.children('td').each(function () { if ($(this).attr("name") !== keepField) { $(this).find("input").val($(this).children('input').attr('data-default')); } }); if (isArray) { updateDataChangedForSiblingRows(row, true) // the addition of any table row should remove the empty-array-row row.siblings('.empty-array-row').remove() } badgeForDifferences($(table)) row.after(input_clone) } function deleteTableRow($row) { var $table = $row.closest('table'); var categoryName = $row.data("category"); var isArray = $table.data('setting-type') === 'array'; $row.empty(); if (!isArray) { if ($row.attr('name')) { $row.html("<input type='hidden' class='form-control' name='" + $row.attr('name') + "' data-changed='true' value=''>"); } else { // for rows that didn't have a key, simply remove the row $row.remove(); } } else { if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { // This is the last row of the category, so delete the header $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); } if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { updateDataChangedForSiblingRows($row); // this isn't the last row - we can just remove it $row.remove(); } else { // this is the last row, we can't remove it completely since we need to post an empty array $row .removeClass(Settings.DATA_ROW_CLASS) .removeClass(Settings.NEW_ROW_CLASS) .removeAttr("data-category") .addClass('empty-array-row') .html("<input type='hidden' class='form-control' name='" + $table.attr("name").replace('[]', '') + "' " + "data-changed='true' value=''>"); } } // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated badgeForDifferences($table); } function addTableCategory($categoryInputRow) { var $input = $categoryInputRow.find("input").first(); var categoryValue = $input.prop("value"); if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { $categoryInputRow.addClass("has-warning"); setTimeout(function () { $categoryInputRow.removeClass("has-warning"); }, 400); return; } var $rowInput = $categoryInputRow.next(".inputs").clone(); if (!$rowInput) { console.error("Error cloning inputs"); } var canAddRows = $categoryInputRow.data("can-add-rows"); var message = $categoryInputRow.data("message"); var categoryKey = $categoryInputRow.data("key"); var width = 0; $categoryInputRow .children("td") .each(function () { width += $(this).prop("colSpan") || 1; }); $input .prop("value", "") .focus(); $rowInput.find("td[name='" + categoryKey + "'] > input").first() .prop("value", categoryValue); $rowInput .attr("data-category", categoryValue) .addClass(Settings.NEW_ROW_CLASS); var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); $categoryInputRow .before($newCategoryRow) .before($rowInput); if (canAddRows) { $rowInput.removeAttr("hidden"); } else { addTableRow($rowInput); } } function deleteTableCategory($categoryHeaderRow) { var categoryName = $categoryHeaderRow.data("category"); $categoryHeaderRow .closest("table") .find("tr[data-category='" + categoryName + "']") .each(function () { if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { deleteTableRow($(this)); } else { $(this).remove(); } }); } function toggleTableCategory($categoryHeaderRow) { var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); var categoryName = $categoryHeaderRow.data("category"); var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); if (wasExpanded) { $icon .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); } else { $icon .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); } $categoryHeaderRow .closest("table") .find("tr[data-category='" + categoryName + "']") .toggleClass("contracted", wasExpanded); } function moveTableRow(row, move_up) { var table = $(row).closest('table') var isArray = table.data('setting-type') === 'array' if (!isArray) { return; } if (move_up) { var prev_row = row.prev() if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) { prev_row.before(row) } } else { var next_row = row.next() if (next_row.hasClass(Settings.DATA_ROW_CLASS)) { next_row.after(row) } } // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated badgeForDifferences($(table)); // figure out which group this row is in var panelParentID = row.closest('.panel').attr('id'); // get the short name for the setting from the table var tableShortName = row.closest('table').data('short-name'); var changed = tableHasChanged(panelParentID, tableShortName); $(table).find('.' + Settings.DATA_ROW_CLASS).each(function(){ var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input'); if (changed) { hiddenInput.attr('data-changed', true); } else { hiddenInput.removeAttr('data-changed'); } }); } function tableHasChanged(panelParentID, tableShortName) { // get a JSON representation of that section var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] if (Settings.initialValues[panelParentID]) { var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] } else { var initialPanelSettingJSON = {}; } return !_.isEqual(panelSettingJSON, initialPanelSettingJSON); } function updateDataChangedForSiblingRows(row, forceTrue) { // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true // unless it matches the inital set of values if (!forceTrue) { // figure out which group this row is in var panelParentID = row.closest('.panel').attr('id') // get the short name for the setting from the table var tableShortName = row.closest('table').data('short-name') // if they are equal, we don't need data-changed isTrue = tableHasChanged(panelParentID, tableShortName); } else { isTrue = true } row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') if (isTrue) { hiddenInput.attr('data-changed', isTrue); } else { hiddenInput.removeAttr('data-changed'); } }) } function cleanupFormValues(node) { if (node.type && node.type === 'checkbox') { return { name: node.name, value: node.checked ? true : false }; } else { return false; } } function showErrorMessage(title, message) { swal(title, message) }