Project

General

Profile

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

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

    
54
// Hides the <div> in which the specified input element lives so that the input, its label and help text are hidden
55
function hideInput(id, hide) {
56
	if(hide)
57
		$('#' + id).parent().parent('div').addClass('hidden');
58
	else
59
		$('#' + id).parent().parent('div').removeClass('hidden');
60
}
61

    
62
// Hides the <div> in which the specified group input element lives so that the input,
63
// its label and help text are hidden
64
function hideGroupInput(id, hide) {
65
	if(hide)
66
		$('#' + id).parent('div').addClass('hidden');
67
	else
68
		$('#' + id).parent('div').removeClass('hidden');
69
}
70

    
71
// Hides the <div> in which the specified checkbox lives so that the checkbox, its label and help text are hidden
72
function hideCheckbox(id, hide) {
73
	if(hide)
74
		$('#' + id).parent().parent().parent('div').addClass('hidden');
75
	else
76
		$('#' + id).parent().parent().parent('div').removeClass('hidden');
77
}
78

    
79
// Disables the specified input element
80
function disableInput(id, disable) {
81
	$('#' + id).prop("disabled", disable);
82
}
83

    
84
// Hides all elements of the specified class. This will usually be a section
85
function hideClass(s_class, hide) {
86
	if(hide)
87
		$('.' + s_class).hide();
88
	else
89
		$('.' + s_class).show();
90
}
91

    
92
// Hides all elements of the specified class assigned to a group. This will usually be a group
93
function hideGroupClass(s_class, hide) {
94
	if(hide)
95
		$('.' + s_class).parent().parent().parent().hide();
96
	else
97
		$('.' + s_class).parent().parent().parent().show();
98
}
99

    
100
function hideSelect(id, hide) {
101
	if(hide)
102
		$('#' + id).parent('div').parent('div').addClass('hidden');
103
	else
104
		$('#' + id).parent('div').parent('div').removeClass('hidden');
105
}
106

    
107
function hideMultiCheckbox(id, hide) {
108
	if(hide)
109
		$("[name=" + id + "]").parent().addClass('hidden');
110
	else
111
		$("[name=" + id + "]").parent().removeClass('hidden');
112
}
113

    
114
// Hides the <div> in which the specified IP address element lives so that the input, its label and help text are hidden
115
function hideIpAddress(id, hide) {
116
	if(hide)
117
		$('#' + id).parent().parent().parent('div').addClass('hidden');
118
	else
119
		$('#' + id).parent().parent().parent('div').removeClass('hidden');
120
}
121

    
122
// Hides all elements of the specified class belonging to a multiselect.
123
function hideMultiClass(s_class, hide) {
124
	if(hide)
125
		$('.' + s_class).parent().parent().hide();
126
	else
127
		$('.' + s_class).parent().parent().show();
128
}
129

    
130
// Hides div whose label contains the specified text. (Good for StaticText)
131
function hideLabel(text, hide) {
132

    
133
	var element = $('label:contains(' + text + ')');
134

    
135
	if(hide)
136
		element.parent('div').addClass('hidden');
137
	else
138
		element.parent('div').removeClass('hidden');
139
}
140

    
141
// Toggle table row checkboxes and background colors on the pages that use sortable tables:
142
//	/usr/local/www/firewall_nat.php
143
//	/usr/local/www/firewall_nat_1to1.php
144
//	/usr/local/www/firewall_nat_out.php
145
//	/usr/local/www/firewall_rules.php
146
//	/usr/local/www/vpn_ipsec.php
147
// Striping of the tables is handled here, NOT with the Bootstrap table-striped class because it would
148
// get confused when rows are sorted or deleted.
149

    
150
function fr_toggle(id, prefix) {
151
	if (!prefix)
152
		prefix = 'fr';
153

    
154
	var checkbox = document.getElementById(prefix + 'c' + id);
155
	checkbox.checked = !checkbox.checked;
156
	fr_bgcolor(id, prefix);
157
}
158

    
159
// Change background color of selected row based on state of checkbox
160
function fr_bgcolor(id, prefix) {
161
	if (!prefix)
162
		prefix = 'fr';
163

    
164
	var row = $('#' + prefix + id);
165

    
166
	if ($('#' + prefix + 'c' + id).prop('checked') ) {
167
		row.addClass('active');
168
	} else {
169
		row.removeClass('active');
170
	}
171
}
172

    
173
// The following functions are used by Form_Groups assigned a class of "repeatable" and provide the ability
174
// to add/delete rows of sequentially numbered elements, their labels and their help text
175
// See firewall_aliases_edit.php for an example
176

    
177
// NOTE: retainhelp is a global var that defined prevents any help text from being deleted as lines are inserted.
178
// IOW it causes every row to have help text, not just the last row
179

    
180
function setMasks() {
181
	// Find all ipaddress masks and make dynamic based on address family of input
182
	$('span.pfIpMask + select').each(function (idx, select){
183
		var input = $(select).prevAll('input[type=text]');
184

    
185
		input.on('change', function(e){
186
			var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
187
			if (!isV6)
188
				max = 32;
189

    
190
			if (input.val() == "")
191
				return;
192

    
193
			while (select.options.length > max)
194
				select.remove(0);
195

    
196
			if (select.options.length < max)
197
			{
198
				for (var i=select.options.length; i<=max; i++)
199
					select.options.add(new Option(i, i), 0);
200
			}
201
		});
202

    
203
		// Fire immediately
204
		input.change();
205
	});
206
}
207

    
208
// Complicated function to move all help text associated with this input id to the same id
209
// on the row above. That way if you delete the last row, you don't lose the help
210
function moveHelpText(id) {
211
	$('#' + id).parent('div').parent('div').find('input, select, checkbox').each(function() {	 // For each <span></span>
212
		var fromId = this.id;
213
		var toId = decrStringInt(fromId);
214
		var helpSpan;
215

    
216
		if(!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
217
			if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) {
218
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
219
			} else {
220
				helpSpan = $('#' + fromId).parent('div').find('span:last').clone();
221
			}
222
			if($(helpSpan).hasClass('help-block')) {
223
				if($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) {
224
					$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
225
				}
226
				else {
227
					$('#' + decrStringInt(fromId)).after(helpSpan);
228
				}
229
			}
230
		}
231
	});
