Project

General

Profile

« Previous | Next » 

Revision 08ec2d99

Added by Stephen Beaver almost 10 years ago

Completes #5159
All duplicated JS removed to included file

View differences:

src/usr/local/www/firewall_aliases_edit.php
733 733
<script>
734 734
//<![CDATA[
735 735
events.push(function(){
736

  
737
	function setMasks() {
738
		// Find all ipaddress masks and make dynamic based on address family of input
739
		$('span.pfIpMask + select').each(function (idx, select){
740
			var input = $(select).prevAll('input[type=text]');
741

  
742
			input.on('change', function(e){
743
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
744
				if (!isV6)
745
					max = 32;
746

  
747
				if (input.val() == "")
748
					return;
749

  
750
				while (select.options.length > max)
751
					select.remove(0);
752

  
753
				if (select.options.length < max)
754
				{
755
					for (var i=select.options.length; i<=max; i++)
756
						select.options.add(new Option(i, i), 0);
757
				}
758
			});
759

  
760
			// Fire immediately
761
			input.change();
762
		});
763
	}
764

  
765
	// Complicated function to move all help text associated with this input id to the same id
766
	// on the row above. That way if you delete the last row, you don't lose the help
767
	function moveHelpText(id) {
768
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
769
			var fromId = this.id;
770
			var toId = decrStringInt(fromId);
771
			var helpSpan;
772

  
773
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
774

  
775
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
776
				if($(helpSpan).hasClass('help-block')) {
777
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
778
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
779
					else
780
						$('#' + decrStringInt(fromId)).after(helpSpan);
781
				}
782
			}
783
		});
784
	}
785

  
786
	// Increment the number at the end of the string
787
	function bumpStringInt( str )	{
788
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
789

  
790
	  if( data )
791
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
792

  
793
	  return newStr || str;
794
	}
795

  
796
	// Decrement the number at the end of the string
797
	function decrStringInt( str )	{
798
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
799

  
800
	  if( data )
801
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
802

  
803
	  return newStr || str;
804
	}
805

  
806
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
807
	// gaps, but I do :)
808
	function renumber() {
809
		var idx = 0;
810

  
811
		$('.repeatable').each(function() {
812

  
813
			$(this).find('input').each(function() {
814
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
815
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
816
			});
817

  
818
			$(this).find('select').each(function() {
819
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
820
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
821
			});
822

  
823
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
824

  
825
			idx++;
826
		});
827
	}
828

  
829
	function delete_row(row) {
830
		$('#' + row).parent('div').parent('div').remove();
831
		renumber();
832
	}
833

  
834
	function add_row() {
835
		// Find the lst repeatable group
836
		var lastRepeatableGroup = $('.repeatable:last');
837

  
838
		// Clone it
839
		var newGroup = lastRepeatableGroup.clone(true);
840

  
841
		// Increment the suffix number for each input elemnt in the new group
842
		$(newGroup).find('input').each(function() {
843
			$(this).prop("id", bumpStringInt(this.id));
844
			$(this).prop("name", bumpStringInt(this.name));
845
			if(!$(this).is('[id^=delete]'))
846
				$(this).val('');
847
		});
848

  
849
		// Do the same for selectors
850
		$(newGroup).find('select').each(function() {
851
			$(this).prop("id", bumpStringInt(this.id));
852
			$(this).prop("name", bumpStringInt(this.name));
853
			// If this selector lists mask bits, we need it to be reset to all 128 options
854
			// and no items selected, so that automatic v4/v6 selection still works
855
			if($(this).is('[id^=address_subnet]')) {
856
				$(this).empty();
857
				for(idx=128; idx>0; idx--) {
858
					$(this).append($('<option>', {
859
						value: idx,
860
						text: idx
861
					}));
862
				}
863
			}
864
		});
865

  
866
		// And for "for" tags
867
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
868
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
869

  
870
		// Insert the updated/cloned row
871
		$(lastRepeatableGroup).after(newGroup);
872

  
873
		// Delete any help text from the group we have cloned
874
		$(lastRepeatableGroup).find('.help-block').each(function() {
875
			$(this).remove();
876
		});
877

  
878
		setMasks();
879

  
880
		$('[id^=address]').autocomplete({
881
			source: addressarray
882
		});
883
	}
884

  
736
	
885 737
	function typechange() {
886 738
		var tab = $('#type').find('option:selected').val();
887 739
		$("[id^='address_subnet']").prop("disabled", (tab == 'host') || (tab == 'port') || (tab == 'url') || (tab == 'url_ports'));
......
899 751
		$('.repeatable:first').find('label').text(labelstr[tab]);
900 752
	}
901 753

  
902
	// These are action buttons, not submit buttons
903
	$('[id^=addrow]').prop('type','button');
904
	$('[id^=delete]').prop('type','button');
905

  
906 754
	// On load . .
907 755
	typechange();
908 756

  
......
914 762
	});
915 763

  
916 764
	// on click . .
917
	$('[id^=addrow]').click(function() {
918
		add_row();
919
	});
920

  
921
	$('[id^=delete]').click(function(event) {
922
		if($('.repeatable').length > 1) {
923
			moveHelpText(event.target.id);
924
			delete_row(event.target.id);
925
		}
926
		else
927
			alert('<?php echo gettext("You may not delete the last one!")?>');
928
	});
