From d9f321a413b69d6a21a91f58b01bb29401542ce0 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Wed, 7 Sep 2011 23:32:24 +0000 Subject: [PATCH] Start of a link-to widget using jQuery-UI's autocomplete --- .../inc/class.etemplate_widget_link.inc.php | 109 ++++++++++ etemplate/js/et2_widget_link.js | 203 ++++++++++++++++++ etemplate/js/etemplate2.js | 2 +- etemplate/js/test/test.css | 20 ++ 4 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 etemplate/inc/class.etemplate_widget_link.inc.php create mode 100644 etemplate/js/et2_widget_link.js diff --git a/etemplate/inc/class.etemplate_widget_link.inc.php b/etemplate/inc/class.etemplate_widget_link.inc.php new file mode 100644 index 0000000000..4cbfa057b3 --- /dev/null +++ b/etemplate/inc/class.etemplate_widget_link.inc.php @@ -0,0 +1,109 @@ + array( + 'link-list'=>array( + 'value' => array('__callback__'=>'get_links'), + 'type' => 'template', + 'id' => 'etemplate.link_widget.list' + ) + ), + ); + */ + + /** + * Set up what we know on the server side. + * + * Set the options for the application select. + * + * @param string $cname + */ + public function beforeSendToClient($cname) + { + $attrs = $this->attrs; + $form_name = self::form_name($cname, $this->id); + $value =& self::get_array(self::$request->content, $form_name, true); + + if(!is_array($value)) + { + throw new egw_exception_wrong_parameter("Wrong value sent to link widget, needs to be an array. " + array2string($value)); + } + + $app = $value['to_app']; + $id = $value['to_id']; + + $help = $attrs['help'] ? ($value['help'] ? $value['help'] : $attrs['help']) : lang('view this linked entry in its application'); + self::setElementAttribute($cname, 'help', $help); + if($this->type == 'link-to') { + if (!is_array(self::$request->sel_options[$form_name])) self::$request->sel_options[$form_name] = array(); + $apps = egw_link::app_list($cell['size'] ? $cell['size'] : 'query'); + asort($apps); + self::$request->sel_options[$form_name] += $apps; + } + + if($attrs['type'] == 'link-list') { + $links = egw_link::get_links($app,$id,'','link_lastmod DESC',true, $value['show_deleted']); + _debug_array($links); + foreach($links as $link) { + $value[] = $link; + } + } + } + + /** + * Find links that match the given parameters + */ + public static function ajax_link_search($app, $type, $pattern, $options=array()) { + $options['type'] = $type ? $type : $options['type']; +error_log("$app, $pattern, $options"); + $links = egw_link::query($app, $pattern, $options); + + $response = egw_json_response::get(); + $response->data($links); + } + + public function get_links($value) { + + $app = $value['to_app']; + $id = $value['to_id']; + + $links = egw_link::get_links($app,$id,'','link_lastmod DESC',true, $value['show_deleted']); +_debug_array($links); + return $links; + } +} diff --git a/etemplate/js/et2_widget_link.js b/etemplate/js/et2_widget_link.js new file mode 100644 index 0000000000..4e37f826dd --- /dev/null +++ b/etemplate/js/et2_widget_link.js @@ -0,0 +1,203 @@ +/** + * eGroupWare eTemplate2 - JS Link object + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Nathan Gray + * @copyright 2011 Nathan Gray + * @version $Id$ + */ + +"use strict"; + +/*egw:uses + jquery.jquery; + jquery.jquery-ui; + et2_core_inputWidget; + et2_core_valueWidget; +*/ + +/** + * UI widgets for Egroupware linking system + */ +var et2_link_to = et2_inputWidget.extend({ + + attributes: { + "application": { + "name": "Application", + "type": "string", + "default": egw_getAppName(), + "description": "Limit to the listed application or applications (comma seperated)" + }, + "blur": { + "name": "Placeholder", + "type": "string", + "default": "", + "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text." + }, + }, + + search_timeout: 200, //ms after change to send query + minimum_characters: 2, // Don't send query unless there's at least this many chars + + init: function() { + this._super.apply(this, arguments); + + this.div = null; + this.search = null; + this.app_select = null; + + this.cache = {}; + + this.createInputWidget(); + }, + + destroy: function() { + this._super.apply(this, arguments); + + this.div = null; + this.search.autocomplete("destroy"); + this.search = null; + this.app_select = null; + + this.createInputWidget(); + }, + + createInputWidget: function() { + var self = this; + this.div = $j(document.createElement("div")); + + // Application selection + this.app_select = $j(document.createElement("select")).appendTo(this.div) + .change(function(e) { + self.cache = {}; // Clear cache when app changes + self.options.value.app = this.val(); + }); + for(var key in this.options.select_options) { + var option = $j(document.createElement("option")) + .attr("value", key) + .text(this.options.select_options[key]); + option.appendTo(this.app_select); + } + self.options.value.app = this.app_select.val(); + + // Search input + this.search = $j(document.createElement("input")).attr("type", "search").appendTo(this.div); + + // Link button + this.link_button = $j(document.createElement("button")) + .text(egw.lang("link")) + .appendTo(this.div).hide() + .click(this, this.createLink); + + this.set_blur(this.blur ? this.blur : egw.lang("search")); + + this.search.autocomplete({ + source: function(request, response) { return self.query(request, response);}, + select: function(event, item) { event.data = self; self.select(event,item); return false;}, + focus: function(event, item) { + event.stopPropagation(); + self.search.val(item.item.label); + return false; + }, + minLength: self.minimum_characters, + disabled: self.options.disabled + }); + this.setDOMNode(this.div[0]); + }, + + transformAttributes: function(_attrs) { + this._super.apply(this, arguments); + + // Try to find the options inside the "sel-options" array + _attrs["select_options"] = this.getArrayMgr("sel_options").getValueForID(this.id); + + // Check whether the options entry was found, if not read it from the + // content array. + if (_attrs["select_options"] == null) + { + _attrs["select_options"] = this.getArrayMgr('content') + .getValueForID("options-" + this.id) + } + + // Default to an empty object + if (_attrs["select_options"] == null) + { + _attrs["select_options"] = {}; + } + }, + + getValue: function() { + return this.options.value; + }, + + set_blur: function(_value) { + if(_value) { + this.search.attr("placeholder", _value); // HTML5 + if(!this.search[0].placeholder) { + // Not HTML5 + if(this.search.val() == "") this.search.val(this.options.blur); + this.search.focus(this,function(e) { + if(e.data.search.val() == e.data.options.blur) e.data.search.val(""); + }).blur(this, function(e) { + if(e.data.search.val() == "") e.data.search.val(e.data.options.blur); + }); + } + } else { + this.search.removeAttr("placeholder"); + } + }, + + /** + * Ask server for entries matching selected app/type and filtered by search string + */ + query: function(request, response) { + if(request.term in this.cache) { + return response(this.cache[request.term]); + } + this.search.addClass("loading"); + this.link_button.hide(); + var request = new egw_json_request("etemplate_widget_link::ajax_link_search::etemplate", + [this.app_select.val(), '', request.term], + this + ); + this.response = response; + request.sendRequest(true, this._results, this); + }, + + /** + * User selected a value + */ + select: function(event, selected) { + event.data.options.value.id = selected.item.value; + event.data.search.val(selected.item.label); + event.data.link_button.show(); + }, + + /** + * Server found some results + */ + _results: function(data) { + this.search.removeClass("loading"); + var result = []; + for(var id in data) { + result.push({"value": id, "label":data[id]}); + } + this.cache[this.search.val()] = result; + this.response(result); + }, + + /** + * Create a link using the current internal values + */ + createLink: function(event) { + console.info("Link: ",event.data.options.value); + event.data.link_button.hide(); + } +}); + +et2_register_widget(et2_link_to, ["link-to"]); + + diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js index cf6f6c90ac..01ae666cd8 100644 --- a/etemplate/js/etemplate2.js +++ b/etemplate/js/etemplate2.js @@ -30,8 +30,8 @@ et2_widget_tabs; et2_widget_hrule; et2_widget_image; - et2_widget_upload; et2_widget_file; + et2_widget_link; et2_extension_nextmatch; diff --git a/etemplate/js/test/test.css b/etemplate/js/test/test.css index 9e223b80f3..55fb4223dc 100644 --- a/etemplate/js/test/test.css +++ b/etemplate/js/test/test.css @@ -190,6 +190,13 @@ span.et2_date span { font-size: 9pt; } +/** Display a loading icon **/ +.loading { + background-position: center right; + background-repeat: no-repeat; + background-image: url('gfx/ajax-loader.gif'); +} + /** * File upload */ @@ -237,6 +244,19 @@ span.et2_date span { .et2_file .progress li.success > span.progressBar { display: none; } + + +/** + * Autocomplete - used in link widget + * Restricting result size + */ +.ui-autocomplete { + max-height: 20ex; + overflow-y: auto; + /* prevent horizontal scrollbar */ + overflow-x: hidden; +} + .egw_tooltip { position: fixed;