232
}
233

    
234
// Increment the number at the end of the string
235
function bumpStringInt( str )	{
236
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
237

    
238
  if( data )
239
	newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
240

    
241
  return newStr || str;
242
}
243

    
244
// Decrement the number at the end of the string
245
function decrStringInt( str )	{
246
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
247

    
248
  if( data )
249
	newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
250

    
251
  return newStr || str;
252
}
253

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

    
259
	$('.repeatable').each(function() {
260

    
261
		$(this).find('input').each(function() {
262
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
263
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
264
		});
265

    
266
		$(this).find('select').each(function() {
267
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
268
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
269
		});
270

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

    
273
		idx++;
274
	});
275
}
276

    
277
function delete_row(rowDelBtn) {
278
	var rowLabel;
279

    
280
	// If we are deleting row zero, we need to save/restore the label
281
	if (rowDelBtn == "deleterow0") {
282
		rowLabel = $('#' + rowDelBtn).parent('div').parent('div').find('label').text();
283
	}
284

    
285
	$('#' + rowDelBtn).parent('div').parent('div').remove();
286
	renumber();
287
	checkLastRow();
288

    
289
	if (rowDelBtn == "deleterow0") {
290
		$('#' + rowDelBtn).parent('div').parent('div').find('label').text(rowLabel);
291
	}
292
}
293

    
294
function checkLastRow() {
295
	if($('.repeatable').length <= 1) {
296
		$('#deleterow0').hide();
297
	} else {
298
		$('[id^=deleterow]').show();
299
	}
300
}
301

    
302
function add_row() {
303
	// Find the last repeatable group
304
	var lastRepeatableGroup = $('.repeatable:last');
305

    
306
	// Clone it
307
	var newGroup = lastRepeatableGroup.clone();
308
	// Increment the suffix number for each input element in the new group
309
	$(newGroup).find('input').each(function() {
310
		$(this).prop("id", bumpStringInt(this.id));
311
		$(this).prop("name", bumpStringInt(this.name));
312
		if(!$(this).is('[id^=delete]'))
313
			$(this).val('');
314
	});
315

    
316
	// Increment the suffix number for the deleterow button element in the new group
317
	$(newGroup).find('[id^=deleterow]').each(function() {
318
		$(this).prop("id", bumpStringInt(this.id));
319
		$(this).prop("name", bumpStringInt(this.name));
320
	});
321

    
322
	// Do the same for selectors
323
	$(newGroup).find('select').each(function() {
324
		$(this).prop("id", bumpStringInt(this.id));
325
		$(this).prop("name", bumpStringInt(this.name));
326
		// If this selector lists mask bits, we need it to be reset to all 128 options
327
		// and no items selected, so that automatic v4/v6 selection still works
328
		if($(this).is('[id^=address_subnet]')) {
329
			$(this).empty();
330
			for(idx=128; idx>0; idx--) {
331
				$(this).append($('<option>', {
332
					value: idx,
333
					text: idx
334
				}));
335
			}
336
		}
337
	});
338

    
339
	// And for "for" tags
340
//	$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
341

    
342
	$(newGroup).find('label').text(""); // Clear the label. We only want it on the very first row
343

    
344
	// Insert the updated/cloned row
345
	$(lastRepeatableGroup).after(newGroup);
346

    
347
	// Delete any help text from the group we have cloned
348
	$(lastRepeatableGroup).find('.help-block').each(function() {
349
		if((typeof retainhelp) == "undefined")
350
			$(this).remove();
351
	});
352

    
353
	setMasks();
354

    
355
	checkLastRow();
356

    
357
	// Autocomplete
358
	if ( typeof addressarray !== 'undefined') {
359
		$('[id^=address]').each(function() {
360
			if(this.id.substring(0, 8) != "address_") {
361
				$(this).autocomplete({
362
					source: addressarray
363
				});
364
			}
365
		});
366
	}
367

    
368
	// Now that we are no longer cloning the event handlers, we need to remove and re-add after a new row
369
	// has been added to the table
370
	$('[id^=delete]').unbind();
371
	$('[id^=delete]').click(function(event) {
372
		if($('.repeatable').length > 1) {
373
			if((typeof retainhelp) == "undefined")
374
				moveHelpText(event.target.id);
375

    
376
			delete_row(event.target.id);
377
		}
378
		else
379
			alert('The last row may not be deleted.');
380
	});
381

    
382
}
383

    
384
// These are action buttons, not submit buttons
385
$('[id^=addrow]').prop('type','button');
386
$('[id^=delete]').prop('type','button');
387

    
388
// on click . .
389
$('[id^=addrow]').click(function() {
390
	add_row();
391
});
392

    
393
$('[id^=delete]').click(function(event) {
394
	if($('.repeatable').length > 1) {
395
		if((typeof retainhelp) == "undefined")
396
			moveHelpText(event.target.id);
397

    
398
		delete_row(event.target.id);
399
	}
400
	else
401
		alert('The last row may not be deleted.');
402
});
403

    
404
// "More information" handlers --------------------------------------------------------------------
405

    
406
// If there is an infoblock, automatically add an info icon that toggles its display
407

    
408
var sfx = 0;
409

    
410
$('.infoblock').each(function() {
411
	// If the block has the class "blockopen" it is initially open
412
	if (! $(this).hasClass("blockopen")) {
413
		$(this).hide();
414
	} else {
415
		$(this).removeClass("blockopen");
416
	}
417

    
418
	// Add the "i" icon before the infoblock, incrementing the icon id for each block (in case there are multiple infoblocks on a page)
419
	$(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>');
420
	$(this).removeClass("infoblock");
421
	$(this).addClass("infoblock" + sfx.toString());
422
	sfx++;
423
});
424

    
425
// Show the help on clicking the info icon
426
$('[id^="showinfo"]').click(function() {
427
	var id = $(this).attr("id");
428

    
429
	$('.' + "infoblock" + id.substr(8)).toggle();
430
	document.getSelection().removeAllRanges();		// Ensure the text is un-selected (Chrome browser quirk)
431
});
432
// ------------------------------------------------------------------------------------------------
433

    
434
// Put a dummy row into any empty table to keep IE happy
435
$('tbody').each(function(){
436
	$(this).html($.trim($(this).html()))
437
});
438

    
439
$('tbody:empty').html("<tr><td></td></tr>");
440

    
441
// Hide configuration button for panels without configuration
442
$('.container .panel-heading a.config').each(function (idx, el){
443
	var config = $(el).parents('.panel').children('.panel-footer');
444
	if (config.length == 1)
445
		$(el).removeClass('hidden');
446
});
447

    
448
// Initial state & toggle icons of collapsed panel
449
$('.container .panel-heading a[data-toggle="collapse"]').each(function (idx, el){
450
	var body = $(el).parents('.panel').children('.panel-body')
451
	var isOpen = body.hasClass('in');
452

    
453
	$(el).children('i').toggleClass('fa-plus-circle', !isOpen);
454
	$(el).children('i').toggleClass('fa-minus-circle', isOpen);
455

    
456
	body.on('shown.bs.collapse', function(){
457
		$(el).children('i').toggleClass('fa-minus-circle', true);
458
		$(el).children('i').toggleClass('fa-plus-circle', false);
459
	});
460

    
461
	body.on('hidden.bs.collapse', function(){
462
		$(el).children('i').toggleClass('fa-minus-circle', false);
463
		$(el).children('i').toggleClass('fa-plus-circle', true);
464
	});
465
});
466

    
467
	// Separator bar stuff ------------------------------------------------------------------------
468

    
469
	// Globals
470
	gColor = 'bg-info';
471
	newSeperator = false;
472
	saving = false;
473
	dirty = false;
474

    
475
	$("#addsep").prop('type' ,'button');
476

    
477
	$("#addsep").click(function() {
478
		if (newSeperator) {
479
			return(false);
480
		}
481

    
482
		gColor = 'bg-info';
483
		// Inset a temporary bar in which the user can enter some optional text
484
		sepcols = $( "#ruletable tr th" ).length - 2;
485

    
486
		$('#ruletable > tbody:last').append('<tr>' +
487
			'<td class="' + gColor + '" colspan="' + sepcols + '"><input id="newsep" placeholder="' + svbtnplaceholder + '" class="col-md-12" type="text" /></td>' +
488
			'<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>' +
489
			'<button class="btn btn-info btn-sm" id="btncncsep"><i class="fa fa-undo icon-embed-btn"></i>' + cncltxt + '</button>' +
490
			'&nbsp;&nbsp;&nbsp;&nbsp;' +
491
			'&nbsp;&nbsp;<a id="sepclrblue" value="bg-info"><i class="fa fa-circle text-info icon-pointer"></i></a>' +
492
			'&nbsp;&nbsp;<a id="sepclrred" value="bg-danger"><i class="fa fa-circle text-danger icon-pointer"></i></a>' +
493
			'&nbsp;&nbsp;<a id="sepclrgreen" value="bg-success"><i class="fa fa-circle text-success icon-pointer"></i></a>' +
494
			'&nbsp;&nbsp;<a id="sepclrorange" value="bg-warning"><i class="fa fa-circle text-warning icon-pointer"></i></button>' +
495
			'</td></tr>');
496

    
497
		$('#newsep').focus();
498
		newSeperator = true;
499

    
500
		$("#btnnewsep").prop('type' ,'button');
501

    
502
		// Watch escape and enter keys
503
		$('#newsep').keyup(function(e) {
504
			if(e.which == 27) {
505
				$('#btncncsep').trigger('click');
506
			}
507
		});
508

    
509
		$('#newsep').keypress(function(e) {
510
			if(e.which == 13) {
511
				$('#btnnewsep').trigger('click');
512
			}
513
		});
514

    
515
		handle_colors();
516

    
517
		// Replace the temporary separator bar with the final version containing the
518
		// user's text and a delete icon
519
		$("#btnnewsep").click(function() {
520
			var septext = escapeHtml($('#newsep').val());
521
			sepcols = $( "#ruletable tr th" ).length - 1;
522

    
523
			$(this).parents('tr').replaceWith('<tr class="ui-sortable-handle separator">' +
524
				'<td class="' + gColor + '" colspan="' + sepcols + '">' + '<span class="' + gColor + '">' + septext + '</span></td>' +
525
				'<td class="' + gColor + '"><a href="#"><i class="fa fa-trash sepdel"></i></a>' +
526
				'</td></tr>');
527

    
528
			$('#order-store').removeAttr('disabled');
529
			newSeperator = false;
530
			dirty = true;
531
		});
532

    
533
		// Cancel button
534
		$('#btncncsep').click(function(e) {
535
			e.preventDefault();
536
			$(this).parents('tr').remove();
537
			newSeperator = false;
538
		});
539
	});
540

    
541
	// Delete a separator row
542
	$(function(){
543
		$('table').on('click','tr a .sepdel',function(e){
544
			e.preventDefault();
545
			$(this).parents('tr').remove();
546
			$('#order-store').removeAttr('disabled');
547
			dirty = true;
548
		});
549
	});
550

    
551
	// Compose an inout array containing the row #, color and text for each separator
552
	function save_separators() {
553
		var row = 0;
554
		var sepinput;
555
		var sepnum = 0;
556

    
557
		$('#ruletable > tbody > tr').each(function() {
558
			if ($(this).hasClass('separator')) {
559
				seprow = $(this).next('tr').attr("id");
560
				if (seprow == undefined) {
561
					seprow = "fr" + row;
562
				}
563

    
564
				sepinput = '<input type="hidden" name="separator[' + sepnum + '][row]" value="' + seprow + '"></input>';
565
				$('form').append(sepinput);
566
				sepinput = '<input type="hidden" name="separator[' + sepnum + '][text]" value="' + escapeHtml($(this).find('td').text()) + '"></input>';
567
				$('form').append(sepinput);
568
				sepinput = '<input type="hidden" name="separator[' + sepnum + '][color]" value="' + $(this).find('td').prop('class') + '"></input>';
569
				$('form').append(sepinput);
570
				sepinput = '<input type="hidden" name="separator[' + sepnum + '][if]" value="' + iface + '"></input>';
571
				$('form').append(sepinput);
572
				sepnum++;
573
			} else {
574
				if ($(this).parent('tbody').hasClass('user-entries')) {
575
					row++;
576
				}
577
			}
578
		});
579
	}
580

    
581
	function reindex_rules(section) {
582
		var row = 0;
583

    
584
		section.find('tr').each(function() {
585
			if(this.id) {
586
				$(this).attr("id", "fr" + row);
587
				$(this).attr("onclick", "fr_toggle(" + row + ")")
588
				$(this).find('input:checkbox:first').each(function() {
589
					$(this).attr("id", "frc" + row);
590
					$(this).attr("onclick", "fr_toggle(" + row + ")");
591
				});
592

    
593
				row++;
594
			}
595
		});
596
	}
597

    
598
	function handle_colors() {
599
		$('[id^=sepclr]').prop("type", "button");
600

    
601
		$('[id^=sepclr]').click(function () {
602
			var color =	 $(this).attr('value');
603
			// Clear all the color classes
604
			$(this).parent('td').prop('class', '');
605
			$(this).parent('td').prev('td').prop('class', '');
606
			// Install our new color class
607
			$(this).parent('td').addClass(color);
608
			$(this).parent('td').prev('td').addClass(color);
609
			// Set the global color
610
			gColor = color;
611
		});
612
	}
613

    
614
	//JS equivalent to PHP htmlspecialchars()
615
	function escapeHtml(text) {
616
		var map = {
617
			'&': '&amp;',
618
			'<': '&lt;',
619
			'>': '&gt;',
620
			'"': '&quot;',
621
			"'": '&#039;'
622
		};
623

    
624
		return text.replace(/[&<>"']/g, function(m) { return map[m]; });
625
	}
626
	// --------------------------------------------------------------------------------------------
627

    
628
	// Select every option in the specified multiselect
629
	function AllServers(id, selectAll) {
630
	   for (i = 0; i < id.length; i++)	   {
631
		   id.eq(i).prop('selected', selectAll);
632
	   }
633
	}
634

    
635
	// Move all selected options from one multiselect to another
636
	function moveOptions(From, To)	{
637
		var len = From.length;
638
		var option;
639

    
640
		if (len > 0) {
641
			for (i=0; i<len; i++) {
642
				if (From.eq(i).is(':selected')) {
643
					option = From.eq(i).val();
644
					value  = From.eq(i).text();
645
					To.append(new Option(value, option));
646
					From.eq(i).remove();
647
				}
648
			}
649
		}
650
	}
(2-2/3)