929

  
930 765
	$('#type').on('change', function() {
931 766
		typechange();
932 767
	});
src/usr/local/www/jquery/pfSenseHelpers.js
1
/* ====================================================================
2
 *	Copyright (c)  2004-2015  Electric Sheep Fencing, LLC. All rights reserved.
3
 *	Copyright (c)  2004, 2005 Scott Ullrich
4
 *
5
 *	Redistribution and use in source and binary forms, with or without modification,
6
 *	are permitted provided that the following conditions are met:
7
 *
8
 *	1. Redistributions of source code must retain the above copyright notice,
9
 *		this list of conditions and the following disclaimer.
10
 *
11
 *	2. Redistributions in binary form must reproduce the above copyright
12
 *		notice, this list of conditions and the following disclaimer in
13
 *		the documentation and/or other materials provided with the
14
 *		distribution.
15
 *
16
 *	3. All advertising materials mentioning features or use of this software
17
 *		must display the following acknowledgment:
18
 *		"This product includes software developed by the pfSense Project
19
 *		 for use in the pfSense software distribution. (http://www.pfsense.org/).
20
 *
21
 *	4. The names "pfSense" and "pfSense Project" must not be used to
22
 *		 endorse or promote products derived from this software without
23
 *		 prior written permission. For written permission, please contact
24
 *		 coreteam@pfsense.org.
25
 *
26
 *	5. Products derived from this software may not be called "pfSense"
27
 *		nor may "pfSense" appear in their names without prior written
28
 *		permission of the Electric Sheep Fencing, LLC.
29
 *
30
 *	6. Redistributions of any form whatsoever must retain the following
31
 *		acknowledgment:
32
 *
33
 *	"This product includes software developed by the pfSense Project
34
 *	for use in the pfSense software distribution (http://www.pfsense.org/).
35
 *
36
 *	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
37
 *	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
39
 *	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
40
 *	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41
 *	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
42
 *	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
43
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
44
 *	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
45
 *	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46
 *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
47
 *	OF THE POSSIBILITY OF SUCH DAMAGE.
48
 *
49
 *	====================================================================
50
 *
51
 */
52

  
1 53
// These helper functions are used on many/most UI pages to hide/show/disable/enable form elements where required
2 54

  
3 55
// Hides the <div> in which the specified input element lives so that the input, its label and help text are hidden
......
85 137
		element.parent('div').addClass('hidden');
86 138
	else
87 139
		element.parent('div').removeClass('hidden');
88
}
140
}
141

  
142
// The followinf functions are used by form froups assigned a class of "repeatable" and provide the ability
143
// to add/delete new rows of sequentially numbered elements, their labels and their help text
144
// See firewall_aliases_edit.php for an example
145

  
146
function setMasks() {
147
	// Find all ipaddress masks and make dynamic based on address family of input
148
	$('span.pfIpMask + select').each(function (idx, select){
149
		var input = $(select).prevAll('input[type=text]');
150

  
151
		input.on('change', function(e){
152
			var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
153
			if (!isV6)
154
				max = 32;
155

  
156
			if (input.val() == "")
157
				return;
158

  
159
			while (select.options.length > max)
160
				select.remove(0);
161

  
162
			if (select.options.length < max)
163
			{
164
				for (var i=select.options.length; i<=max; i++)
165
					select.options.add(new Option(i, i), 0);
166
			}
167
		});
168

  
169
		// Fire immediately
170
		input.change();
171
	});
172
}
173

  
174
// Complicated function to move all help text associated with this input id to the same id
175
// on the row above. That way if you delete the last row, you don't lose the help
176
function moveHelpText(id) {
177
	$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
178
		var fromId = this.id;
179
		var toId = decrStringInt(fromId);
180
		var helpSpan;
181

  
182
		if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
183

  
184
			helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
185
			if($(helpSpan).hasClass('help-block')) {
186
				if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
187
					$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
188
				else
189
					$('#' + decrStringInt(fromId)).after(helpSpan);
190
			}
191
		}
192
	});
