diff options
author | Jared Dillard <jdillard@netgate.com> | 2016-03-16 14:19:04 -0500 |
---|---|---|
committer | Jared Dillard <jdillard@netgate.com> | 2016-03-16 14:25:27 -0500 |
commit | 9d1be24ef72c0c27fe7a297bf79ec5e4f552a390 (patch) | |
tree | 47be453db62ee886d797b26efdf3104731b0cce2 /src/usr/local/www/js | |
parent | 1c9818970fcc994d6599d794047397655f867a12 (diff) | |
download | pfsense-9d1be24ef72c0c27fe7a297bf79ec5e4f552a390.zip pfsense-9d1be24ef72c0c27fe7a297bf79ec5e4f552a390.tar.gz |
organize assets and put 3rd party into vendor folder
Diffstat (limited to 'src/usr/local/www/js')
-rw-r--r-- | src/usr/local/www/js/pfSense.js | 278 | ||||
-rw-r--r-- | src/usr/local/www/js/pfSenseHelpers.js | 645 |
2 files changed, 923 insertions, 0 deletions
diff --git a/src/usr/local/www/js/pfSense.js b/src/usr/local/www/js/pfSense.js new file mode 100644 index 0000000..3ca74c4 --- /dev/null +++ b/src/usr/local/www/js/pfSense.js @@ -0,0 +1,278 @@ +/* + * This file should only contain functions that will be used on more than 2 pages + */ + +$(function() { + // Attach collapsable behaviour to select options + (function() + { + var selects = $('select[data-toggle="collapse"]'); + + selects.on('change', function(){ + var options = $(this).find('option'); + var selectedValue = $(this).find(':selected').val(); + + options.each(function(){ + if ($(this).val() == selectedValue) + return; + + targets = $('.toggle-'+ $(this).val() +'.in:not(.toggle-'+ selectedValue +')'); + + // Hide related collapsables which are visible (.in) + targets.collapse('hide'); + + // Disable all invisible inputs + targets.find(':input').prop('disabled', true); + }); + + $('.toggle-' + selectedValue).collapse('show').find(':input').prop('disabled', false); + }); + + // Trigger change to open currently selected item + selects.trigger('change'); + })(); + + + // Add +/- buttons to certain Groups; to allow adding multiple entries + // This time making the buttons col-2 wide so they can fit on the same line as the + // rest of the group (providing the total width of the group is col-8 or less) + (function() + { + var groups = $('div.form-group.user-duplication-horiz'); + var controlsContainer = $('<div class="col-sm-2"></div>'); + var plus = $('<a class="btn btn-sm btn-success"><i class="fa fa-plus icon-embed-btn"></i>Add</a>'); + var minus = $('<a class="btn btn-sm btn-warning"><i class="fa fa-trash icon-embed-btn"></i>Delete</a>'); + + minus.on('click', function(){ + $(this).parents('div.form-group').remove(); + }); + + plus.on('click', function(){ + var group = $(this).parents('div.form-group'); + + var clone = group.clone(true); + clone.find('*').val(''); + clone.appendTo(group.parent()); + }); + + groups.each(function(idx, group){ + var controlsClone = controlsContainer.clone(true).appendTo(group); + minus.clone(true).appendTo(controlsClone); + + if (group == group.parentNode.lastElementChild) + plus.clone(true).appendTo(controlsClone); + }); + })(); + + // Add +/- buttons to certain Groups; to allow adding multiple entries + (function() + { + var groups = $('div.form-group.user-duplication'); + var controlsContainer = $('<div class="col-sm-10 col-sm-offset-2 controls"></div>'); + var plus = $('<a class="btn btn-xs btn-success"><i class="fa fa-plus icon-embed-btn"></i>Add</a>'); + var minus = $('<a class="btn btn-xs btn-warning"><i class="fa fa-trash icon-embed-btn"></i>Delete</a>'); + + minus.on('click', function(){ + $(this).parents('div.form-group').remove(); + }); + + plus.on('click', function(){ + var group = $(this).parents('div.form-group'); + + var clone = group.clone(true); + clone.find('*').removeAttr('value'); + clone.appendTo(group.parent()); + }); + + groups.each(function(idx, group){ + var controlsClone = controlsContainer.clone(true).appendTo(group); + minus.clone(true).appendTo(controlsClone); + + if (group == group.parentNode.lastElementChild) + plus.clone(true).appendTo(controlsClone); + }); + })(); + + // Automatically change IpAddress mask selectors to 128/32 options for IPv6/IPv4 addresses + $('span.pfIpMask + select').each(function (idx, select){ + var input = $(select).prevAll('input[type=text]'); + + input.on('change', function(e){ + var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128; + if (!isV6) + max = 32; + + if (input.val() == "") + return; + + // Eat all of the options with a value greater than max. We don't want them to be available + while (select.options[0].value > max) + select.remove(0); + + if (select.options.length < max) { + for (var i=select.options.length; i<=max; i++) + select.options.add(new Option(i, i), 0); + } + }); + + // Fire immediately + input.change(); + }); + + // Add confirm to all btn-danger buttons and fa-trash icons + // Use element title in the confirmation message, or if not available + // the element value + $('.btn-danger, .fa-trash').on('click', function(e){ + if(!($(this).hasClass('no-confirm'))) { + var msg = $.trim(this.textContent); + + if(!msg) + var msg = $.trim(this.value).toLowerCase(); + + var q = 'Are you sure you wish to '+ msg +'?'; + + if ($(this).attr('title') != undefined) + q = 'Are you sure you wish to '+ $(this).attr('title').toLowerCase() + '?'; + + if (!confirm(q)) { + e.preventDefault(); + e.stopPropagation(); // Don't leave ancestor(s) selected. + } + } + }); + + // Add toggle-all when there are multiple checkboxes + $('.control-label + .checkbox.multi').each(function() { + var a = $('<a name="btntoggleall" class="btn btn-xs btn-info"><i class="fa fa-check-square-o icon-embed-btn"></i>Toggle All</a>'); + + a.on('click', function() { + var wrap = $(this).parents('.form-group').find('.checkbox.multi'), + all = wrap.find('input[type=checkbox]'), + checked = wrap.find('input[type=checkbox]:checked'); + + all.prop('checked', (all.length != checked.length)); + }); + + a.appendTo($(this)); + }); + + // The need to NOT hide the advanced options if the elements therein are not set to the system + // default values makes it better to handle advanced option hiding in each PHP file so this is being + // disabled for now by changing the class name it acts on to "auto-advanced" + + // Hide advanced inputs by default + if ($('.auto-advanced').length > 0) + { + var advButt = $('<a id="toggle-advanced" class="btn btn-default">toggle advanced options</a>'); + advButt.on('click', function() { + $('.advanced').parents('.form-group').collapse('toggle'); + }); + + advButt.insertAfter($('#save')); + + $('.auto-advanced').parents('.form-group').collapse({toggle: true}); + } + + var originalLeave = $.fn.popover.Constructor.prototype.leave; + $.fn.popover.Constructor.prototype.leave = function(obj){ + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) + var container, timeout; + + originalLeave.call(this, obj); + + if(self.$tip && self.$tip.length) { + container = self.$tip; + timeout = self.timeout; + container.one('mouseenter', function(){ + //We entered the actual popover - call off the dogs + clearTimeout(timeout); + //Let's monitor popover content instead + container.one('mouseleave', function(){ + $.fn.popover.Constructor.prototype.leave.call(self, self); + }); + }) + } + }; + + // Enable popovers globally + $('[data-toggle="popover"]').popover({ delay: {show: 50, hide: 400} }); + + // Force correct initial state for toggleable checkboxes + $('input[type=checkbox][data-toggle="collapse"]:not(:checked)').each(function() { + $( $(this).data('target') ).addClass('collapse'); + }); + + $('input[type=checkbox][data-toggle="disable"]:not(:checked)').each(function() { + $( $(this).data('target') ).prop('disabled', true); + }); + + // Focus first input + $(':input:enabled:visible:first').focus(); + + // Run in-page defined events + while (func = window.events.shift()) + func(); +}); + +// Implement data-toggle=disable +// Source: https://github.com/synergic-cz/synergic-ui/blob/master/src/js/disable.js +;(function($, window, document) { + 'use strict'; + + var Disable = function($element) { + this.$element = $element; + }; + + Disable.prototype.toggle = function() { + this.$element.prop('disabled', !this.$element.prop('disabled')); + }; + + function Plugin(options) { + $(document).trigger('toggle.sui.disable'); + + this.each(function() { + var $this = $(this); + var data = $this.data('sui.disable'); + + if (!data) { + $this.data('sui.disable', (data = new Disable($this))); + } + + if (options === 'toggle') { + data.toggle(); + } + }); + + $(document).trigger('toggled.sui.disable'); + + return this; + } + + var old = $.fn.disable; + + $.fn.disable = Plugin; + $.fn.disable.Constructor = Disable; + + $.fn.disable.noConflict = function() { + $.fn.disable = old; + return this; + }; + + (function(Plugin, $, window) { + $(window).load(function() { + var $controls = $('[data-toggle=disable]'); + + $controls.each(function() { + var $this = $(this); + var eventType = $this.data('disable-event'); + if (!eventType) { + eventType = 'change'; + } + $this.on(eventType + '.sui.disable.data-api', function() { + Plugin.call($($this.data('target')), 'toggle'); + }); + }); + }); + }(Plugin, $, window, document)); +}(jQuery, window, document)); diff --git a/src/usr/local/www/js/pfSenseHelpers.js b/src/usr/local/www/js/pfSenseHelpers.js new file mode 100644 index 0000000..9e90e5d --- /dev/null +++ b/src/usr/local/www/js/pfSenseHelpers.js @@ -0,0 +1,645 @@ +/* ==================================================================== + * Copyright (c) 2004-2015 Electric Sheep Fencing, LLC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgment: + * "This product includes software developed by the pfSense Project + * for use in the pfSense software distribution. (http://www.pfsense.org/). + * + * 4. The names "pfSense" and "pfSense Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * coreteam@pfsense.org. + * + * 5. Products derived from this software may not be called "pfSense" + * nor may "pfSense" appear in their names without prior written + * permission of the Electric Sheep Fencing, LLC. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * + * "This product includes software developed by the pfSense Project + * for use in the pfSense software distribution (http://www.pfsense.org/). + * + * THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ==================================================================== + * + */ + +// These helper functions are used on many/most UI pages to hide/show/disable/enable form elements where required + +// Hides the <div> in which the specified input element lives so that the input, its label and help text are hidden +function hideInput(id, hide) { + if(hide) + $('#' + id).parent().parent('div').addClass('hidden'); + else + $('#' + id).parent().parent('div').removeClass('hidden'); +} + +// Hides the <div> in which the specified group input element lives so that the input, +// its label and help text are hidden +function hideGroupInput(id, hide) { + if(hide) + $('#' + id).parent('div').addClass('hidden'); + else + $('#' + id).parent('div').removeClass('hidden'); +} + +// Hides the <div> in which the specified checkbox lives so that the checkbox, its label and help text are hidden +function hideCheckbox(id, hide) { + if(hide) + $('#' + id).parent().parent().parent('div').addClass('hidden'); + else + $('#' + id).parent().parent().parent('div').removeClass('hidden'); +} + +// Disables the specified input element +function disableInput(id, disable) { + $('#' + id).prop("disabled", disable); +} + +// Hides all elements of the specified class. This will usually be a section +function hideClass(s_class, hide) { + if(hide) + $('.' + s_class).hide(); + else + $('.' + s_class).show(); +} + +// Hides all elements of the specified class assigned to a group. This will usually be a group +function hideGroupClass(s_class, hide) { + if(hide) + $('.' + s_class).parent().parent().parent().hide(); + else + $('.' + s_class).parent().parent().parent().show(); +} + +function hideSelect(id, hide) { + if(hide) + $('#' + id).parent('div').parent('div').addClass('hidden'); + else + $('#' + id).parent('div').parent('div').removeClass('hidden'); +} + +function hideMultiCheckbox(id, hide) { + if(hide) + $("[name=" + id + "]").parent().addClass('hidden'); + else + $("[name=" + id + "]").parent().removeClass('hidden'); +} + +// Hides the <div> in which the specified IP address element lives so that the input, its label and help text are hidden +function hideIpAddress(id, hide) { + if(hide) + $('#' + id).parent().parent().parent('div').addClass('hidden'); + else + $('#' + id).parent().parent().parent('div').removeClass('hidden'); +} + +// Hides all elements of the specified class belonging to a multiselect. +function hideMultiClass(s_class, hide) { + if(hide) + $('.' + s_class).parent().parent().hide(); + else + $('.' + s_class).parent().parent().show(); +} + +// Hides div whose label contains the specified text. (Good for StaticText) +function hideLabel(text, hide) { + + var element = $('label:contains(' + text + ')'); + + if(hide) + element.parent('div').addClass('hidden'); + else + element.parent('div').removeClass('hidden'); +} + +// Toggle table row checkboxes and background colors on the pages that use sortable tables: +// /usr/local/www/firewall_nat.php +// /usr/local/www/firewall_nat_1to1.php +// /usr/local/www/firewall_nat_out.php +// /usr/local/www/firewall_rules.php +// /usr/local/www/vpn_ipsec.php +// Striping of the tables is handled here, NOT with the Bootstrap table-striped class because it would +// get confused when rows are sorted or deleted. + +function fr_toggle(id, prefix) { + if (!prefix) + prefix = 'fr'; + + var checkbox = document.getElementById(prefix + 'c' + id); + checkbox.checked = !checkbox.checked; + fr_bgcolor(id, prefix); +} + +// Change background color of selected row based on state of checkbox +function fr_bgcolor(id, prefix) { + if (!prefix) + prefix = 'fr'; + + var row = $('#' + prefix + id); + + if ($('#' + prefix + 'c' + id).prop('checked') ) { + row.addClass('active'); + } else { + row.removeClass('active'); + } +} + +// The following functions are used by Form_Groups assigned a class of "repeatable" and provide the ability +// to add/delete rows of sequentially numbered elements, their labels and their help text +// See firewall_aliases_edit.php for an example + +// NOTE: retainhelp is a global var that defined prevents any help text from being deleted as lines are inserted. +// IOW it causes every row to have help text, not just the last row + +function setMasks() { + // Find all ipaddress masks and make dynamic based on address family of input + $('span.pfIpMask + select').each(function (idx, select){ + var input = $(select).prevAll('input[type=text]'); + + input.on('change', function(e){ + var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128; + if (!isV6) + max = 32; + + if (input.val() == "") + return; + + while (select.options.length > max) + select.remove(0); + + if (select.options.length < max) + { + for (var i=select.options.length; i<=max; i++) + select.options.add(new Option(i, i), 0); + } + }); + + // Fire immediately + input.change(); + }); +} + +// Complicated function to move all help text associated with this input id to the same id +// on the row above. That way if you delete the last row, you don't lose the help +function moveHelpText(id) { + $('#' + id).parent('div').parent('div').find('input, select, checkbox').each(function() { // For each <span></span> + var fromId = this.id; + var toId = decrStringInt(fromId); + var helpSpan; + + if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) { + if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) { + helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone(); + } else { + helpSpan = $('#' + fromId).parent('div').find('span:last').clone(); + } + if($(helpSpan).hasClass('help-block')) { + if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) { + $('#' + decrStringInt(fromId)).parent('div').after(helpSpan); + } + else { + $('#' + decrStringInt(fromId)).after(helpSpan); + } + } + } + }); +} + +// Increment the number at the end of the string +function bumpStringInt( str ) { + var data = str.match(/(\D*)(\d+)(\D*)/), newStr = ""; + + if( data ) + newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ]; + + return newStr || str; +} + +// Decrement the number at the end of the string +function decrStringInt( str ) { + var data = str.match(/(\D*)(\d+)(\D*)/), newStr = ""; + + if( data ) + newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ]; + + return newStr || str; +} + +// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about +// gaps, but I do :) +function renumber() { + var idx = 0; + + $('.repeatable').each(function() { + + $(this).find('input').each(function() { + $(this).prop("id", this.id.replace(/\d+$/, "") + idx); + $(this).prop("name", this.name.replace(/\d+$/, "") + idx); + }); + + $(this).find('select').each(function() { + $(this).prop("id", this.id.replace(/\d+$/, "") + idx); + $(this).prop("name", this.name.replace(/\d+$/, "") + idx); + }); + +// $(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx); + + idx++; + }); +} + +function delete_row(rowDelBtn) { + var rowLabel; + + // If we are deleting row zero, we need to save/restore the label + if (rowDelBtn == "deleterow0") { + rowLabel = $('#' + rowDelBtn).parent('div').parent('div').find('label').text(); + } + + $('#' + rowDelBtn).parent('div').parent('div').remove(); + renumber(); + checkLastRow(); + + if (rowDelBtn == "deleterow0") { + $('#' + rowDelBtn).parent('div').parent('div').find('label').text(rowLabel); + } +} + +function checkLastRow() { + if($('.repeatable').length <= 1) { + $('#deleterow0').hide(); + } else { + $('[id^=deleterow]').show(); + } +} + +function add_row() { + // Find the last repeatable group + var lastRepeatableGroup = $('.repeatable:last'); + + // Clone it + var newGroup = lastRepeatableGroup.clone(); + // Increment the suffix number for each input element in the new group + $(newGroup).find('input').each(function() { + $(this).prop("id", bumpStringInt(this.id)); + $(this).prop("name", bumpStringInt(this.name)); + if(!$(this).is('[id^=delete]')) + $(this).val(''); + }); + + // Do the same for selectors + $(newGroup).find('select').each(function() { + $(this).prop("id", bumpStringInt(this.id)); + $(this).prop("name", bumpStringInt(this.name)); + // If this selector lists mask bits, we need it to be reset to all 128 options + // and no items selected, so that automatic v4/v6 selection still works + if($(this).is('[id^=address_subnet]')) { + $(this).empty(); + for(idx=128; idx>0; idx--) { + $(this).append($('<option>', { + value: idx, + text: idx + })); + } + } + }); + + // And for "for" tags +// $(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for'))); + + $(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row + + // Insert the updated/cloned row + $(lastRepeatableGroup).after(newGroup); + + // Delete any help text from the group we have cloned + $(lastRepeatableGroup).find('.help-block').each(function() { + if((typeof retainhelp) == "undefined") + $(this).remove(); + }); + + setMasks(); + + checkLastRow(); + + // Autocomplete + if ( typeof addressarray !== 'undefined') { + $('[id^=address]').each(function() { + if(this.id.substring(0, 8) != "address_") { + $(this).autocomplete({ + source: addressarray + }); + } + }); + } + + // Now that we are no longer cloning the event handlers, we need to remove and re-add after a new row + // has been added to the table + $('[id^=delete]').unbind(); + $('[id^=delete]').click(function(event) { + if($('.repeatable').length > 1) { + if((typeof retainhelp) == "undefined") + moveHelpText(event.target.id); + + delete_row(event.target.id); + } + else + alert('You may not delete the last row!'); + }); + +} + +// These are action buttons, not submit buttons +$('[id^=addrow]').prop('type','button'); +$('[id^=delete]').prop('type','button'); + +// on click . . +$('[id^=addrow]').click(function() { + add_row(); +}); + +$('[id^=delete]').click(function(event) { + if($('.repeatable').length > 1) { + if((typeof retainhelp) == "undefined") + moveHelpText(event.target.id); + + delete_row(event.target.id); + } + else + alert('You may not delete the last row!'); +}); + +// "More information" handlers -------------------------------------------------------------------- + +// If there is an infoblock, automatically add an info icon that toggles its display + +var sfx = 0; + +$('.infoblock').each(function() { + // If the block has the class "blockopen" it is initially open + if (! $(this).hasClass("blockopen")) { + $(this).hide(); + } else { + $(this).removeClass("blockopen"); + } + + // Add the "i" icon before the infoblock, incrementing the icon id for each block (in case there are multiple infoblocks on a page) + $(this).before('<i class="fa fa-info-circle icon-pointer" style="color: #337AB7; font-size:20px; margin-left: 10px; margin-bottom: 10px;" id="showinfo' + sfx.toString() + '" title="More information"></i>'); + $(this).removeClass("infoblock"); + $(this).addClass("infoblock" + sfx.toString()); + sfx++; +}); + +// Show the help on clicking the info icon +$('[id^="showinfo"]').click(function() { + var id = $(this).attr("id"); + + $('.' + "infoblock" + id.substr(8)).toggle(); + document.getSelection().removeAllRanges(); // Ensure the text is un-selected (Chrome browser quirk) +}); +// ------------------------------------------------------------------------------------------------ + +// Put a dummy row into any empty table to keep IE happy +$('tbody').each(function(){ + $(this).html($.trim($(this).html())) +}); + +$('tbody:empty').html("<tr><td></td></tr>"); + +// Hide configuration button for panels without configuration +$('.container .panel-heading a.config').each(function (idx, el){ + var config = $(el).parents('.panel').children('.panel-footer'); + if (config.length == 1) + $(el).removeClass('hidden'); +}); + +// Initial state & toggle icons of collapsed panel +$('.container .panel-heading a[data-toggle="collapse"]').each(function (idx, el){ + var body = $(el).parents('.panel').children('.panel-body') + var isOpen = body.hasClass('in'); + + $(el).children('i').toggleClass('fa-plus-circle', !isOpen); + $(el).children('i').toggleClass('fa-minus-circle', isOpen); + + body.on('shown.bs.collapse', function(){ + $(el).children('i').toggleClass('fa-minus-circle', true); + $(el).children('i').toggleClass('fa-plus-circle', false); + }); + + body.on('hidden.bs.collapse', function(){ + $(el).children('i').toggleClass('fa-minus-circle', false); + $(el).children('i').toggleClass('fa-plus-circle', true); + }); +}); + + // Separator bar stuff ------------------------------------------------------------------------ + + // Globals + gColor = 'bg-info'; + newSeperator = false; + saving = false; + dirty = false; + + $("#addsep").prop('type' ,'button'); + + $("#addsep").click(function() { + if (newSeperator) { + return(false); + } + + gColor = 'bg-info'; + // Inset a temporary bar in which the user can enter some optional text + sepcols = $( "#ruletable tr th" ).length - 2; + + $('#ruletable > tbody:last').append('<tr>' + + '<td class="' + gColor + '" colspan="' + sepcols + '"><input id="newsep" placeholder="' + svbtnplaceholder + '" class="col-md-12" type="text" /></td>' + + '<td class="' + gColor + '" colspan="2"><button class="btn btn-primary btn-sm" id="btnnewsep"><i class="fa fa-save icon-embed-btn"></i>' + svtxt + '</button>' + + '<button class="btn btn-info btn-sm" id="btncncsep"><i class="fa fa-undo icon-embed-btn"></i>' + cncltxt + '</button>' + + ' ' + + ' <a id="sepclrblue" value="bg-info"><i class="fa fa-circle text-info icon-pointer"></i></a>' + + ' <a id="sepclrred" value="bg-danger"><i class="fa fa-circle text-danger icon-pointer"></i></a>' + + ' <a id="sepclrgreen" value="bg-success"><i class="fa fa-circle text-success icon-pointer"></i></a>' + + ' <a id="sepclrorange" value="bg-warning"><i class="fa fa-circle text-warning icon-pointer"></i></button>' + + '</td></tr>'); + + $('#newsep').focus(); + newSeperator = true; + + $("#btnnewsep").prop('type' ,'button'); + + // Watch escape and enter keys + $('#newsep').keyup(function(e) { + if(e.which == 27) { + $('#btncncsep').trigger('click'); + } + }); + + $('#newsep').keypress(function(e) { + if(e.which == 13) { + $('#btnnewsep').trigger('click'); + } + }); + + handle_colors(); + + // Remove the temporary separator bar and replace it with the final version containing the + // user's text and a delete icon + $("#btnnewsep").click(function() { + var septext = escapeHtml($('#newsep').val()); + sepcols = $( "#ruletable tr th" ).length - 1; + + $('#ruletable > tbody:last >tr:last').remove(); + $('#ruletable > tbody:last').append('<tr class="ui-sortable-handle separator">' + + '<td class="' + gColor + '" colspan="' + sepcols + '">' + '<span class="' + gColor + '">' + septext + '</span></td>' + + '<td class="' + gColor + '"><a href="#"><i class="fa fa-trash sepdel"></i></a>' + + '</td></tr>'); + + $('#order-store').removeAttr('disabled'); + newSeperator = false; + dirty = true; + }); + + // Cancel button + $('#btncncsep').click(function(e) { + e.preventDefault(); + $(this).parents('tr').remove(); + newSeperator = false; + }); + }); + + // Delete a separator row + $(function(){ + $('table').on('click','tr a .sepdel',function(e){ + e.preventDefault(); + $(this).parents('tr').remove(); + $('#order-store').removeAttr('disabled'); + dirty = true; + }); + }); + + // Compose an inout array containing the row #, color and text for each separator + function save_separators() { + var row = 0; + var sepinput; + var sepnum = 0; + + $('#ruletable > tbody > tr').each(function() { + if ($(this).hasClass('separator')) { + seprow = $(this).next('tr').attr("id"); + if (seprow == undefined) { + seprow = "fr" + row; + } + + sepinput = '<input type="hidden" name="separator[' + sepnum + '][row]" value="' + seprow + '"></input>'; + $('form').append(sepinput); + sepinput = '<input type="hidden" name="separator[' + sepnum + '][text]" value="' + escapeHtml($(this).find('td').text()) + '"></input>'; + $('form').append(sepinput); + sepinput = '<input type="hidden" name="separator[' + sepnum + '][color]" value="' + $(this).find('td').prop('class') + '"></input>'; + $('form').append(sepinput); + sepinput = '<input type="hidden" name="separator[' + sepnum + '][if]" value="' + iface + '"></input>'; + $('form').append(sepinput); + sepnum++; + } else { + if ($(this).parent('tbody').hasClass('user-entries')) { + row++; + } + } + }); + } + + function reindex_rules(section) { + var row = 0; + + section.find('tr').each(function() { + if(this.id) { + $(this).attr("id", "fr" + row); + $(this).attr("onclick", "fr_toggle(" + row + ")") + $(this).find('input:checkbox:first').each(function() { + $(this).attr("id", "frc" + row); + $(this).attr("onclick", "fr_toggle(" + row + ")"); + }); + + row++; + } + }); + } + + function handle_colors() { + $('[id^=sepclr]').prop("type", "button"); + + $('[id^=sepclr]').click(function () { + var color = $(this).attr('value'); + // Clear all the color classes + $(this).parent('td').prop('class', ''); + $(this).parent('td').prev('td').prop('class', ''); + // Install our new color class + $(this).parent('td').addClass(color); + $(this).parent('td').prev('td').addClass(color); + // Set the global color + gColor = color; + }); + } + + //JS equivalent to PHP htmlspecialchars() + function escapeHtml(text) { + var map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + return text.replace(/[&<>"']/g, function(m) { return map[m]; }); + } + // -------------------------------------------------------------------------------------------- + + // Select every option in the specified multiselect + function AllServers(id, selectAll) { + for (i = 0; i < id.length; i++) { + id.eq(i).prop('selected', selectAll); + } + } + + // Move all selected options from one multiselect to another + function moveOptions(From, To) { + var len = From.length; + var option; + + if (len > 0) { + for (i=0; i<len; i++) { + if (From.eq(i).is(':selected')) { + option = From.eq(i).val(); + value = From.eq(i).text(); + To.append(new Option(value, option)); + From.eq(i).remove(); + } + } + } + } |