egroupware/api/js/etemplate/et2_widget_textbox.js

621 lines
15 KiB
JavaScript
Raw Normal View History

/**
* EGroupware eTemplate2 - JS Textbox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
/*egw:uses
2016-06-06 17:38:20 +02:00
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
et2_core_valueWidget;
*/
/**
* Class which implements the "textbox" XET-Tag
*
* @augments et2_inputWidget
*/
var et2_textbox = (function(){ "use strict"; return et2_inputWidget.extend([et2_IResizeable],
{
attributes: {
"multiline": {
"name": "multiline",
"type": "boolean",
"default": false,
"description": "If true, the textbox is a multiline edit field."
},
"size": {
"name": "Size",
"type": "integer",
"default": et2_no_init,
"description": "Field width"
},
2012-05-08 22:27:38 +02:00
"maxlength": {
"name": "Maximum length",
"type": "integer",
"default": et2_no_init,
"description": "Maximum number of characters allowed"
},
2011-08-26 01:39:34 +02:00
"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."
},
// These for multi-line
"rows": {
"name": "Rows",
"type": "integer",
"default": -1,
"description": "Multiline field height - better to use CSS"
},
"cols": {
"name": "Size",
"type": "integer",
"default": -1,
"description": "Multiline field width - better to use CSS"
},
"validator": {
"name": "Validator",
"type": "string",
"default": et2_no_init,
"description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'"
},
"autocomplete": {
"name": "Autocomplete",
"type": "string",
"default": "",
"description": "Weither or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value for type password is set to off."
},
onkeypress: {
name: "onKeypress",
type: "js",
default: et2_no_init,
description: "JS code or app.$app.$method called when key is pressed, return false cancels it."
}
},
legacyOptions: ["size", "maxlength", "validator"],
/**
* Constructor
*
* @memberOf et2_textbox
*/
init: function() {
this._super.apply(this, arguments);
this.input = null;
this.createInputWidget();
},
createInputWidget: function() {
Major update of the et2_widget internal structure. The following changes were made: - All attributes of the widgets are now parsed from XML before the widget itself is created. These attributes plus all default values are then added to an associative array. The associative array is passed as second parameter to the init function of et2_widget, but is also available as this.options *after* the constructor of the et2_widget baseclass has been called. The et2_widget constructor also calls a function parseArrayMgrAttrs(_attrs) - in this function widget implementations can read the values from e.g. the content and validation_errors array and merge it into the given _attrs associative array. After the complete internal widgettree is completely loaded and created the "loadingFinished" function gets called and invokes all given setter functions. After that it "glues" the DOM tree together. This should also (I didn't measure it) be a bit faster than before, when the DOM-Tree was created on the fly. Please have a look at the changes of the et2_textbox widget to see how this affects writing widgets. Note: The "id" property is copied to the object scope on the top of the et2_widget constructor. - When widgets are cloned the "options" array gets passed along to the newly created widget. This means that changes made on the widgets during runtime are not automatically copied to the clone - as this didn't happen anyhow it is not a really disadvantage. On the other side there should be no difference between widgets directly inside the "overlay" xet tag and widgets which are inside instanciated templates. - The selbox widget doesn't work anymore - it relied on the loadAttributes function which isn't available anymore. et2_selbox should use the parseArrayMgrAttrs function to access - I've commented out some of the "validator"-code in etemplate2.js as it created some error messages when destroying the widget tree.
2011-08-19 18:00:44 +02:00
if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1)
{
this.input = jQuery(document.createElement("textarea"));
if (this.options.rows > 0)
{
this.input.attr("rows", this.options.rows);
}
if (this.options.cols > 0)
{
this.input.attr("cols", this.options.cols);
}
}
else
{
this.input = jQuery(document.createElement("input"));
switch(this.options.type)
{
case "passwd":
this.input.attr("type", "password");
// Make autocomplete default value off for password field
// seems browsers not respecting 'off' anymore and started to
// impelement a new key called "new-password" considered as switching
// autocomplete off.
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
if (this.options.autocomplete === "" || this.options.autocomplete == "off") this.options.autocomplete = "new-password";
break;
case "hidden":
this.input.attr("type", "hidden");
break;
2012-06-26 22:37:58 +02:00
}
if (this.options.autocomplete) this.input.attr("autocomplete", this.options.autocomplete);
}
if(this.options.size) {
this.set_size(this.options.size);
}
if(this.options.blur) {
this.set_blur(this.options.blur);
2011-08-26 01:39:34 +02:00
}
if(this.options.readonly) {
this.set_readonly(true);
}
this.input.addClass("et2_textbox");
this.setDOMNode(this.input[0]);
if(this.options.value)
{
this.set_value(this.options.value);
}
if (this.options.onkeypress && typeof this.options.onkeypress == 'function')
{
var self = this;
this.input.keypress(function(_ev)
{
return self.options.onkeypress.call(this, _ev, self);
});
}
},
/**
* Override the parent set_id method to manuipulate the input DOM node
*
* @param {type} _value
* @returns {undefined}
*/
set_id: function(_value)
{
this._super.apply(this,arguments);
// Remove the name attribute inorder to affect autocomplete="off"
// for no password save. ATM seems all browsers ignore autocomplete for
// input field inside the form
if (this.options.type === "passwd"
&& this.options.autocomplete === "off") this.input.removeAttr('name');
},
destroy: function() {
var node = this.getInputNode();
if (node) jQuery(node).unbind("keypress");
this._super.apply(this, arguments);
},
getValue: function()
{
if(this.options && this.options.blur && this.input.val() == this.options.blur) return "";
return this._super.apply(this, arguments);
},
/**
* Clientside validation using regular expression in "validator" attribute
*
* @param {array} _messages
*/
isValid: function(_messages)
{
var ok = true;
// Check input is valid
if(this.options && this.options.validator && !this.options.readonly && !this.disabled)
{
if (typeof this.options.validator == 'string')
{
var parts = this.options.validator.split('/');
var flags = parts.pop();
if (parts.length < 2 || parts[0] !== '')
{
_messages.push(this.egw().lang("'%1' has an invalid format !!!", this.options.validator));
return false; // show invalid expression
}
parts.shift();
this.options.validator = new RegExp(parts.join('/'), flags);
}
var value = this.getValue();
if (!(ok = this.options.validator.test(value)))
{
_messages.push(this.egw().lang("'%1' has an invalid format !!!", value));
}
}
return this._super.apply(this, arguments) && ok;
},
/**
* Set input widget size
* @param _size Rather arbitrary size units, approximately characters
*/
set_size: function(_size) {
if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1)
{
this.input.css('width', _size + "em");
}
else if (typeof _size != 'undefined' && _size != this.input.attr("size"))
{
this.size = _size;
this.input.attr("size", this.size);
}
2011-08-26 01:39:34 +02:00
},
/**
* Set maximum characters allowed
* @param _size Max characters allowed
*/
2012-05-08 22:27:38 +02:00
set_maxlength: function(_size) {
if (typeof _size != 'undefined' && _size != this.input.attr("maxlength"))
{
this.maxLength = _size;
this.input.attr("maxLength", this.maxLength);
}
},
/**
* Set HTML readonly attribute.
* Do not confuse this with etemplate readonly, which would use et_textbox_ro instead
* @param _readonly Boolean
*/
set_readonly: function(_readonly) {
this.input.attr("readonly", _readonly);
this.input.toggleClass('et2_textbox_ro', _readonly);
},
2011-08-26 01:39:34 +02:00
set_blur: function(_value) {
if(_value) {
this.input.attr("placeholder", this.egw().lang(_value) + ""); // HTML5
2011-08-26 01:39:34 +02:00
if(!this.input[0].placeholder) {
// Not HTML5
if(this.input.val() == "") this.input.val(this.egw().lang(this.options.blur));
2011-08-26 01:39:34 +02:00
this.input.focus(this,function(e) {
if(e.data.input.val() == e.data.egw().lang(e.data.options.blur)) e.data.input.val("");
2011-08-26 01:39:34 +02:00
}).blur(this, function(e) {
if(e.data.input.val() == "") e.data.input.val(e.data.egw().lang(e.data.options.blur));
2011-08-26 01:39:34 +02:00
});
}
} else {
if (!this.getValue()) this.input.val('');
2011-08-26 01:39:34 +02:00
this.input.removeAttr("placeholder");
}
this.options.blur = _value;
},
set_autocomplete: function(_value) {
this.options.autocomplete = _value;
this.input.attr('autocomplete', _value);
},
resize: function (_height)
{
if (_height && this.options.multiline)
{
// apply the ratio
_height = (this.options.resize_ratio != '')? _height * this.options.resize_ratio: _height;
if (_height != 0)
{
this.input.height(this.input.height() + _height);
// resize parent too, so mailvelope injected into parent inherits its height
this.input.parent().height(this.input.parent().height()+_height);
}
}
}
});}).call(this);
et2_register_widget(et2_textbox, ["textbox", "passwd", "hidden"]);
/**
* et2_textbox_ro is the dummy readonly implementation of the textbox.
*
* @augments et2_valueWidget
*/
var et2_textbox_ro = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDetachedDOM],
{
/**
* Ignore all more advanced attributes.
*/
attributes: {
"multiline": {
"ignore": true
},
"maxlength": {
"ignore": true
},
"onchange": {
"ignore": true
},
"rows": {
"ignore": true
},
"cols": {
"ignore": true
},
"size": {
"ignore": true
},
"needed": {
"ignore": true
}
},
/**
* Constructor
*
* @memberOf et2_textbox_ro
*/
init: function() {
this._super.apply(this, arguments);
this.value = "";
this.span = jQuery(document.createElement("label"))
2013-10-08 09:46:44 +02:00
.addClass("et2_label");
this.value_span = jQuery(document.createElement("span"))
2013-10-08 09:46:44 +02:00
.addClass("et2_textbox_ro")
.appendTo(this.span);
this.setDOMNode(this.span[0]);
},
set_label: function(label)
{
2013-10-08 09:46:44 +02:00
// Remove current label
this.span.contents()
.filter(function(){ return this.nodeType == 3; }).remove();
2013-10-08 09:46:44 +02:00
var parts = et2_csvSplit(label, 2, "%s");
this.span.prepend(parts[0]);
this.span.append(parts[1]);
this.label = label;
// add class if label is empty
this.span.toggleClass('et2_label_empty', !label || !parts[0]);
2013-10-08 09:46:44 +02:00
},
set_value: function(_value)
{
this.value = _value;
if(!_value)
{
_value = "";
}
if (this.label !="")
{
this.span.removeClass('et2_label_empty');
}
else
{
this.span.addClass('et2_label_empty');
}
2013-10-08 09:46:44 +02:00
this.value_span.text(_value);
},
/**
2013-04-12 11:38:12 +02:00
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs array to add further attributes to
2013-04-12 11:38:12 +02:00
*/
getDetachedAttributes: function(_attrs)
{
2013-10-08 09:46:44 +02:00
_attrs.push("value", "label");
2013-04-12 11:38:12 +02:00
},
getDetachedNodes: function()
{
2013-10-08 09:46:44 +02:00
return [this.span[0], this.value_span[0]];
2013-04-12 11:38:12 +02:00
},
setDetachedAttributes: function(_nodes, _values)
{
this.span = jQuery(_nodes[0]);
2013-10-08 09:46:44 +02:00
this.value_span = jQuery(_nodes[1]);
if(typeof _values["label"] != 'undefined')
{
this.set_label(_values["label"]);
}
if(typeof _values["value"] != 'undefined')
{
this.set_value(_values["value"]);
}
}
});}).call(this);
et2_register_widget(et2_textbox_ro, ["textbox_ro"]);
2016-02-15 19:30:40 +01:00
/**
* et2_searchbox is a widget which provides a collapsable input search
* with on searching indicator and clear handler regardless of any browser limitation.
*
2016-02-15 19:30:40 +01:00
* @type type
*/
var et2_searchbox = (function(){ "use strict"; return et2_textbox.extend(
2016-02-15 19:30:40 +01:00
{
/**
* Advanced attributes
2016-02-15 19:30:40 +01:00
*/
attributes: {
overlay:{
name:"Overlay searchbox",
type:"boolean",
default:false,
description:"Define wheter the searchbox overlays while it's open (true) or stay as solid box infront of the search button (false). Default is false."
},
fix: {
name:"Fix searchbox",
type:"boolean",
default:true,
description:"Define wheter the searchbox should be a fix input field or flexible search button. Default is true (fix)."
}
},
2016-02-15 19:30:40 +01:00
/**
* Constructor
*
* @memberOf et2_searchbox
*/
init: function() {
this.value = "";
this.div = jQuery(document.createElement('div'))
.addClass('et2_searchbox');
this.flex = jQuery(document.createElement('div'))
.addClass('flex')
.appendTo(this.div);
2016-02-15 19:30:40 +01:00
this._super.apply(this, arguments);
this.setDOMNode(this.div[0]);
this._createWidget();
},
2016-02-15 19:30:40 +01:00
_createWidget:function()
{
var self = this;
if (this.options.overlay) this.flex.addClass('overlay');
2016-02-15 19:30:40 +01:00
// search button indicator
// no need to create search button if it's a fix search field
if (!this.options.fix)
{
this.button = et2_createWidget('button',{image:"search","background_image":"1"},this);
this.button.onclick= function(){
self._show_hide(jQuery(self.flex).hasClass('hide'));
self.search.input.focus();
};
this.div.prepend(this.button.getDOMNode());
}
2016-02-15 19:30:40 +01:00
// input field
this.search = et2_createWidget('textbox',{"blur":egw.lang("search"),
onkeypress:function(event) {
if(event.which == 13)
{
event.preventDefault();
self.getInstanceManager().autocomplete_fixer();
// Use a timeout to make sure we get the autocomplete value,
// if one was chosen, instead of what was actually typed.
// Chrome doesn't need this, but FF does.
window.setTimeout(function() {
self.set_value(self.search.input.val());
self.change();
},0);
}
}},this);
// Autocomplete needs name
this.search.input.attr('name', this.id||'searchbox');
2016-02-15 19:30:40 +01:00
this.search.input.on({
keyup:function(event)
{
self.clear.toggle(self.get_value() !='' || !self.options.fix);
2016-02-15 19:30:40 +01:00
if(event.which == 27) // Escape
{
// Excape clears search
self.set_value('');
}
},
2016-02-15 19:30:40 +01:00
blur: function(event){
if (egwIsMobile()) return;
if (!event.relatedTarget || !jQuery(event.relatedTarget.parentNode).hasClass('et2_searchbox'))
{
self._show_hide((!self.options.overlay && self.get_value()));
}
if (typeof self.oldValue !='undefined' && self._oldValue != self.get_value()) {
2016-02-15 19:30:40 +01:00
self.change();
}
},
mousedown:function(event){
if (event.target.type == 'span') event.stopImmidatePropagation();
}
});
this.flex.append(this.search.getDOMNode());
2016-02-15 19:30:40 +01:00
// clear button implementation
this.clear = jQuery(document.createElement('span'))
.addClass('ui-icon clear')
.toggle(!this.options.fix || (this._oldValue != '' && !jQuery.isEmptyObject(this._oldValue)))
2016-02-15 19:30:40 +01:00
.on('mousedown',function(event){
event.preventDefault();
})
.on('click',function(event) {
if (self.get_value()){
self.search.input.val('');
self.search.input.focus();
self._show_hide(true);
if (self._oldValue) self.change();
}
else
{
self._show_hide(false);
}
if (self.options.fix) self.clear.hide();
2016-02-15 19:30:40 +01:00
})
.appendTo(this.flex);
2016-02-15 19:30:40 +01:00
},
2016-02-15 19:30:40 +01:00
/**
* Show/hide search field
2016-02-15 19:30:40 +01:00
* @param {boolean} _stat true means show and false means hide
*/
_show_hide: function(_stat)
{
// Not applied for fix option
if (this.options.fix) return;
jQuery(this.flex).toggleClass('hide',!_stat);
jQuery(this.getDOMNode()).toggleClass('expanded', _stat);
2016-02-15 19:30:40 +01:00
},
2016-02-15 19:30:40 +01:00
/**
* toggle search button status based on value
*/
_searchToggleState:function()
{
if (this.options.fix || egwIsMobile()) return;
2016-02-15 19:30:40 +01:00
if (!this.get_value())
{
jQuery(this.button.getDOMNode()).removeClass('toolbar_toggled');
this.button.set_statustext('');
2016-02-15 19:30:40 +01:00
}
else
{
jQuery(this.button.getDOMNode()).addClass('toolbar_toggled');
this.button.set_statustext(egw.lang("search for '%1'", this.get_value()));
2016-02-15 19:30:40 +01:00
}
},
/**
* override change function in order to preset the toggle state
*/
change:function()
{
this._searchToggleState();
2016-02-15 19:30:40 +01:00
this._super.apply(this,arguments);
},
2016-02-15 19:30:40 +01:00
get_value:function(){
return this.search.input.val();
},
set_value: function (_value){
this._super.apply(this,arguments);
if (this.search) this.search.input.val(_value);
},
2016-02-15 19:30:40 +01:00
/**
* override doLoadingFinished in order to set initial state
2016-02-15 19:30:40 +01:00
*/
doLoadingFinished: function()
{
this._super.apply(this,arguments);
if (!this.get_value()) {
this._show_hide(false);
}
else{
this._show_hide(!this.options.overlay);
2016-02-15 19:30:40 +01:00
this._searchToggleState();
}
},
/**
* Overrride attachToDOM in order to unbind change handler
*/
attachToDOM: function() {
this._super.apply(this,arguments);
var node = this.getInputNode();
if (node)
{
jQuery(node).off('.et2_inputWidget');
}
},
});}).call(this);
et2_register_widget(et2_searchbox, ["searchbox"]);