193
}
194

  
195
// Increment the number at the end of the string
196
function bumpStringInt( str )	{
197
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
198

  
199
  if( data )
200
	newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
201

  
202
  return newStr || str;
203
}
204

  
205
// Decrement the number at the end of the string
206
function decrStringInt( str )	{
207
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
208

  
209
  if( data )
210
	newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
211

  
212
  return newStr || str;
213
}
214

  
215
// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
216
// gaps, but I do :)
217
function renumber() {
218
	var idx = 0;
219

  
220
	$('.repeatable').each(function() {
221

  
222
		$(this).find('input').each(function() {
223
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
224
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
225
		});
226

  
227
		$(this).find('select').each(function() {
228
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
229
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
230
		});
231

  
232
		$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
233

  
234
		idx++;
235
	});
236
}
237

  
238
function delete_row(row) {
239
	$('#' + row).parent('div').parent('div').remove();
240
	renumber();
241
}
242

  
243
function add_row() {
244
	// Find the lst repeatable group
245
	var lastRepeatableGroup = $('.repeatable:last');
246

  
247
	// Clone it
248
	var newGroup = lastRepeatableGroup.clone(true);
249

  
250
	// Increment the suffix number for each input elemnt in the new group
251
	$(newGroup).find('input').each(function() {
252
		$(this).prop("id", bumpStringInt(this.id));
253
		$(this).prop("name", bumpStringInt(this.name));
254
		if(!$(this).is('[id^=delete]'))
255
			$(this).val('');
256
	});
257

  
258
	// Do the same for selectors
259
	$(newGroup).find('select').each(function() {
260
		$(this).prop("id", bumpStringInt(this.id));
261
		$(this).prop("name", bumpStringInt(this.name));
262
		// If this selector lists mask bits, we need it to be reset to all 128 options
263
		// and no items selected, so that automatic v4/v6 selection still works
264
		if($(this).is('[id^=address_subnet]')) {
265
			$(this).empty();
266
			for(idx=128; idx>0; idx--) {
267
				$(this).append($('<option>', {
268
					value: idx,
269
					text: idx
270
				}));
271
			}
272
		}
273
	});
274

  
275
	// And for "for" tags
276
	$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
277
	$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
278

  
279
	// Insert the updated/cloned row
280
	$(lastRepeatableGroup).after(newGroup);
281

  
282
	// Delete any help text from the group we have cloned
283
	$(lastRepeatableGroup).find('.help-block').each(function() {
284
		if((typeof retainhelp) == "undefined")
285
			$(this).remove();
286
	});
287

  
288
	setMasks();
289

  
290
	$('[id^=address]').autocomplete({
291
		source: addressarray
292
	});
293
}
294

  
295
// These are action buttons, not submit buttons
296
$('[id^=addrow]').prop('type','button');
297
$('[id^=delete]').prop('type','button');
298

  
299
// on click . .
300
$('[id^=addrow]').click(function() {
301
	add_row();
302
});
303

  
304
$('[id^=delete]').click(function(event) {
305
	if($('.repeatable').length > 1) {
306
		if((typeof retainhelp) == "undefined")
307
			moveHelpText(event.target.id);
308

  
309
		delete_row(event.target.id);
310
	}
311
	else
312
		alert('You may not delete the last row!');
313
});
src/usr/local/www/pkg_edit.php
1412 1412
//<![CDATA[
1413 1413
	events.push(function(){
1414 1414

  
1415
	function setMasks() {
1416
		// Find all ipaddress masks and make dynamic based on address family of input
1417
		$('span.pfIpMask + select').each(function (idx, select){
1418
			var input = $(select).prevAll('input[type=text]');
1419

  
1420
			input.on('change', function(e){
1421
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
1422
				if (!isV6)
1423
					max = 32;
1424

  
1425
				if (input.val() == "")
1426
					return;
1427

  
1428
				while (select.options.length > max)
1429
					select.remove(0);
1430

  
1431
				if (select.options.length < max)
1432
				{
1433
					for (var i=select.options.length; i<=max; i++)
1434
						select.options.add(new Option(i, i), 0);
1435
				}
1436
			});
1437

  
1438
			// Fire immediately
1439
			input.change();
1440
		});
1441
	}
1442

  
1443
	// Complicated function to move all help text associated with this input id to the same id
1444
	// on the row above. That way if you delete the last row, you don't lose the help
1445
	function moveHelpText(id) {
1446
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
1447
			var fromId = this.id;
1448
			var toId = decrStringInt(fromId);
1449
			var helpSpan;
1450

  
1451
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
1452

  
1453
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
1454
				if($(helpSpan).hasClass('help-block')) {
1455
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
1456
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
1457
					else
1458
						$('#' + decrStringInt(fromId)).after(helpSpan);
1459
				}
1460
			}
1461
		});
1462
	}
1463

  
1464
	// Increment the number at the end of the string
1465
	function bumpStringInt( str )	{
1466
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
1467

  
1468
	  if( data )
1469
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
1470

  
1471
	  return newStr || str;
1472
	}
1473

  
1474
	// Decrement the number at the end of the string
1475
	function decrStringInt( str )	{
1476
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
1477

  
1478
	  if( data )
1479
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
1480

  
1481
	  return newStr || str;
1482
	}
1483

  
1484
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
1485
	// gaps, but I do :)
1486
	function renumber() {
1487
		var idx = 0;
1488

  
1489
		$('.repeatable').each(function() {
1490

  
1491
			$(this).find('input').each(function() {
1492
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
1493
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
1494
			});
1495

  
1496
			$(this).find('select').each(function() {
1497
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
1498
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
1499
			});
1500

  
1501
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
1502

  
1503
			idx++;
1504
		});
1505
	}
1506

  
1507
	function delete_row(row) {
1508
		$('#' + row).parent('div').parent('div').remove();
1509
		renumber();
1510
	}
