Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

placeholder feature for HTML Text Input #288

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/aria/html/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,31 @@
*/
_notifyDataChange : function (args, propertyName) {
this.onbind(propertyName, this._transform(this._cfg.bind[propertyName].transform, args.newValue, "toWidget"), args.oldValue);
},

/**
* Add a listener for an event. It will be called before an already registered event, if any.
* @protected
* @param {aria.html.beans.ElementCfg.Properties.$properties.on} listeners Map of listeners
* @param {aria.templates.TemplateCtxt} context Template context
* @param {String} eventType Type of the event
* @param {aria.core.CfgBeans.Callback} callback listener to chain
*/
_chainListener : function (listeners, context, eventType, callback) {
var normalized = null;
if (listeners[eventType]) {
normalized = this.$normCallback.call(context._tpl, listeners[eventType]);
}

listeners[eventType] = {
fn : function (event) {
this.$callback(callback, event);
if (normalized) {
normalized.fn.call(normalized.scope, event, normalized.args);
}
},
scope : this
};
}
}
});
Expand Down
196 changes: 155 additions & 41 deletions src/aria/html/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,20 @@
*/
function typeCallback (callback) {
this._typeCallback = null;

callNormalizedCallback(callback, this._domElt.value);
}

/**
* Callback for handling placeholder after type.
* @private
*/
function setPlaceholderOnType () {
// This is to display the placeholder when the text input value is empty
if (!_placeholderSupported && this._cfg.placeholder) {
this._setPlaceholder();
}
}

/**
* Convert a keydown event into a type event. This is achieved adding a very short callback on keydown. The reason
* being the fact that on keydown the input has still the previous value. In the callback we'll see the correct text
Expand All @@ -58,41 +68,104 @@
}

/**
* Being a BindableWidget we already have one direction binding of value (from the datamodel to teh widget). This
* Internal callback for placeholder handling on keydown.
* @param {aria.DomEvent} event keydown event
* @private
*/
function keyDownCallback (event) {
// This is to remove the placeholder when the text input receives the first input char
if (!_placeholderSupported && this._cfg.placeholder) {
if (this._hasPlaceholder) {
var domevent = aria.DomEvent;
var specialKeys = [domevent.KC_END, domevent.KC_RIGHT, domevent.KC_ARROW_RIGHT, domevent.KC_DOWN,
domevent.KC_ARROW_DOWN, domevent.KC_DELETE, domevent.KC_BACKSPACE];
if (!aria.utils.Array.contains(specialKeys, event.keyCode)) {
var cssClass = new aria.utils.ClassList(this._domElt);
this._domElt.value = "";
this._hasPlaceholder = false;
cssClass.remove('placeholder');
cssClass.$dispose();
} else {
event.preventDefault();
}
}
}
}

/**
* Being a BindableWidget we already have one direction binding of value (from the datamodel to the widget). This
* function is the callback for implementing the other bind, from the widget to the datamodel. The value is set in
* the datamodel on blur. It also takes care of calling the 'on blur' callback if it was defined.
* @param {aria.DomEvent} event blur event
* @param {aria.core.CfgBeans.Callback} blurCallback On blur callback
* @private
*/
function bidirectionalBlurBinding (event, blurCallback) {
function bidirectionalBlurBinding (event) {
var bind = this._bindingListeners.value;
var newValue = this._transform(bind.transform, event.target.getValue(), "fromWidget");
aria.utils.Json.setValue(bind.inside, bind.to, newValue, bind.cb);

this._hasFocus = false;

if (this._cfg.placeholder) {
if (!this._hasPlaceholder) {
aria.utils.Json.setValue(bind.inside, bind.to, newValue, bind.cb);
} else {
aria.utils.Json.setValue(bind.inside, bind.to, "", bind.cb);
}
}

if (!_placeholderSupported && this._cfg.placeholder) {
this._setPlaceholder();
}

this._firstFocus = true;

if (blurCallback) {
blurCallback.fn.call(blurCallback.scope, event, blurCallback.args);
}

/**
* This is to put the caret at position (0, 0) in browsers that do not support the placeholder attribute.
* @param {aria.DomEvent} event focus event
* @private
*/
function focusBinding (event) {
this._hasFocus = true;

if (this._cfg.placeholder) {
var cssClass = new aria.utils.ClassList(this._domElt);
if (cssClass.contains('placeholder')) {
aria.utils.Caret.setPosition(this._domElt, 0, 0);
}
cssClass.$dispose();
}
}

/**
* This is to implement the autoselect.
* @param {aria.DomEvent} event focus event
* @param {aria.core.CfgBeans.Callback} clickCallback On click callback
* @private
*/
function clickBinding (event, clickCallback) {
function clickBinding (event) {
if (this._cfg.autoselect) {
this._autoselect();
}

if (clickCallback) {
clickCallback.fn.call(clickCallback.scope, event, clickCallback.args);
if (!_placeholderSupported && this._cfg.placeholder) {
var cssClass = new aria.utils.ClassList(this._domElt);
if (cssClass.contains('placeholder')) {
aria.utils.Caret.setPosition(this._domElt, 0, 0);
} else {
aria.utils.Caret.select(this._domElt);
}
cssClass.$dispose();
} else {
aria.utils.Caret.select(this._domElt);
}
}
}

/**
* This is to check if the browser supports placeholder attribute.
* @private
* @type Boolean
*/
var _placeholderSupported = null;

/**
* TextInput widget. Bindable widget providing bi-directional bind of 'value' and on 'type' event callback.
*/
Expand All @@ -111,23 +184,44 @@
cfg.attributes.type = (cfg.password) ? "password" : "text";
cfg.on = cfg.on || {};


_placeholderSupported = ("placeholder" in Aria.$window.document.createElement("input"));
if (cfg.placeholder && _placeholderSupported) {
cfg.attributes.placeholder = cfg.placeholder;
}

this._registerListeners(cfg, context);

/**
* Wheter or not this widget has a 'on type' callback
* @protected
* @type Boolean
*/
this._reactOnType = this._registerType(cfg.on, context);

this._registerListeners(cfg, context);

/**
* Flag set to false after first focus, and set back to true after a blur. Used for the autoselect
* behaviour. This value is true when the field receives focus for the first time (user action) and false
* when the focus is given programmatically by the controller
* @protected
* @type Boolean
*/
this._firstFocus = true;

/**
* Flag used to indicate if the element has focus
* @protected
* @type Boolean
*/
this._hasFocus = false;

/**
* Flag used to indicate if the element has the placeholder
* @protected
* @type Boolean
*/
this._hasPlaceholder = false;

this.$Element.constructor.call(this, cfg, context, line);
},
$destructor : function () {
Expand Down Expand Up @@ -165,6 +259,11 @@
this._domElt.value = newValue;
}
}

var placeholder = this._cfg.placeholder;
if (!_placeholderSupported && placeholder) {
this._setPlaceholder();
}
},

