diff --git a/etemplate/inc/class.etemplate_widget_menupopup.inc.php b/etemplate/inc/class.etemplate_widget_menupopup.inc.php index 31812a89cc..4d2545915f 100644 --- a/etemplate/inc/class.etemplate_widget_menupopup.inc.php +++ b/etemplate/inc/class.etemplate_widget_menupopup.inc.php @@ -120,6 +120,13 @@ class etemplate_widget_menupopup extends etemplate_widget if (!is_array(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = array(); if ($this->attrs['type']) { + // Check selection preference, we may be able to skip reading some data + $select_pref = $GLOBALS['egw_info']['user']['preferences']['common']['account_selection']; + if(!$GLOBALS['egw_info']['apps']['admin'] && $select_pref == 'none') + { + $this->attrs['readonly'] = true; + } + // += to keep further options set by app code self::$request->sel_options[$form_name] += self::typeOptions($this->attrs['type'], // typeOptions thinks # of rows is the first thing in options @@ -300,7 +307,10 @@ class etemplate_widget_menupopup extends etemplate_widget 'title' => empty($cat['description']) ? $s : $cat['description'], ); // Send data too - $options[$cat['id']] += $cat['data']; + if(is_array($cat['data'])) + { + $options[$cat['id']] += $cat['data']; + } } // preserv unavailible cats (eg. private user-cats) /* TODO @@ -316,8 +326,12 @@ class etemplate_widget_menupopup extends etemplate_widget case 'select-account': // options: #rows,{accounts(default)|both|groups|owngroups},{0(=lid)|1(default=name)|2(=lid+name),expand-multiselect-rows,not-to-show-accounts,...)} //echo "

select-account widget: name=$cell[name], type='$type', rows=$rows, readonly=".(int)($cell['readonly'] || $readonlys)."

\n"; + + // Get preference for selection display + $select_pref = $GLOBALS['egw_info']['user']['preferences']['common']['account_selection']; + // in case of readonly, we read/create only the needed entries, as reading accounts is expensive - if ($readonly) + if ($readonly || $select_pref == 'popup') { $no_lang = True; if (!is_array($value) && strpos($value,',') !== false) $value = explode(',',$value); @@ -328,42 +342,21 @@ class etemplate_widget_menupopup extends etemplate_widget } break; } - if($type == 'owngroups') + if($type == 'owngroups' || $select_pref == 'groupmembers') { $type = 'groups'; $owngroups = true; foreach($GLOBALS['egw']->accounts->membership() as $group) $mygroups[] = $group['account_id']; } - /* account-selection for hughe number of accounts - if ($this->ui == 'html' && $type != 'groups') // use eGW's new account-selection (html only) + elseif (in_array($type, array('accounts', 'both')) && $select_pref == 'primary_group') { - $not = array_slice(explode(',',$cell['size']),4); - $help = (int)$no_lang < 2 ? lang($cell['help']) : $cell['help']; - $onFocus = "self.status='".addslashes(htmlspecialchars($help))."'; return true;"; - $onBlur = "self.status=''; return true;"; - if ($cell['noprint']) - { - foreach(is_array($value) ? $value : (strpos($value,',') !== false ? explode(',',$value) : array($value)) as $id) - { - if ($id) $onlyPrint[] = self::accountInfo($id,$acc,$type2,$type=='both'); - } - $onlyPrint = $onlyPrint ? implode('
',$onlyPrint) : lang((int)$rows < 0 ? 'all' : $rows); - $noPrint_class = ' class="noPrint"'; - } - if (($rows > 0 || $type3) && substr($name,-2) != '[]') $name .= '[]'; - $value = $GLOBALS['egw']->uiaccountsel->selection($name,'eT_accountsel_'.str_replace(array('[','][',']'),array('_','_',''),$name), - $value,$type,$rows > 0 ? $rows : ($type3 ? -$type3 : 0),$not,' onfocus="'.$onFocus.'" onblur="'.$onBlur.'"'.$noPrint_class, - $cell['onchange'] == '1' ? 'this.form.submit();' : $cell['onchange'], - !empty($rows) && 0+$rows <= 0 ? lang($rows < 0 ? 'all' : $rows) : False); - if ($cell['noprint']) - { - $value = ''.$onlyPrint.''.$value; - } - $cell['type'] = 'html'; - $cell['size'] = ''; // is interpreted as link otherwise - etemplate::$request->set_to_process($name,'select'); - break; - }*/ + $owngroups = true; + $mygroups[] = $GLOBALS['egw_info']['user']['primary_group']; + } + + // Popup is the only preference option that doesn't need more select options + if($select_pref == 'popup') break; + $no_lang = True; $accs = $GLOBALS['egw']->accounts->get_list(empty($type) ? 'accounts' : $type); // default is accounts foreach($accs as $acc) diff --git a/etemplate/js/et2_widget_selectAccount.js b/etemplate/js/et2_widget_selectAccount.js index 5092b2c8e2..e4d3cafe62 100644 --- a/etemplate/js/et2_widget_selectAccount.js +++ b/etemplate/js/et2_widget_selectAccount.js @@ -19,6 +19,418 @@ et2_widget_link; */ +/** + * Account selection widget + * Changes according to the user's account_selection preference + * - 'none' => Server-side: the read-only widget is used + * - 'groupmembers' => Non admins can only select groupmembers (Server side - normal selectbox) + * - 'selectbox' => Selectbox with all accounts and groups (Server side - normal selectbox) + * - 'primary_group' => Selectbox with primary group and search + * - 'popup' => Empty selectbox with search + * + * Only primary_group and popup need anything different from a normal selectbox + */ +var et2_selectAccount = et2_selectbox.extend({ + + attributes: { + 'account_type': { + 'name': 'Account type', + 'default': 'accounts', + 'type': 'string', + 'description': 'Limit type of accounts. One of {accounts,groups,both,owngroups}.' + } + }, + + legacyOptions: ['empty_label','account_type'], + + account_types: ['accounts','groups','both','owngroups'], + + init: function(_parent, _attrs) { + + // Type in rows or somewhere else? + if(jQuery.inArray(_attrs['empty_label'], this.account_types) > 0 && ( + jQuery.inArray(_attrs['account_type'], this.account_types) < 0 || + _attrs['account_type'] == this.attributes.account_type.default) + ) + { + _attrs['account_type'] = _attrs['empty_label'] + _attrs['empty_label'] = ''; + } + if(jQuery.inArray(_attrs['account_type'], this.account_types) < 0) + { + this.egw().debug("warn", "Invalid account_type: %s Valid options:",_attrs['account_type'], this.account_types); + } + + this._super.apply(this, arguments); + + // Holder for search jQuery nodes + this.search = null; + + // Reference to object with dialog + this.dialog = null; + }, + + destroy: function() { + this._super.apply(this, arguments); + }, + + /** + * Single selection - override to add search button + */ + createInputWidget: function() { + + this._super.apply(this, arguments); + + // Add search button + var type = this.egw().preference('account_selection', 'common'); + if(type == 'primary_group' || type == 'popup') + { + var button = jQuery(document.createElement("span")) + .addClass("et2_clickable") + .click(this, this._open_search) + .append('') + + this.getSurroundings().insertDOMNode(button[0]); + } + }, + + /** + * Multiple selection - override to add search button + */ + createMultiSelect: function() { + this._super.apply(this, arguments); + + // Add search button + var button = jQuery(document.createElement("li")) + .addClass("et2_clickable") + .click(this, this._open_multi_search) + .append('') + .append(""+this.egw().lang('search')+""); + var type = this.egw().preference('account_selection', 'common'); + + // Put it first so check/uncheck don't move around + this.multiOptions.prev().show().find('ul') + .prepend(button); + }, + + /** + * Create & display a way to search & select a single account / group + * Single selection is just link widget + */ + _open_search: function(e) { + var widget = e.data; + var search = widget._create_search(); + + // Selecting a single user closes the dialog, this only used if user cleared + var ok_click = function() { + jQuery(this).dialog("close"); + widget.set_value([]); + // Free it up, it will be re-created, if ever needed again + jQuery(this).dialog("destroy"); + }; + widget._create_dialog(search, ok_click); + }, + + /** + * Create & display a way to search & select multiple accounts / groups + */ + _open_multi_search: function(e) { + var widget = e.data; + var table = widget.search = jQuery('
'); + table.css("width", "100%").css("height", "100%"); + var search_col = jQuery('#search_col',table); + var select_col = jQuery('#selection_col',table); + + // Search / Selection + search_col.append(widget._create_search()); + + // Currently selected + select_col.append(widget._create_selected()); + + var ok_click = function() { + jQuery(this).dialog("close"); + // Update widget with selected + var ids = []; + var data = {}; + jQuery('#selected li',select_col).each(function() { + // Remove sel_ prefix and add to list + ids.push(this.id.substr(4)); + + // Make sure option is there + if(jQuery('input[id$="_opt_'+this.id.substr(4)+'"]',widget.multiOptions).length == 0) + { + widget._appendMultiOption(this.id.substr(4),jQuery('label',this).text()); + } + }); + + widget.set_value(ids); + + // Free it up, it will be re-created, if ever needed again + jQuery(this).dialog("destroy"); + }; + + var container = jQuery(document.createElement("div")).append(table); + return widget._create_dialog(container, ok_click); + }, + + /** + * Create / display popup with search / selection widgets + */ + _create_dialog: function(widgets, update_function) { + this.dialog = widgets; + widgets.dialog({ + title: this.options.label ? this.options.label : this.egw().lang('Select'), + modal: true, + // Static size for easier layout + width: "500", + height: "350", + buttons: [{ + text: this.egw().lang("ok"), + click: update_function + },{ + text: this.egw().lang("cancel"), + click: function() { + jQuery(this).dialog("close"); + jQuery(this).dialog("destroy"); + }} + ] + }); + }, + + /** + * Search is a link-entry widget, with some special display for multi-select + */ + _create_search: function() { + var self = this; + var search = this.search = jQuery(document.createElement("div")); + + var search_widget = this.search_widget = et2_createWidget('link-entry', { + 'application': 'home-accounts', + 'query': function(request, response) { + // Clear previous search results for multi-select + if(!request.options) + { + search.find('#search_results').empty(); + } + // Restrict to specified account type + if(!request['options'] || !request['options']['filter']) + { + request['options'] = {filter:{group:self.options.account_type}}; + } + return true; + }, + 'select': function(e, selected) { + // Make sure option is there + if(typeof self.options.select_options[selected.item.value] == 'undefined') + { + self.options.select_options[selected.item.value] = selected.item; + self._appendOptionElement(selected.item.value, selected.item.label); + } + self.set_value(selected.item.value); + if(self.dialog) + { + self.dialog.dialog("close"); + self.dialog.dialog("destroy"); + } + } + }, this); + // select widget doesn't allow children, so add it + search.append(search_widget.getDOMNode()); + + if(!this.options.multiple) return search; + + // Multiple is more complicated. It uses a custom display for results to + // allow choosing multiples from a match + var results = jQuery(document.createElement("ul")) + .attr("id", "search_results") + .css("height", "230px") + .addClass("ui-multiselect-checkboxes ui-helper-reset"); + jQuery(document.createElement("div")) + .addClass("et2_selectbox") + .css("height", "100%") + .append(results) + .appendTo(search); + + // Override link-entry auto-complete for custom display + // Don't show normal drop-down + search_widget.search.data("autocomplete")._suggest = function(items) { + jQuery.each(items, function (index, item) { + self._add_search_result(results, item); + }); + } + + return search; + }, + + /** + * Add the selected result to the list of search results + */ + _add_search_result: function(list, item) { + + var node = null; + var self = this; + + // (containter of) Currently selected users / groups + var selected = jQuery('#selected', this.dialog); + + // Group + if(item.value && item.value < 0) + { + node = jQuery(document.createElement('ul')); + // Add button to show users + if(this.options.account_type != 'groups') + { + jQuery('') + .css("float", "left") + .appendTo(node) + .toggle(function() { + jQuery(this).removeClass("ui-icon-circlesmall-plus") + .addClass("ui-icon-circlesmall-minus"); + + var group = jQuery(this).parent() + .addClass("expanded"); + + if(group.children("li").length == 0) + { + // Fetch group members + self.search_widget.query({term:"",options: {filter:{group: item.value}}}, function(items) { + jQuery(items).each(function(index,item) { + self._add_search_result(node, item); + }); + }); + } + else + { + group.children("li") + // Only show children that are not selected + .each(function(index, item) { + var j = jQuery(item); + if(jQuery('#sel_'+j.attr("id").substr(5),selected).length == 0) + { + j.show(); + } + }); + } + },function() { + jQuery(this).addClass("ui-icon-circlesmall-plus") + .removeClass("ui-icon-circlesmall-minus"); + + var group = jQuery(this).parent().children("li").hide(); + }); + } + + } + // User + else if (item.value) + { + node = jQuery(document.createElement('li')); + } + node.attr("id", "list_"+item.value); + + jQuery('') + .css("float", "right") + .appendTo(node) + .click(function() { + var button = jQuery(this); + self._add_selected(selected, button.parent().attr("id").substr(5)); + // Hide user, but only hide button for group + if(button.parent().is('li')) + { + button.parent().hide(); + } + else + { + button.hide(); + } + }); + + // If already in list, hide it + if(jQuery('#sel_'+item.value,selected).length != 0) + { + node.hide(); + } + + var label = jQuery(document.createElement('label')) + .addClass("loading") + .appendTo(node); + this.egw().link_title('home-accounts', item.value, function(name) { + label.text(name).removeClass("loading") + }, label); + + node.appendTo(list) + }, + + _create_selected: function() { + var node = jQuery(document.createElement("div")) + .addClass("et2_selectbox"); + + var header = jQuery(document.createElement("div")) + .addClass("ui-widget-header ui-helper-clearfix") + .appendTo(node); + + var selected = jQuery(document.createElement("ul")) + .addClass("ui-multiselect-checkboxes ui-helper-reset") + .attr("id", "selected") + .css("height", "230px") + .appendTo(node); + + jQuery(document.createElement("span")) + .text(this.egw().lang("Selection")) + .addClass("ui-multiselect-header") + .appendTo(header); + + var controls = jQuery(document.createElement("ul")) + .addClass('ui-helper-reset') + .appendTo(header); + + jQuery(document.createElement("li")) + .addClass("et2_clickable") + .click(selected, function(e) {jQuery("li",e.data).remove();}) + .append('') + .append(""+this.egw().lang('Remove all')+"") + .appendTo(controls); + + // Add in currently selected + if(this.getValue()) + { + var value = this.getValue(); + for(var i = 0; i < value.length; i++) { + this._add_selected(selected, value[i]); + } + } + return node; + }, + + /** + * Add an option to the list of selected accounts + * value is the account / group ID + */ + _add_selected: function(list, value) { + + var option = jQuery(document.createElement('li')) + .attr("id",'sel_'+value) + .appendTo(list); + jQuery('') + .css("float", "right") + .appendTo(option) + .click(function() { + var id = jQuery(this).parent().attr("id").substr(4); + jQuery(this).parent().remove(); + // Add 'add' button back, if in results list + list.parents("tr").find("#list_"+id).show() + // Show button(s) for group + .children('span').show(); + }); + + var label = jQuery(document.createElement('label')) + .addClass("loading") + .appendTo(option); + this.egw().link_title('home-accounts', value, function(name) {this.text(name).removeClass("loading")}, label); + } +}); +et2_register_widget(et2_selectAccount, ["select-account"]); + + /** * et2_selectAccount_ro is the readonly implementation of select account * It extends et2_link to avoid needing the whole user list on the client. diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index 1b10ea7d0f..6b36a10438 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -211,6 +211,18 @@ button.et2_button_text:focus, input[type=button]:focus { list-style-type: none; text-indent: -26px; } +.et2_selectbox .ui-multiselect-checkboxes ul { + margin: 0px auto; + padding-left: 0px; + clear:both; + text-decoration: none; + list-style-image: none; + list-style-type: none; +} +.et2_selectbox .ui-multiselect-checkboxes ul>label { + border-bottom: 1px solid black; +} + .et2_selectbox .ui-multiselect-checkboxes label { display: block; border: 1px solid transparent; @@ -234,6 +246,10 @@ ul.et2_selectbox { list-style-type: none; } +/** + * Select account dialog + */ + /** * Date / Time widgets */