1511

  
1512
	function add_row() {
1513
		// Find the lst repeatable group
1514
		var lastRepeatableGroup = $('.repeatable:last');
1515

  
1516
		// Clone it
1517
		var newGroup = lastRepeatableGroup.clone(true);
1518

  
1519
		// Increment the suffix number for each input elemnt in the new group
1520
		$(newGroup).find('input').each(function() {
1521
			$(this).prop("id", bumpStringInt(this.id));
1522
			$(this).prop("name", bumpStringInt(this.name));
1523
			if(!$(this).is('[id^=delete]'))
1524
				$(this).val('');
1525
		});
1526

  
1527
		// Do the same for selectors
1528
		$(newGroup).find('select').each(function() {
1529
			$(this).prop("id", bumpStringInt(this.id));
1530
			$(this).prop("name", bumpStringInt(this.name));
1531
			// If this selector lists mask bits, we need it to be reset to all 128 options
1532
			// and no items selected, so that automatic v4/v6 selection still works
1533
			if($(this).is('[id^=address_subnet]')) {
1534
				$(this).empty();
1535
				for(idx=128; idx>0; idx--) {
1536
					$(this).append($('<option>', {
1537
						value: idx,
1538
						text: idx
1539
					}));
1540
				}
1541
			}
1542
		});
1543

  
1544
		// And for "for" tags
1545
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
1546
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
1547

  
1548
		// Insert the updated/cloned row
1549
		$(lastRepeatableGroup).after(newGroup);
1550

  
1551
		// Delete any help text from the group we have cloned
1552
		$(lastRepeatableGroup).find('.help-block').each(function() {
1553
			$(this).remove();
1554
		});
1555

  
1556
		setMasks();
1557

  
1558
		$('[id^=address]').autocomplete({
1559
			source: addressarray
1560
		});
1561
	}
1562

  
1563
	// These are action buttons, not submit buttons
1564
	$('[id^=addrow]').prop('type','button');
1565
	$('[id^=delete]').prop('type','button');
1566

  
1567
	// on click . .
1568
	$('[id^=addrow]').click(function() {
1569
		add_row();
1570
	});
1571

  
1572
	$('[id^=delete]').click(function(event) {
1573
		if($('.repeatable').length > 1) {
1574
			moveHelpText(event.target.id);
1575
			delete_row(event.target.id);
1576
		}
1577
		else
1578
			alert('<?php echo gettext("You may not delete the last one!")?>');
1579
	});
1580

  
1581 1415
	// Hide the advanced section
1582 1416
	var advanced_visible = false;
1583 1417

  
src/usr/local/www/services_dhcp.php
1338 1338
//<![CDATA[
1339 1339
events.push(function(){
1340 1340

  
1341
	// Row add/delete function for class="repeatable" =================================================================
1342

  
1343
	function setMasks() {
1344
		// Find all ipaddress masks and make dynamic based on address family of input
1345
		$('span.pfIpMask + select').each(function (idx, select){
1346
			var input = $(select).prevAll('input[type=text]');
1347

  
1348
			input.on('change', function(e){
1349
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
1350
				if (!isV6)
1351
					max = 32;
1352

  
1353
				if (input.val() == "")
1354
					return;
1355

  
1356
				while (select.options.length > max)
1357
					select.remove(0);
1358

  
1359
				if (select.options.length < max)
1360
				{
1361
					for (var i=select.options.length; i<=max; i++)
1362
						select.options.add(new Option(i, i), 0);
1363
				}
1364
			});
1365

  
1366
			// Fire immediately
1367
			input.change();
1368
		});
1369
	}
1370

  
1371
	// Complicated function to move all help text associated with this input id to the same id
1372
	// on the row above. That way if you delete the last row, you don't lose the help
1373
	function moveHelpText(id) {
1374
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
1375
			var fromId = this.id;
1376
			var toId = decrStringInt(fromId);
1377
			var helpSpan;
1378

  
1379
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
1380

  
1381
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
1382
				if($(helpSpan).hasClass('help-block')) {
1383
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
1384
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
1385
					else
1386
						$('#' + decrStringInt(fromId)).after(helpSpan);
1387
				}
1388
			}
1389
		});
1390
	}
1391

  
1392
	// Increment the number at the end of the string
1393
	function bumpStringInt( str )	{
1394
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
1395

  
1396
	  if( data )
1397
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
1398

  
1399
	  return newStr || str;
1400
	}
1401

  
1402
	// Decrement the number at the end of the string
1403
	function decrStringInt( str )	{
1404
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
1405

  
1406
	  if( data )
1407
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
1408

  
1409
	  return newStr || str;
1410
	}
1411

  
1412
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
1413
	// gaps, but I do :)
1414
	function renumber() {
1415
		var idx = 0;
1416

  
1417
		$('.repeatable').each(function() {
1418

  
1419
			$(this).find('input').each(function() {
1420
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
1421
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
1422
			});
1423

  
1424
			$(this).find('select').each(function() {
1425
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
1426
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
1427
			});
1428

  
1429
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
1430

  
1431
			idx++;
1432
		});
1433
	}
1434

  
1435
	function delete_row(row) {
1436
		$('#' + row).parent('div').parent('div').remove();
1437
		renumber();
1438
	}
