diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index c7af191..249da95 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1,3639 +1,3640 @@
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2014, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak |
+--------------------------------------------------------------------------+
*/
function kolab_admin()
{
var self = this;
this.env = {};
this.translations = {};
this.request_timeout = 300;
this.message_time = 3000;
this.events = {};
this.attr_default_rx = /^(text|select|imap_acl)/;
// set jQuery ajax options
$.ajaxSetup({
cache: false,
error: function(request, status, err) { self.http_error(request, status, err); },
beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', self.env.token); }
});
/*********************************************************/
/********* basic utilities *********/
/*********************************************************/
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// add a localized label(s) to the client environment
this.tdef = function(p, value)
{
if (typeof p == 'string')
this.translations[p] = value;
else if (typeof p == 'object')
$.extend(this.translations, p);
};
// return a localized string
this.t = function(label)
{
if (this.translations[label])
return this.translations[label];
else
return label;
};
// print a message into browser console
this.log = function(msg)
{
if (window.console && console.log)
console.log(msg);
};
// execute a specific command on the web client
this.command = function(command, props, obj)
{
if (obj && obj.blur)
obj.blur();
if (this.busy)
return false;
this.set_busy(true, 'loading');
var ret = undefined,
func = command.replace(/[^a-z]/g, '_'),
task = command.replace(/\.[a-z-_]+$/g, '');
if (this[func] && typeof this[func] === 'function') {
ret = this[func](props);
}
else {
this.http_post(command, props);
}
// update menu state
$('li', $('#navigation')).removeClass('active');
$('li.'+task, ('#navigation')).addClass('active');
if (ret === false)
this.set_busy(false);
return ret === false ? false : obj ? false : true;
};
this.set_busy = function(a, message)
{
if (a && message) {
var msg = this.t(message);
if (msg == message)
msg = 'Loading...';
this.display_message(msg, 'loading');
}
else if (!a) {
this.hide_message('loading');
}
this.busy = a;
// if (this.gui_objects.editform)
// this.lock_form(this.gui_objects.editform, a);
// clear pending timer
if (this.request_timer)
clearTimeout(this.request_timer);
// set timer for requests
if (a && this.env.request_timeout)
this.request_timer = window.setTimeout(function() { self.request_timed_out(); }, this.request_timeout * 1000);
};
// called when a request timed out
this.request_timed_out = function()
{
this.set_busy(false);
this.display_message('Request timed out!', 'error');
};
// Add variable to GET string, replace old value if exists
this.add_url = function(url, name, value)
{
value = urlencode(value);
if (/(\?.*)$/.test(url)) {
var urldata = RegExp.$1,
datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
if (datax.test(urldata))
urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
else
urldata += '&' + name + '=' + value
return url.replace(/(\?.*)$/, urldata);
}
else
return url + '?' + name + '=' + value;
};
this.trigger_event = function(event, data)
{
if (this.events[event])
for (var i in this.events[event])
this.events[event][i](data);
};
this.add_event_listener = function(event, func)
{
if (!this.events[event])
this.events[event] = [];
this.events[event].push(func);
};
/*********************************************************/
/********* GUI functionality *********/
/*********************************************************/
// write to the document/window title
this.set_pagetitle = function(title)
{
if (title && document.title)
document.title = title;
};
// display a system message (types: loading, notice, error)
this.display_message = function(msg, type, timeout)
{
var obj;
if (!type)
type = 'notice';
if (msg)
msg = this.t(msg);
if (type == 'loading') {
timeout = this.request_timeout * 1000;
if (!msg)
msg = this.t('loading');
}
else if (!timeout)
timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
obj = $('
');
if (type != 'loading') {
msg = '
' + msg + '
';
obj.addClass(type).click(function() { return self.hide_message(); });
}
if (timeout > 0)
window.setTimeout(function() { self.hide_message(type, type != 'loading'); }, timeout);
if (type == 'loading')
this.hide_message(type);
obj.attr('id', type == 'loading' ? 'loading' : 'message')
.appendTo('body').html(msg).show();
};
// make a message to disapear
this.hide_message = function(type, fade)
{
if (type == 'loading')
$('#loading').remove();
else
$('#message').fadeOut('normal', function() { $(this).remove(); });
};
this.set_watermark = function(id)
{
if (this.env.watermark)
$('#'+id).html(this.env.watermark);
}
// modal dialog popup
this.modal_dialog = function(content, buttons, opts)
{
var settings = {btns: {}},
body = $(''),
head, foot, footer = [];
// title bar
if (opts && opts.title)
$('')
.append($('').text(opts.title))
.appendTo(body);
// dialog content
if (typeof content != 'object')
content = $('').html(content);
content.addClass('modal_msg').appendTo(body);
// buttons
$.each(buttons, function(i, v) {
var n = i.replace(/[^a-z0-9_]/ig, '');
settings.btns[n] = v;
footer.push({name: n, label: self.t(i)});
});
// if (!settings.btns.cancel && (!opts || !opts.no_cancel))
// settings.btns.cancel = function() { this.hide(); };
if (footer.length) {
foot = $('');
$.each(footer, function() {
$('').addClass('modal_btn_' + this.name).text(this.label).appendTo(foot);
});
body.append(foot);
}
// configure and display dialog
body.wModal(settings).wModal('show');
};
this.tree_list_init = function()
{
$('table.list.tree span.expando').click(function() {
var tr = $(this).parents('table.list.tree tr'),
expanded = tr.hasClass('expanded'),
level = tr.data('level') || 0,
row = tr[0],
found = false;
tr[expanded ? 'removeClass' : 'addClass']('expanded');
$('tr', tr.parent()).each(function() {
if (this === row) {
found = true;
return;
}
if (!found)
return;
var r = $(this), l = r.data('level') || 0;
if (l <= level)
return false;
if (!expanded && l == level+1)
r.show();
else if (expanded && l > level)
r.hide().removeClass('expanded');
});
return false;
});
};
// position and display popup
this.popup_show = function(e, popup)
{
var popup = $(popup),
pos = this.mouse_pos(e),
win = $(window),
w = popup.width(),
h = popup.height(),
left = pos.left - w,
top = pos.top;
if (top + h > win.height())
top -= h;
if (left + w > win.width())
left -= w;
popup.css({left: left + 'px', top: top + 'px'}).show();
e.stopPropagation();
};
/********************************************************/
/********* Remote request methods *********/
/********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var k, param = {},
querystring = typeof query === 'string' ? '&' + query : '';
if (typeof action !== 'string')
query = action;
else if (!query || typeof query !== 'object')
query = {};
// overwrite task name
if (action) {
if (action.match(/^([a-z]+)/i))
query.task = RegExp.$1;
if (action.match(/[^a-z0-9-_]([a-z0-9-_]+)$/i))
query.action = RegExp.$1;
}
// remove undefined values
for (k in query) {
if (query[k] !== undefined && query[k] !== null)
param[k] = query[k];
}
return '?' + $.param(param) + querystring;
};
// send a http POST request to the server
this.http_post = function(action, postdata)
{
var url = this.url(action);
if (postdata && typeof postdata === 'object')
postdata.remote = 1;
else {
if (!postdata)
postdata = '';
postdata += '&remote=1';
}
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: postdata, dataType: 'json',
success: function(response) { kadm.http_response(response, action); },
error: function(o, status, err) { kadm.http_error(o, status, err); }
});
};
// send a http POST request to the API service
this.api_post = function(action, postdata, func)
{
var url = 'api/' + action;
if (!func) func = 'api_response';
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: JSON.stringify(postdata), dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function(response) { kadm[func](response); },
error: function(o, status, err) { kadm.http_error(o, status, err); }
});
};
// handle HTTP response
this.http_response = function(response, action)
{
var i;
if (!response)
return;
// set env vars
if (response.env)
this.set_env(response.env);
// we have translation labels to add
if (typeof response.labels === 'object')
this.tdef(response.labels);
// HTML page elements
if (response.objects)
for (i in response.objects)
$('#'+i).html(response.objects[i]);
this.update_request_time();
this.set_busy(false);
// if we get javascript code from server -> execute it
if (response.exec)
eval(response.exec);
response.action = action;
this.trigger_event('http-response', response);
};
// handle HTTP request errors
this.http_error = function(request, status, err)
{
var errmsg = request.statusText;
this.set_busy(false);
request.abort();
if (request.status && errmsg)
this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error');
};
this.api_response = function(response)
{
this.update_request_time();
this.set_busy(false);
if (!response || response.status != 'OK') {
// Logout on invalid-session error
if (response && response.code == 403)
this.main_logout();
else
this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error');
return false;
}
return true;
};
/*********************************************************/
/********* keyboard autocomplete methods *********/
/*********************************************************/
this.ac_init = function(obj, props)
{
if (props && props.form) {
if (i = $('[name="type_id"]', props.form).val())
props.type_id = i;
if (i = $('[name="object_type"]', props.form).val())
props.object_type = i;
if (i = $('[name="id"]', props.form).val())
props.id = i;
delete props['form'];
}
obj.keydown(function(e) { return kadm.ac_keydown(e, props); })
.attr('autocomplete', 'off');
};
// handler for keyboard events on autocomplete-fields
this.ac_keydown = function(e, props)
{
if (this.ac_timer)
clearTimeout(this.ac_timer);
var highlight, key = e.which;
switch (key) {
case 38: // arrow up
case 40: // arrow down
if (!this.ac_visible())
break;
var dir = key == 38 ? 1 : 0;
highlight = $('.selected', this.ac_pane).get(0);
if (!highlight)
highlight = this.ac_pane.__ul.firstChild;
if (highlight)
this.ac_select(dir ? highlight.previousSibling : highlight.nextSibling);
return e.stopPropagation();
case 9: // tab
if (e.shiftKey || !this.ac_visible()) {
this.ac_stop();
return;
}
case 13: // enter
if (!this.ac_visible())
return false;
// insert selected item and hide selection pane
this.ac_insert(this.ac_selected);
this.ac_stop();
- return e.stopPropagation();
+ e.stopPropagation();
+ return false;
case 27: // escape
this.ac_stop();
return;
case 37: // left
case 39: // right
if (!e.shiftKey)
return;
}
// start timer
this.ac_timer = window.setTimeout(function() { kadm.ac_start(props); }, 500);
this.ac_input = e.target;
return true;
};
this.ac_visible = function()
{
return (this.ac_selected !== null && this.ac_selected !== undefined && this.ac_value);
};
this.ac_select = function(node)
{
if (!node)
return;
var current = $('.selected', this.ac_pane);
if (current.length)
current.removeClass('selected');
$(node).addClass('selected');
this.ac_selected = node._id;
};
// autocomplete search processor
this.ac_start = function(props)
{
var q = this.ac_input ? this.ac_input.value : null,
min = this.env.autocomplete_min_length,
old_value = this.ac_value,
ac = this.ac_data;
if (q === null)
return;
// trim query string
q = $.trim(q);
// Don't (re-)search if the last results are still active
if (q == old_value)
return;
// Stop and destroy last search
this.ac_stop();
if (q.length && q.length < min) {
this.display_message(this.t('search.acchars').replace('$min', min), 'notice', 2000);
return;
}
this.ac_value = q;
// ...string is empty
if (!q.length)
return;
// ...new search value contains old one, but the old result was empty
if (old_value && old_value.length && q.indexOf(old_value) == 0 && this.ac_result && !this.ac_result.length)
return;
var i, xhr, data = props,
action = props && props.action ? props.action : 'form_value.list_options';
this.ac_oninsert = props.oninsert;
data.search = q;
delete data['action'];
delete data['insert_func'];
this.display_message(this.t('search.loading'), 'loading');
xhr = this.api_post(action, data, 'ac_result');
this.ac_data = xhr;
};
this.ac_result = function(response)
{
// search stopped in meantime?
if (!this.ac_value)
return;
if (!this.api_response(response))
return;
// ignore this outdated search response
if (this.ac_input && response.result.search != this.ac_value)
return;
// display search results
var i, ul, li, text,
result = response.result.list,
pos = $(this.ac_input).offset(),
value = this.ac_value,
rx = new RegExp('(' + RegExp.escape(value) + ')', 'ig');
// create results pane if not present
if (!this.ac_pane) {
ul = $('
');
this.ac_pane = $('
').attr('id', 'autocompletepane')
.css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
this.ac_pane.__ul = ul[0];
}
ul = this.ac_pane.__ul;
// reset content
ul.innerHTML = '';
// move the results pane right under the input box
this.ac_pane.css({left: (pos.left - 1)+'px', top: (pos.top + this.ac_input.offsetHeight - 1)+'px', display: 'none'});
// add each result line to the list
for (i in result) {
text = result[i];
li = document.createElement('LI');
li.innerHTML = text.replace(rx, '##$1%%').replace(//g, '>').replace(/##([^%]+)%%/g, '$1');
li.onmouseover = function() { kadm.ac_select(this); };
li.onmouseup = function() { kadm.ac_click(this) };
li._id = i;
ul.appendChild(li);
}
if (ul.childNodes.length) {
this.ac_pane.show();
// select the first
li = $('li:first', ul);
li.addClass('selected');
this.ac_selected = li.get(0)._id;
}
this.env.ac_result = result;
};
this.ac_click = function(node)
{
if (this.ac_input)
this.ac_input.focus();
this.ac_insert(node._id);
this.ac_stop();
};
this.ac_insert = function(id)
{
var val = this.env.ac_result[id];
if (typeof this.ac_oninsert == 'function')
this.ac_oninsert(id, val);
else
$(this.ac_input).val(val);
};
this.ac_blur = function()
{
if (this.ac_timer)
clearTimeout(this.ac_timer);
this.ac_input = null;
this.ac_stop();
};
this.ac_stop = function()
{
this.ac_selected = null;
this.ac_value = '';
if (this.ac_pane)
this.ac_pane.hide();
this.ac_destroy();
};
// Clears autocomplete data/requests
this.ac_destroy = function()
{
if (this.ac_data)
this.ac_data.abort();
this.ac_data = null;
this.ac_info = null;
};
/*********************************************************/
/********* Forms widgets *********/
/*********************************************************/
// Form initialization
this.form_init = function(id)
{
var form = $('#'+id),
aci_fields = $('textarea[data-type="aci"]', form);
this.aci = {};
this.acl = {};
this.trigger_event('form-load', id);
// replace some textarea fields with pretty/smart input lists
$('textarea[data-type="list"]', form)
.each(function() { kadm.form_list_element_wrapper(this); });
// create smart select fields
$('input[data-type="select"]', form)
.each(function() { kadm.form_select_element_wrapper(this); });
// create LDAP URL fields
$('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form)
.each(function() { kadm.form_url_element_wrapper(this); });
// create IMAP ACL fields
$('textarea[data-type="acl"]', form)
.each(function() { kadm.form_acl_element_wrapper(this); });
// create ACI fields
aci_fields.each(function() { kadm.form_aci_element_wrapper(this); });
if (aci_fields.length)
this.form_aci_init();
};
// Form serialization
this.form_serialize = function(data)
{
var form = $(data.id);
// smart list fields
$('textarea[data-type="list"]:not(:disabled)', form).each(function() {
var i, v, value = [],
re = RegExp('^' + RegExp.escape(this.name) + '\[[0-9-]+\]$');
for (i in data.json) {
if (i.match(re)) {
if (v = $('input[name="'+i+'"]', form).val())
value.push(v);
delete data.json[i];
}
}
// autocompletion lists data is stored in env variable
if (kadm.env.assoc_fields[this.name]) {
value = [];
for (i in kadm.env.assoc_fields[this.name])
value.push(i);
}
data.json[this.name] = value;
});
// smart selects
$('input[data-type="select"]', form).each(function() {
delete data.json[this.name];
});
// LDAP URL fields
$('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form).each(function() {
data.json = kadm.form_url_element_submit(this.name, data.json, form);
});
// IMAP ACL fields
$('textarea[data-type="acl"]:not(:disabled):not([readonly])', form).each(function() {
data.json = kadm.form_acl_element_submit(this.name, data.json, form);
});
// ACI fields
$('textarea[data-type="aci"]:not(:disabled):not([readonly])', form).each(function() {
data.json = kadm.form_aci_element_submit(this.name, data.json, form);
});
// text-separated fields (convert text into array)
$('textarea[data-type="separated"]:not(:disabled)', form).each(function() {
data.json[this.name] = data.json[this.name] ? data.json[this.name].split(/[\r\n]+/) : '';
});
// quota inputs
$('input[data-type="quota"]', form).each(function() {
var unit = $('select[name="' + this.name + '-unit"]').val();
if (unit && this.value)
data.json[this.name] = this.value + unit;
delete data.json[this.name + '-unit'];
});
// "boolean" checkbox inputs
$('input[type="checkbox"][value="TRUE"]', form).each(function() {
data.json[this.name] = this.checked ? 'TRUE' : 'FALSE';
});
return data;
};
// Form element update handler
this.form_element_update = function(data)
{
var elem = $('[name="'+data.name+'"]');
if (!elem.length)
return;
if (elem.attr('data-type') == 'list') {
// remove old wrapper
$('span[class="listarea"]', elem.parent()).remove();
// insert new list element
this.form_list_element_wrapper(elem.get(0));
}
};
// Replaces form element with smart list element
this.form_list_element_wrapper = function(form_element)
{
var i = 0, j = 0, list = [], elem, e = $(form_element),
form = form_element.form,
disabled = e.attr('disabled'),
readonly = e.attr('readonly'),
autocomplete = e.attr('data-autocomplete'),
maxlength = e.attr('data-maxlength'),
maxcount = e.attr('data-maxcount'),
area = $('');
e.hide();
if (autocomplete)
list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : [];
else if (form_element.value)
list = form_element.value.split("\n");
// Need at least one element
if (!autocomplete || disabled || readonly) {
$.each(list, function() { i++; });
if (!i)
list = [''];
}
// Create simple list for readonly/disabled
if (disabled || readonly) {
area.addClass('readonly');
// add simple input rows
$.each(list, function(i, v) {
var elem = $('');
elem.attr({
value: v,
disabled: disabled,
readonly: readonly,
name: form_element.name + '[' + (j++) + ']'
})
elem = $('').append(elem);
elem.appendTo(area);
});
}
// extended widget with add/remove buttons and/or autocompletion
else {
// add autocompletion input
if (autocomplete) {
elem = this.form_list_element(form, {
maxlength: maxlength,
autocomplete: autocomplete,
element: e
}, -1);
// Initialize autocompletion
this.ac_init(elem, {
form: form,
attribute: form_element.name,
oninsert: this.form_element_oninsert
});
// when max=1 we use only one input
if (maxcount == 1) {
$.each(list, function(i, v) {
$('input', elem).val(v);
return false;
});
list = [];
}
elem.appendTo(area);
area.addClass('autocomplete');
}
// add input rows
$.each(list, function(i, v) {
var elem = kadm.form_list_element(form, {
value: v,
key: i,
maxlength: maxlength,
autocomplete: autocomplete,
element: e
}, j++);
elem.appendTo(area);
});
}
area.appendTo(form_element.parentNode);
};
// Creates smart list element
this.form_list_element = function(form, data, idx)
{
var content, elem, input,
key = data.key,
orig = data.element
ac = data.autocomplete;
data.name = (orig ? orig.attr('name') : data.name) + '[' + idx + ']';
data.readonly = (ac && idx >= 0);
// remove internal attributes
delete data['element'];
delete data['autocomplete'];
delete data['key'];
// build element content
content = ''
+ (!ac ? '' : ac && idx == -1 ? '' : '')
+ (!ac || idx >= 0 ? '' : '')
+ '';
elem = $(content);
input = $('input', elem);
// Set INPUT attributes
input.attr(data);
if (data.readonly)
input.addClass('readonly');
if (ac)
input.addClass('autocomplete');
// attach element creation event
if (!ac)
$('span[class="add"]', elem).click(function() {
var name = data.name.replace(/\[[0-9]+\]$/, ''),
span = $(this.parentNode.parentNode),
maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount');
// check element count limit
if (maxcount && maxcount <= span.parent().children().length) {
alert(kadm.t('form.maxcount.exceeded'));
return;
}
var dt = (new Date()).getTime(),
elem = kadm.form_list_element(form, {name: name}, dt);
kadm.ac_stop();
span.after(elem);
$('input', elem).focus();
});
// attach element deletion event
if (!ac || idx >= 0)
$('span[class="reset"]', elem).click(function() {
var span = $(this.parentNode.parentNode),
name = data.name.replace(/\[[0-9]+\]$/, ''),
l = $('input[name^="' + name + '["]', form),
key = $(this).data('key');
if (l.length > 1 || $('input[name="' + name + '"]', form).attr('data-autocomplete'))
span.remove();
else
$('input', span).val('').focus();
// delete key from internal field representation
if (key !== undefined && kadm.env.assoc_fields[name])
delete kadm.env.assoc_fields[name][key];
kadm.ac_stop();
}).data('key', key);
return elem;
};
this.form_element_oninsert = function(key, val)
{
var elem, input = $(this.ac_input).get(0),
dt = (new Date()).getTime(),
span = $(input.parentNode),
name = input.name.replace(/\[-1\]$/, ''),
af = kadm.env.assoc_fields,
maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount');
if (maxcount == 1) {
$(input).val(val);
af[name] = {};
af[name][key] = val;
return;
}
// reset autocomplete input
input.value = '';
// check element count limit
if (maxcount && maxcount <= span.parent().children().length - 1) {
alert(kadm.t('form.maxcount.exceeded'));
return;
}
// check if element doesn't exist on the list already
if (!af[name])
af[name] = {};
if (af[name][key])
return;
// add element
elem = kadm.form_list_element(input.form, {
name: name,
autocomplete: true,
value: val
}, dt);
span.after(elem);
// update field variable
af[name][key] = val;
};
// Replaces form element with smart select element
this.form_select_element_wrapper = function(form_element)
{
var e = $(form_element),
form = form_element.form,
name = form_element.name,
elem = $('#selectlabel_' + name),
area = $(''),
content = $(''),
list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : [];
if (elem.length) {
$('#selectarea_' + name).remove();
$('#selectcontent_' + name).remove();
}
else {
elem = $('')
.css({cursor: 'pointer'})
.click(function(e) {
var popup = $('span.listarea', this.parentNode);
kadm.popup_show(e, popup);
$('input', popup).val('').focus();
$('span.listcontent > span.listelement', popup).removeClass('selected').show();
})
.appendTo(form_element.parentNode);
}
elem.text(e.val());
if (list.length <= 1)
return;
if (form_element.type != 'hidden') e.hide();
elem = this.form_list_element(form, {
autocomplete: true,
element: e
}, -1);
elem.appendTo(area);
content.appendTo(area);
area.hide().appendTo(form_element.parentNode);
// popup events
$('input', area)
.click(function(e) {
// stop click on the popup
e.stopPropagation();
})
.keypress(function(e) {
// prevent form submission with Enter key
if (e.which == 13)
e.preventDefault();
})
.keydown(function(e) {
// block Up/Down arrow keys,
// in Firefox Up arrow moves cursor left
if (e.which == 38 || e.which == 40)
e.preventDefault();
})
.keyup(function(e) {
// filtering
var s = this.value,
options = $('span.listcontent > span.listelement', area);
// Enter key
if (e.which == 13) {
options.filter('.selected').click()
return;
}
// Escape
else if (e.which == 27) {
area.hide();
this.value = s = '';
}
// UP/Down arrows
else if (e.which == 38 || e.which == 40) {
options = options.not(':hidden');
if (options.length <= 1)
return;
var focused,
selected = options.filter('.selected'),
index = options.index(selected);
if (e.which == 40) {
if (!(focused = options.get(index+1)))
focused = options.get(index-1);
}
else {
if (!(focused = options.get(index-1)))
focused = options.get(index+1);
}
if (focused) {
focused = $(focused);
selected.removeClass('selected');
focused.addClass('selected');
var parent = focused.parent(),
parent_height = parent.height(),
parent_top = parent.get(0).scrollTop,
top = focused.offset().top - parent.offset().top,
height = focused.height();
if (top < 0)
parent.get(0).scrollTop = 0;
else if (top >= parent_height)
parent.get(0).scrollTop = top - parent_height + height + parent_top;
}
return;
}
if (!s) {
options.show().removeClass('selected');
return;
}
options.each(function() {
var o = $(this), v = o.data('value');
o[v.indexOf(s) != -1 ? 'show' : 'hide']().removeClass('selected');
});
options = options.not(':hidden');
if (options.length == 1)
options.addClass('selected');
});
// add option rows
$.each(list, function(i, v) {
var elem = kadm.form_select_option_element(form, {value: v, key: v, element: e});
elem.appendTo(content);
});
};
// Creates option element for smart select
this.form_select_option_element = function(form, data)
{
// build element content
var elem = $('')
.data('value', data.key).text(data.value)
.click(function(e) {
var val = $(this).data('value'),
elem = $(data.element),
old_val = elem.val();
$('span.link', elem.parent()).text(val);
elem.val(val);
if (val != old_val)
elem.change();
});
return elem;
};
// initialize ACI fields in form
this.form_aci_init = function()
{
// get list of ldap attributes for ACI form
if (!this.ldap_attributes) {
this.api_post('form_value.select_options', {attributes: ['attribute']}, 'form_aci_init_response');
}
};
this.form_aci_init_response = function(response)
{
if (!this.api_response(response))
return;
this.ldap_attributes = response.result.attribute ? response.result.attribute.list : [];
};
// Replaces form element with ACI element
this.form_aci_element_wrapper = function(form_element)
{
var i, e = $(form_element),
form = form_element.form,
name = form_element.name,
div = $(''),
select = $(''),
table = $('