/**
Expand Down Expand Up @@ -231,48 +330,63 @@
*/
_autoselect : function () {
if (this._firstFocus) {
// this allow to click again and put the cursor at a given
// position
this._firstFocus = false;
var field = this._domElt;
var start = 0;
var end = (field.value.length) ? field.value.length : 0;
if (end) {
aria.utils.Caret.setCaretPosition(field, start, end);
aria.utils.Caret.select(this._domElt);
}
},

/**
* Set the css class and value for placeholder. Used only in IE 6/7/8/9 and FF 3.6.
* @protected
*/
_setPlaceholder : function () {
var element = this._domElt;
if (element.value === "") {
element.value = this._cfg.placeholder;
var cssClass = new aria.utils.ClassList(element);
cssClass.add('placeholder');
cssClass.$dispose();
if (this._hasFocus) {
aria.utils.Caret.setPosition(element, 0, 0);
}
this._hasPlaceholder = true;
}
},

/**
* Add special listeners on top of the ones specified in configuration.
* @param {aria.html.beans.TextInputCfg.Properties} cfg Widget configuration.
* @param {aria.templates.TemplateCtxt} context Reference of the template context.
* @param {aria.templates.TemplateCtxt} context Template context.
* @protected
*/
_registerListeners : function (cfg, context) {
var listeners = cfg.on;
var normalized;

if (listeners.blur) {
normalized = this.$normCallback.call(context._tpl, listeners.blur);
}

listeners.blur = {
this._chainListener(listeners, context, "blur", {
fn : bidirectionalBlurBinding,
scope : this,
args : normalized
};
scope : this
});

if (cfg.autoselect) {
if (listeners.click) {
normalized = this.$normCallback.call(context._tpl, listeners.click);
}
if ((!_placeholderSupported && cfg.placeholder) || cfg.autoselect) {
this._chainListener(listeners, context, "focus", {
fn : focusBinding,
scope : this
});

listeners.click = {
this._chainListener(listeners, context, "click", {
fn : clickBinding,
scope : this,
args : normalized
};
scope : this
});

this._chainListener(listeners, context, "keydown", {
fn : keyDownCallback,
scope : this
});

this._chainListener(listeners, context, "type", {
fn : setPlaceholderOnType,
scope : this
});
}
}

Expand Down
16 changes: 14 additions & 2 deletions src/aria/utils/Caret.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Aria.classDefinition({
* @param {HTMLElement} element The html element
* @return {Object} The caret position (start and end)
*/
getCaretPosition : function (element) {
getPosition : function (element) {
var pos = {
start : 0,
end : 0
Expand Down Expand Up @@ -58,7 +58,7 @@ Aria.classDefinition({
* @param {Number} start The starting caret position
* @param {Number} end The ending caret position
*/
setCaretPosition : function (element, start, end) {
setPosition : function (element, start, end) {
if ("selectionStart" in element) {
element.selectionStart = start;
element.selectionEnd = end;
Expand All @@ -71,6 +71,18 @@ Aria.classDefinition({
range.select();
}
}
},

/**
* Select the element text setting the caret position to the whole input value.
* @type {HTMLElement} element The html elment
*/
select : function (element) {
var start = 0;
var end = (element.value.length) ? element.value.length : 0;
if (end) {
this.setPosition(element, start, end);
}
}
}
});
4 changes: 2 additions & 2 deletions src/aria/widgets/form/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ Aria.classDefinition({
return null;
}
var ctrl = this.getTextInputField();
return aria.utils.Caret.getCaretPosition(ctrl);
return aria.utils.Caret.getPosition(ctrl);
},

/**
Expand All @@ -521,7 +521,7 @@ Aria.classDefinition({
}

var ctrl = this.getTextInputField();
aria.utils.Caret.setCaretPosition(ctrl, start, end);
aria.utils.Caret.setPosition(ctrl, start, end);
},

/**
Expand Down
1 change: 1 addition & 0 deletions test/aria/html/textinput/TextInputTestSuite.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ Aria.classDefinition({
this.addTests("test.aria.html.textinput.TextInputPasswordTest");
this.addTests("test.aria.html.textinput.focus.FocusTestCase");
this.addTests("test.aria.html.textinput.autoselect.AutoselectTestCase");
this.addTests("test.aria.html.textinput.placeholder.PlaceholderTestCase");
}
});
Loading