1439

  
1440
	function add_row() {
1441
		// Find the lst repeatable group
1442
		var lastRepeatableGroup = $('.repeatable:last');
1443

  
1444
		// Clone it
1445
		var newGroup = lastRepeatableGroup.clone(true);
1446

  
1447
		// Increment the suffix number for each input elemnt in the new group
1448
		$(newGroup).find('input').each(function() {
1449
			$(this).prop("id", bumpStringInt(this.id));
1450
			$(this).prop("name", bumpStringInt(this.name));
1451
			if(!$(this).is('[id^=delete]'))
1452
				$(this).val('');
1453
		});
1454

  
1455
		// Do the same for selectors
1456
		$(newGroup).find('select').each(function() {
1457
			$(this).prop("id", bumpStringInt(this.id));
1458
			$(this).prop("name", bumpStringInt(this.name));
1459
			// If this selector lists mask bits, we need it to be reset to all 128 options
1460
			// and no items selected, so that automatic v4/v6 selection still works
1461
			if($(this).is('[id^=address_subnet]')) {
1462
				$(this).empty();
1463
				for(idx=128; idx>0; idx--) {
1464
					$(this).append($('<option>', {
1465
						value: idx,
1466
						text: idx
1467
					}));
1468
				}
1469
			}
1470
		});
1471

  
1472
		// And for "for" tags
1473
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
1474
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
1475

  
1476
		// Insert the updated/cloned row
1477
		$(lastRepeatableGroup).after(newGroup);
1478

  
1479
		// Delete any help text from the group we have cloned
1480
		$(lastRepeatableGroup).find('.help-block').each(function() {
1481
			$(this).remove();
1482
		});
1483

  
1484
		setMasks();
1485

  
1486
	}
1487

  
1488
	// These are action buttons, not submit buttons
1489
	$('[id^=addrow]').prop('type','button');
1490
	$('[id^=delete]').prop('type','button');
1491

  
1492
	// on click . .
1493
	$('[id^=addrow]').click(function() {
1494
		add_row();
1495
	});
1496

  
1497
	$('[id^=delete]').click(function(event) {
1498
		if($('.repeatable').length > 1) {
1499
			moveHelpText(event.target.id);
1500
			delete_row(event.target.id);
1501
		}
1502
		else
1503
			alert('<?php echo gettext("You may not delete the last one!")?>');
1504
	});
1505

  
1506 1341
	// Show advanced DNS options ======================================================================================
1507 1342
	var showadvdns = false;
1508 1343

  
src/usr/local/www/services_ntpd.php
501 501

  
502 502
?>
503 503

  
504
<script>
505
	// If this variable is declared, any help text will not be deleted when rows are added
506
	// IOW the help text will appear on every row
507
	retainhelp = true;
508
</script>
509

  
504 510
<script>
505 511
//<![CDATA[
506 512
events.push(function(){
507 513

  
508
	function setMasks() {
509
		// Find all ipaddress masks and make dynamic based on address family of input
510
		$('span.pfIpMask + select').each(function (idx, select){
511
			var input = $(select).prevAll('input[type=text]');
512

  
513
			input.on('change', function(e){
514
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
515
				if (!isV6)
516
					max = 32;
517

  
518
				if (input.val() == "")
519
					return;
520

  
521
				while (select.options.length > max)
522
					select.remove(0);
523

  
524
				if (select.options.length < max)
525
				{
526
					for (var i=select.options.length; i<=max; i++)
527
						select.options.add(new Option(i, i), 0);
528
				}
529
			});
530

  
531
			// Fire immediately
532
			input.change();
533
		});
534
	}
535

  
536
	// Complicated function to move all help text associated with this input id to the same id
537
	// on the row above. That way if you delete the last row, you don't lose the help
538
	function moveHelpText(id) {
539
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
540
			var fromId = this.id;
541
			var toId = decrStringInt(fromId);
542
			var helpSpan;
543

  
544
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
545

  
546
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
547
				if($(helpSpan).hasClass('help-block')) {
548
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
549
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
550
					else
551
						$('#' + decrStringInt(fromId)).after(helpSpan);
552
				}
553
			}
554
		});
555
	}
556

  
557
	// Increment the number at the end of the string
558
	function bumpStringInt( str )	{
559
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
560

  
561
	  if( data )
562
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
563

  
564
	  return newStr || str;
565
	}
566

  
567
	// Decrement the number at the end of the string
568
	function decrStringInt( str )	{
569
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
570

  
571
	  if( data )
572
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
573

  
574
	  return newStr || str;
575
	}
576

  
577
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
578
	// gaps, but I do :)
579
	function renumber() {
580
		var idx = 0;
581

  
582
		$('.repeatable').each(function() {
583

  
584
			$(this).find('input').each(function() {
585
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
586
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
587
			});
588

  
589
			$(this).find('select').each(function() {
590
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
591
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
592
			});
593

  
594
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
595

  
596
			idx++;
597
		});
598
	}
599

  
600
	function delete_row(row) {
601
		$('#' + row).parent('div').parent('div').remove();
602
		renumber();
603
	}
604

  
605
	function add_row() {
606
		// Find the lst repeatable group
607
		var lastRepeatableGroup = $('.repeatable:last');
608

  
609
		// Clone it
610
		var newGroup = lastRepeatableGroup.clone(true);
611

  
612
		// Increment the suffix number for each input elemnt in the new group
613
		$(newGroup).find('input').each(function() {
614
			$(this).prop("id", bumpStringInt(this.id));
615
			$(this).prop("name", bumpStringInt(this.name));
616
			if(!$(this).is('[id^=delete]'))
617
				$(this).val('');
618
		});
619

  
620
		// Do the same for selectors
621
		$(newGroup).find('select').each(function() {
622
			$(this).prop("id", bumpStringInt(this.id));
623
			$(this).prop("name", bumpStringInt(this.name));
624
			// If this selector lists mask bits, we need it to be reset to all 128 options
625
			// and no items selected, so that automatic v4/v6 selection still works
626
			if($(this).is('[id^=address_subnet]')) {
627
				$(this).empty();
628
				for(idx=128; idx>0; idx--) {
629
					$(this).append($('<option>', {
630
						value: idx,
631
						text: idx
632
					}));
633
				}
634
			}
635
		});
636

  
637
		// And for "for" tags
638
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
639
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
640

  
641
		// Insert the updated/cloned row
642
		$(lastRepeatableGroup).after(newGroup);
643
/*
644
		// Delete any help text from the group we have cloned
645
		$(lastRepeatableGroup).find('.help-block').each(function() {
646
			$(this).remove();
647
		});
648
*/
649
		setMasks();
650

  
651
		$('[id^=address]').autocomplete({
652
			source: addressarray
653
		});
654
	}
655

  
656
	// These are action buttons, not submit buttons
657
	$('[id^=addrow]').prop('type','button');
658
	$('[id^=delete]').prop('type','button');
659

  
660
	// on click . .
661
	$('[id^=addrow]').click(function() {
662
		add_row();
663
	});
664

  
665
	$('[id^=delete]').click(function(event) {
666
		if($('.repeatable').length > 1) {
667
	//		moveHelpText(event.target.id);
668
			delete_row(event.target.id);
669
		}
670
		else
671
			alert('<?php echo gettext("You may not delete the last one!")?>');
672
	});
673

  
674 514
	// Make the ‘clear’ button a plain button, not a submit button
675 515
	$('#btnadvstats').prop('type','button');
676 516

  
src/usr/local/www/services_router_advertisements.php
372 372

  
373 373
$section = new Form_Section('DNS Configuration');
374 374

  
375
for($idx=1; $idx=<4; $idx++) {
375
for($idx=1; $idx<=4; $idx++) {
376 376
	$section->addInput(new Form_IpAddress(
377 377
		'radns' . $idx,
378 378
		'Server ' . $idx,
......
410 410
//<![CDATA[
411 411
events.push(function(){
412 412

  
413
	function setMasks() {
414
		// Find all ipaddress masks and make dynamic based on address family of input
415
		$('span.pfIpMask + select').each(function (idx, select){
416
			var input = $(select).prevAll('input[type=text]');
417

  
418
			input.on('change', function(e){
419
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
420
				if (!isV6)
421
					max = 32;
422

  
423
				if (input.val() == "")
424
					return;
425

  
426
				while (select.options.length > max)
427
					select.remove(0);
428

  
429
				if (select.options.length < max)
430
				{
431
					for (var i=select.options.length; i<=max; i++)
432
						select.options.add(new Option(i, i), 0);
433
				}
434
			});
435

  
436
			// Fire immediately
437
			input.change();
438
		});
439
	}
440

  
441
	// Complicated function to move all help text associated with this input id to the same id
442
	// on the row above. That way if you delete the last row, you don't lose the help
443
	function moveHelpText(id) {
444
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
445
			var fromId = this.id;
446
			var toId = decrStringInt(fromId);
447
			var helpSpan;
448

  
449
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
450

  
451
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
452
				if($(helpSpan).hasClass('help-block')) {
453
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
454
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
455
					else
456
						$('#' + decrStringInt(fromId)).after(helpSpan);
457
				}
458
			}
459
		});
460
	}
461

  
462
	// Increment the number at the end of the string
463
	function bumpStringInt( str )	{
464
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
465

  
466
	  if( data )
467
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
468

  
469
	  return newStr || str;
470
	}
471

  
472
	// Decrement the number at the end of the string
473
	function decrStringInt( str )	{
474
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
475

  
476
	  if( data )
477
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
478

  
479
	  return newStr || str;
480
	}
481

  
482
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
483
	// gaps, but I do :)
484
	function renumber() {
485
		var idx = 0;
486

  
487
		$('.repeatable').each(function() {
488

  
489
			$(this).find('input').each(function() {
490
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
491
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
492
			});
493

  
494
			$(this).find('select').each(function() {
495
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
496
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
497
			});
498

  
499
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
500

  
501
			idx++;
502
		});
503
	}
504

  
505
	function delete_row(row) {
506
		$('#' + row).parent('div').parent('div').remove();
507
		renumber();
508
	}
509

  
510
	function add_row() {
511
		// Find the lst repeatable group
512
		var lastRepeatableGroup = $('.repeatable:last');
513

  
514
		// Clone it
515
		var newGroup = lastRepeatableGroup.clone(true);
516

  
517
		// Increment the suffix number for each input elemnt in the new group
518
		$(newGroup).find('input').each(function() {
519
			$(this).prop("id", bumpStringInt(this.id));
520
			$(this).prop("name", bumpStringInt(this.name));
521
			if(!$(this).is('[id^=delete]'))
522
				$(this).val('');
523
		});
524

  
525
		// Do the same for selectors
526
		$(newGroup).find('select').each(function() {
527
			$(this).prop("id", bumpStringInt(this.id));
528
			$(this).prop("name", bumpStringInt(this.name));
529
			// If this selector lists mask bits, we need it to be reset to all 128 options
530
			// and no items selected, so that automatic v4/v6 selection still works
531
			if($(this).is('[id^=address_subnet]')) {
532
				$(this).empty();
533
				for(idx=128; idx>0; idx--) {
534
					$(this).append($('<option>', {
535
						value: idx,
536
						text: idx
537
					}));
538
				}
539
			}
540
		});
541

  
542
		// And for "for" tags
543
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
544
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
545

  
546
		// Insert the updated/cloned row
547
		$(lastRepeatableGroup).after(newGroup);
548

  
549
		// Delete any help text from the group we have cloned
550
		$(lastRepeatableGroup).find('.help-block').each(function() {
551
			$(this).remove();
552
		});
553

  
554
		setMasks();
555
	}
556

  
557
	// These are action buttons, not submit buttons
558
	$('[id^=addrow]').prop('type','button');
559
	$('[id^=delete]').prop('type','button');
560

  
561
	// on click . .
562
	$('[id^=addrow]').click(function() {
563
		add_row();
564
	});
565

  
566
	$('[id^=delete]').click(function(event) {
567
		if($('.repeatable').length > 1) {
568
			moveHelpText(event.target.id);
569
			delete_row(event.target.id);
570
		}
571
		else
572
			alert('<?php echo gettext("You may not delete the last one!")?>');
573
	});
574

  
575 413
	// --------- Autocomplete -----------------------------------------------------------------------------------------
576 414
	var addressarray = <?= json_encode(get_alias_list(array("host", "network", "openvpn", "urltable"))) ?>;
577 415

  
src/usr/local/www/services_unbound_acls.php
269 269
		$counter++;
270 270
	}
271 271

  
272
	$section->addInput(new Form_Button(
272
	$form->addGlobal(new Form_Button(
273 273
		'addrow',
274 274
		'Add network'
275 275
	))->removeClass('btn-primary')->addClass('btn-success');
......
328 328
<?php
329 329
}
330 330

  
331
?>
332
<script>
333
//<![CDATA[
334
// Most of this needs to live in a common include file. It will be moved before production release.
335
events.push(function(){
336

  
337
	function setMasks() {
338
		// Find all ipaddress masks and make dynamic based on address family of input
339
		$('span.pfIpMask + select').each(function (idx, select){
340
			var input = $(select).prevAll('input[type=text]');
341

  
342
			input.on('change', function(e){
343
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
344
				if (!isV6)
345
					max = 32;
346

  
347
				if (input.val() == "")
348
					return;
349

  
350
				while (select.options.length > max)
351
					select.remove(0);
352

  
353
				if (select.options.length < max)
354
				{
355
					for (var i=select.options.length; i<=max; i++)
356
						select.options.add(new Option(i, i), 0);
357
				}
358
			});
359

  
360
			// Fire immediately
361
			input.change();
362
		});
363
	}
364

  
365
	// Complicated function to move all help text associated with this input id to the same id
366
	// on the row above. That way if you delete the last row, you don't lose the help
367
	function moveHelpText(id) {
368
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
369
			var fromId = this.id;
370
			var toId = decrStringInt(fromId);
371
			var helpSpan;
372

  
373
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
374

  
375
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
376
				if($(helpSpan).hasClass('help-block')) {
377
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
378
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
379
					else
380
						$('#' + decrStringInt(fromId)).after(helpSpan);
381
				}
382
			}
383
		});
384
	}
385

  
386
	// Increment the number at the end of the string
387
	function bumpStringInt( str )	{
388
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
389

  
390
	  if( data )
391
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
392

  
393
	  return newStr || str;
394
	}
395

  
396
	// Decrement the number at the end of the string
397
	function decrStringInt( str )	{
398
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
399

  
400
	  if( data )
401
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
402

  
403
	  return newStr || str;
404
	}
405

  
406
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
407
	// gaps, but I do :)
408
	function renumber() {
409
		var idx = 0;
410

  
411
		$('.repeatable').each(function() {
412

  
413
			$(this).find('input').each(function() {
414
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
415
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
416
			});
417

  
418
			$(this).find('select').each(function() {
419
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
420
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
421
			});
422

  
423
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
424

  
425
			idx++;
426
		});
427
	}
428

  
429

  
430
	function delete_row(row) {
431
		$('#' + row).parent('div').parent('div').remove();
432
		renumber();
433
	}
434

  
435
	function add_row() {
436
		// Find the lst repeatable group
437
		var lastRepeatableGroup = $('.repeatable:last');
438

  
439
		// Clone it
440
		var newGroup = lastRepeatableGroup.clone(true);
441

  
442
		// Increment the suffix number for each input elemnt in the new group
443
		$(newGroup).find('input').each(function() {
444
			$(this).prop("id", bumpStringInt(this.id));
445
			$(this).prop("name", bumpStringInt(this.name));
446
			if(!$(this).is('[id^=delete]'))
447
				$(this).val('');
448
		});
449

  
450
		// Do the same for selectors
451
		$(newGroup).find('select').each(function() {
452
			$(this).prop("id", bumpStringInt(this.id));
453
			$(this).prop("name", bumpStringInt(this.name));
454
			// If this selector lists mask bits, we need it to be reset to all 128 options
455
			// and no items selected, so that automatic v4/v6 selection still works
456
			if($(this).is('[id^=mask]')) {
457
				$(this).empty();
458
				for(idx=128; idx>0; idx--) {
459
					$(this).append($('<option>', {
460
						value: idx,
461
						text: idx
462
					}));
463
				}
464
			}
465
		});
466

  
467
		// And for "for" tags
468
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
469
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
470

  
471
		// Insert the updated/cloned row
472
		$(lastRepeatableGroup).after(newGroup);
473

  
474
		// Delete any help text from the group we have cloned
475
		$(lastRepeatableGroup).find('.help-block').each(function() {
476
			$(this).remove();
477
		});
478

  
479
		setMasks();
480
	}
481

  
482
	// These are action buttons, not submit buttons
483
	$('[id^=addrow]').prop('type','button');
484
	$('[id^=delete]').prop('type','button');
485

  
486
	// on click . .
487
	$('[id^=addrow]').click(function() {
488
		add_row();
489
	});
490

  
491
	$('[id^=delete]').click(function(event) {
492
		if($('.repeatable').length > 1) {
493
			moveHelpText(event.target.id);
494
			delete_row(event.target.id);
495
		}
496
		else
497
			alert('<?php echo gettext("You may not delete the last one!")?>');
498
	});
499
});
500
//]]>
501
</script>
502

  
503
<?php
504
include("foot.inc");
331
include("foot.inc");
src/usr/local/www/system_certmanager.php
1058 1058
//<![CDATA[
1059 1059
events.push(function(){
1060 1060

  
1061
	function setMasks() {
1062
		// Find all ipaddress masks and make dynamic based on address family of input
1063
		$('span.pfIpMask + select').each(function (idx, select){
1064
			var input = $(select).prevAll('input[type=text]');
1065

  
1066
			input.on('change', function(e){
1067
				var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
1068
				if (!isV6)
1069
					max = 32;
1070

  
1071
				if (input.val() == "")
1072
					return;
1073

  
1074
				while (select.options.length > max)
1075
					select.remove(0);
1076

  
1077
				if (select.options.length < max)
1078
				{
1079
					for (var i=select.options.length; i<=max; i++)
1080
						select.options.add(new Option(i, i), 0);
1081
				}
1082
			});
1083

  
1084
			// Fire immediately
1085
			input.change();
1086
		});
1087
	}
1088

  
1089
	// Complicated function to move all help text associated with this input id to the same id
1090
	// on the row above. That way if you delete the last row, you don't lose the help
1091
	function moveHelpText(id) {
1092
		$('#' + id).parent('div').parent('div').find('input').each(function() {	 // For each <span></span>
1093
			var fromId = this.id;
1094
			var toId = decrStringInt(fromId);
1095
			var helpSpan;
1096

  
1097
			if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
1098

  
1099
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
1100
				if($(helpSpan).hasClass('help-block')) {
1101
					if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group'))
1102
						$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
1103
					else
1104
						$('#' + decrStringInt(fromId)).after(helpSpan);
1105
				}
1106
			}
1107
		});
1108
	}
1109

  
1110
	// Increment the number at the end of the string
1111
	function bumpStringInt( str )	{
1112
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
1113

  
1114
	  if( data )
1115
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
1116

  
1117
	  return newStr || str;
1118
	}
1119

  
1120
	// Decrement the number at the end of the string
1121
	function decrStringInt( str )	{
1122
	  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
1123

  
1124
	  if( data )
1125
		newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
1126

  
1127
	  return newStr || str;
1128
	}
1129

  
1130
	// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
1131
	// gaps, but I do :)
1132
	function renumber() {
1133
		var idx = 0;
1134

  
1135
		$('.repeatable').each(function() {
1136

  
1137
			$(this).find('input').each(function() {
1138
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
1139
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
1140
			});
1141

  
1142
			$(this).find('select').each(function() {
1143
				$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
1144
				$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
1145
			});
1146

  
1147
			$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
1148

  
1149
			idx++;
1150
		});
1151
	}
1152

  
1153
	function delete_row(row) {
1154
		$('#' + row).parent('div').parent('div').remove();
1155
		renumber();
1156
	}
1157

  
1158
	function add_row() {
1159
		// Find the lst repeatable group
1160
		var lastRepeatableGroup = $('.repeatable:last');
1161

  
1162
		// Clone it
1163
		var newGroup = lastRepeatableGroup.clone(true);
1164

  
1165
		// Increment the suffix number for each input elemnt in the new group
1166
		$(newGroup).find('input').each(function() {
1167
			$(this).prop("id", bumpStringInt(this.id));
1168
			$(this).prop("name", bumpStringInt(this.name));
1169
			if(!$(this).is('[id^=delete]'))
1170
				$(this).val('');
1171
		});
1172

  
1173
		// Do the same for selectors
1174
		$(newGroup).find('select').each(function() {
1175
			$(this).prop("id", bumpStringInt(this.id));
1176
			$(this).prop("name", bumpStringInt(this.name));
1177
			// If this selector lists mask bits, we need it to be reset to all 128 options
1178
			// and no items selected, so that automatic v4/v6 selection still works
1179
			if($(this).is('[id^=address_subnet]')) {
1180
				$(this).empty();
1181
				for(idx=128; idx>0; idx--) {
1182
					$(this).append($('<option>', {
1183
						value: idx,
1184
						text: idx
1185
					}));
1186
				}
1187
			}
1188
		});
1189

  
1190
		// And for "for" tags
1191
		$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
1192
		$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
1193

  
1194
		// Insert the updated/cloned row
1195
		$(lastRepeatableGroup).after(newGroup);
1196

  
1197
		// Delete any help text from the group we have cloned
1198
		$(lastRepeatableGroup).find('.help-block').each(function() {
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff