Autoselect for HTML Text Input #272

Closed
wants to merge 1 commit into from
View
2 src/aria/html/Element.js
@@ -111,7 +111,7 @@
if (eventListeners.hasOwnProperty(listener)) {
hasListeners = true;
- eventListeners[listener] = this.$normCallback.call(this._context, eventListeners[listener]);
+ eventListeners[listener] = this.$normCallback.call(this._context._tpl, eventListeners[listener]);
}
}
View
68 src/aria/html/TextInput.js
@@ -70,18 +70,36 @@
var newValue = this._transform(bind.transform, event.target.getValue(), "fromWidget");
aria.utils.Json.setValue(bind.inside, bind.to, newValue, bind.cb);
+ this._firstFocus = true;
+
if (blurCallback) {
blurCallback.fn.call(blurCallback.scope, event, blurCallback.args);
}
}
/**
+ * 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) {
+ if (this._cfg.autoselect) {
+ this._autoselect();
+ }
+
+ if (clickCallback) {
+ clickCallback.fn.call(clickCallback.scope, event, clickCallback.args);
+ }
+ }
+
+ /**
* TextInput widget. Bindable widget providing bi-directional bind of 'value' and on 'type' event callback.
*/
Aria.classDefinition({
$classpath : "aria.html.TextInput",
$extends : "aria.html.Element",
- $dependencies : ["aria.html.beans.TextInputCfg"],
+ $dependencies : ["aria.html.beans.TextInputCfg", "aria.utils.Caret"],
$statics : {
INVALID_USAGE : "Widget %1 can only be used as a %2."
},
@@ -100,7 +118,15 @@
*/
this._reactOnType = this._registerType(cfg.on, context);
- this._registerBlur(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
+ * @type Boolean
+ */
+ this._firstFocus = true;
this.$Element.constructor.call(this, cfg, context, line);
},
@@ -200,13 +226,31 @@
},
/**
- * Convert the special event type into a keydown event listener.
- * @param {Object} listeners On listeners taken from the widget configuration.
+ * If enabled, autoselect the widget text setting the caret position to the whole input value.
+ * @protected
+ */
+ _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);
+ }
+ }
+ },
+
+ /**
+ * 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.
- * @return {Boolean} Whether the keydown events should be converted back to type events.
* @protected
*/
- _registerBlur : function (listeners, context) {
+ _registerListeners : function (cfg, context) {
+ var listeners = cfg.on;
var normalized;
if (listeners.blur) {
@@ -218,6 +262,18 @@
scope : this,
args : normalized
};
+
+ if (cfg.autoselect) {
+ if (listeners.click) {
+ normalized = this.$normCallback.call(context._tpl, listeners.click);
+ }
+
+ listeners.click = {
+ fn : clickBinding,
+ scope : this,
+ args : normalized
+ };
+ }
}
}
View
5 src/aria/html/beans/TextInputCfg.js
@@ -53,6 +53,11 @@ Aria.beanDefinitions({
$type : "json:Boolean",
$description : "Whether the input field should be of type password.",
$default : false
+ },
+ "autoselect" : {
+ $type : "json:Boolean",
+ $description : "Autoselect for the input field. If true, the whole text inside the field is automatically selected when the user clicks on it.",
+ $default : false
}
}
}
View
76 src/aria/utils/Caret.js
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 Amadeus s.a.s.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class aria.utils.Caret Utilities for the caret
+ * @singleton
+ */
+Aria.classDefinition({
+ $classpath : 'aria.utils.Caret',
+ $singleton : true,
+ $prototype : {
+ /**
+ * Return the caret position of the HTML element
+ * @param {HTMLElement} element The html element
+ * @return {Object} The caret position (start and end)
+ */
+ getCaretPosition : function (element) {
+ var pos = {
+ start : 0,
+ end : 0
+ };
+
+ if ("selectionStart" in element) {
+ // w3c standard, available in all but IE<9
+ pos.start = element.selectionStart;
+ pos.end = element.selectionEnd;
+ } else {
+ // old IE support
+ var document = Aria.$window.document;
+ if (document.selection) {
+ var sel = document.selection.createRange();
+ var initialLength = sel.text.length;
+ sel.moveStart('character', -element.value.length);
+ var x = sel.text.length;
+ pos.start = x - initialLength;
+ pos.end = x;
+ }
+ }
+
+ return pos;
+ },
+
+ /**
+ * Set the caret position of the HTML element
+ * @param {HTMLElement} element The html element
+ * @param {Number} start The starting caret position
+ * @param {Number} end The ending caret position
+ */
+ setCaretPosition : function (element, start, end) {
+ if ("selectionStart" in element) {
+ element.selectionStart = start;
+ element.selectionEnd = end;
+ } else {
+ var document = Aria.$window.document;
+ if (document.selection) {
+ var range = element.createTextRange();
+ range.moveStart('character', start);
+ range.moveEnd('character', -element.value.length + end);
+ range.select();
+ }
+ }
+ }
+ }
+});
View
40 src/aria/widgets/form/TextInput.js
@@ -20,7 +20,7 @@ Aria.classDefinition({
$classpath : "aria.widgets.form.TextInput",
$extends : "aria.widgets.form.InputWithFrame",
$dependencies : ["aria.utils.Function", "aria.utils.Data", "aria.utils.String",
- "aria.widgets.environment.WidgetSettings"],
+ "aria.widgets.environment.WidgetSettings", "aria.utils.Caret"],
$css : ["aria.widgets.form.TextInputStyle"],
/**
* TextInput constructor
@@ -507,29 +507,7 @@ Aria.classDefinition({
return null;
}
var ctrl = this.getTextInputField();
- var pos = {
- start : 0,
- end : 0
- };
-
- if ("selectionStart" in ctrl) {
- // w3c standard, available in all but IE<9
- pos.start = ctrl.selectionStart;
- pos.end = ctrl.selectionEnd;
- } else {
- // old IE support
- var document = Aria.$window.document;
- if (document.selection) {
- var sel = document.selection.createRange();
- var initialLength = sel.text.length;
- sel.moveStart('character', -ctrl.value.length);
- var x = sel.text.length;
- pos.start = x - initialLength;
- pos.end = x;
- }
- }
-
- return pos;
+ return aria.utils.Caret.getCaretPosition(ctrl);
},
/**
@@ -543,19 +521,7 @@ Aria.classDefinition({
}
var ctrl = this.getTextInputField();
-
- if ("selectionStart" in ctrl) {
- ctrl.selectionStart = start;
- ctrl.selectionEnd = end;
- } else {
- var document = Aria.$window.document;
- if (document.selection) {
- var range = ctrl.createTextRange();
- range.moveStart('character', start);
- range.moveEnd('character', -ctrl.value.length + end);
- range.select();
- }
- }
+ aria.utils.Caret.setCaretPosition(ctrl, start, end);
},
/**
View
34 test/aria/html/ElementEventsTest.js
@@ -67,7 +67,7 @@ Aria.classDefinition({
oneEvent = events[1];
this.assertEquals(oneEvent.type, "keydown", "Second event should be a keydown, got " + oneEvent.type);
- this.assertEquals(oneEvent.context, "Element", "Second event should have context Element, got "
+ this.assertEquals(oneEvent.context, "TplContext", "Second event should have context Element, got "
+ oneEvent.context);
this.assertEquals(oneEvent.tagName, "DIV", "Second event should happen on a DIV, got "
+ oneEvent.tagName);
@@ -79,7 +79,7 @@ Aria.classDefinition({
oneEvent = events[2];
this.assertEquals(oneEvent.type, "mousedown", "Third event should be a mousedown, got " + oneEvent.type);
- this.assertEquals(oneEvent.context, "Element", "Third event should have context Element, got "
+ this.assertEquals(oneEvent.context, "TplContext", "Third event should have context Element, got "
+ oneEvent.context);
this.assertEquals(oneEvent.tagName, "SPAN", "Third event should happen on a SPAN, got "
+ oneEvent.tagName);
@@ -145,7 +145,7 @@ Aria.classDefinition({
keydown : function (evt) {
events.push({
type : evt.type,
- context : this.tplClasspath,
+ context : this.context,
tagName : evt.target.tagName,
key : "tokyo-keydown",
param : "missing",
@@ -157,7 +157,10 @@ Aria.classDefinition({
};
return new aria.html.Element(first, {
- tplClasspath : "Element"
+ tplClasspath : "Element",
+ _tpl : {
+ context : "TplContext"
+ }
});
},
@@ -198,16 +201,19 @@ Aria.classDefinition({
return new aria.html.Element(second, {
tplClasspath : "Element",
- functionOnTemplateContext : function (evt) {
- events.push({
- type : evt.type,
- context : this.tplClasspath,
- tagName : evt.target.tagName,
- key : "osaka-mousedown",
- param : "missing",
- isTargetWrapped : !!evt.target.$DomElementWrapper,
- name : evt.target.getAttribute("name")
- });
+ _tpl : {
+ functionOnTemplateContext : function (evt) {
+ events.push({
+ type : evt.type,
+ context : this.context,
+ tagName : evt.target.tagName,
+ key : "osaka-mousedown",
+ param : "missing",
+ isTargetWrapped : !!evt.target.$DomElementWrapper,
+ name : evt.target.getAttribute("name")
+ });
+ },
+ context : "TplContext"
}
});
},
View
1 test/aria/html/textinput/TextInputTestSuite.js
@@ -24,5 +24,6 @@ Aria.classDefinition({
this.addTests("test.aria.html.textinput.TextInputOnTypeTest");
this.addTests("test.aria.html.textinput.TextInputPasswordTest");
this.addTests("test.aria.html.textinput.focus.FocusTestCase");
+ this.addTests("test.aria.html.textinput.autoselect.AutoselectTestCase");
}
});
View
135 test/aria/html/textinput/autoselect/AutoselectTestCase.js
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012 Amadeus s.a.s.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Aria.classDefinition({
+ $classpath : "test.aria.html.textinput.autoselect.AutoselectTestCase",
+ $extends : "aria.jsunit.TemplateTestCase",
+ $dependencies : ["aria.html.TextInput", "aria.utils.SynEvents", "aria.utils.Dom"],
+ $constructor : function () {
+ this.$TemplateTestCase.constructor.call(this);
+ this.element = null;
+ },
+ $destructor : function () {
+ this.element = null;
+ },
+ $prototype : {
+ runTemplateTest : function () {
+ var document = Aria.$window.document;
+ var inputs = document.getElementsByTagName("input");
+ this.element = inputs[0];
+ this.secondElement = inputs[1];
+
+ aria.utils.SynEvents.click(this.element, {
+ fn : this.afterFirstClick,
+ scope : this
+ });
+ },
+
+ afterFirstClick : function () {
+ var caretPos = aria.utils.Caret.getCaretPosition(this.element);
+
+ this.assertEquals(caretPos.start, 0, "The start pos of caret is not zero");
+ this.assertEquals(caretPos.end, 0, "The end pos of caret is not zero");
+
+ aria.utils.SynEvents.type(this.element, "brazil", {
+ fn : this.afterType,
+ scope : this
+ });
+ },
+
+ afterType : function () {
+ var caretPos = aria.utils.Caret.getCaretPosition(this.element);
+
+ this.assertEquals(caretPos.start, this.element.value.length, "The start pos of caret is not at the end of the word typed");
+ this.assertEquals(caretPos.end, this.element.value.length, "The end pos of caret is not at the end of the word typed");
+
+ this.assertEquals(this.element.value, "brazil", "The value of input text is not brazil");
+
+ var outside = aria.utils.Dom.getElementById("outsideDiv");
+
+ aria.utils.SynEvents.click(outside, {
+ fn : this.afterSecondClick,
+ scope : this
+ });
+ },
+
+ afterSecondClick : function () {
+ aria.utils.SynEvents.click(this.element, {
+ fn : this.afterThirdClick,
+ scope : this
+ });
+ },
+
+ afterThirdClick : function () {
+ this.assertEquals(this.templateCtxt._tpl.data.click, 2, "Click callback set in the widget configuration has not been called");
+
+ var caretPos = aria.utils.Caret.getCaretPosition(this.element);
+ this.assertEquals(caretPos.start, 0, "The start pos of caret is not zero");
+ this.assertEquals(caretPos.end, this.element.value.length, "The end pos of caret is not at the end of the word typed");
+ this.assertEquals(this.element.value, "brazil", "The value of input text is not brazil");
+
+ aria.utils.SynEvents.click(this.secondElement, {
+ fn : this.afterFirstClickTwo,
+ scope : this
+ });
+ },
+
+ afterFirstClickTwo : function () {
+ var caretPos = aria.utils.Caret.getCaretPosition(this.secondElement);
+
+ this.assertEquals(caretPos.start, 0, "The start pos of caret is not zero");
+ this.assertEquals(caretPos.end, 0, "The end pos of caret is not zero");
+
+ aria.utils.SynEvents.type(this.secondElement, "argentina", {
+ fn : this.afterTypeTwo,
+ scope : this
+ });
+ },
+
+ afterTypeTwo : function () {
+ var caretPos = aria.utils.Caret.getCaretPosition(this.secondElement);
+
+ this.assertEquals(caretPos.start, this.secondElement.value.length, "The start pos of caret is not at the end of the word typed");
+ this.assertEquals(caretPos.end, this.secondElement.value.length, "The end pos of caret is not at the end of the word typed");
+
+ this.assertEquals(this.secondElement.value, "argentina", "The value of input text is not argentina");
+
+ var outside = aria.utils.Dom.getElementById("outsideDiv");
+
+ aria.utils.SynEvents.click(outside, {
+ fn : this.afterSecondClickTwo,
+ scope : this
+ });
+ },
+
+ afterSecondClickTwo : function () {
+ aria.utils.SynEvents.click(this.secondElement, {
+ fn : this.afterThirdClickTwo,
+ scope : this
+ });
+ },
+
+ afterThirdClickTwo : function () {
+ // check that click callback declared in the widget configuration is called
+ this.assertEquals(this.templateCtxt._tpl.data.clickNoAutoselect, 2, "Click callback set in the widget configuration has not been called");
+
+ var caretPos = aria.utils.Caret.getCaretPosition(this.secondElement);
+ this.assertEquals(caretPos.start - caretPos.end, 0, "Autoselect false has not been taken into account");
+ this.assertEquals(this.secondElement.value, "argentina", "The value of input text is not argentina");
+
+ this.end();
+ }
+ }
+});
View
61 test/aria/html/textinput/autoselect/AutoselectTestCaseTpl.tpl
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 Amadeus s.a.s.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+{Template {
+ $classpath : "test.aria.html.textinput.autoselect.AutoselectTestCaseTpl",
+ $hasScript: true,
+ $wlibs : {
+ html : "aria.html.HtmlLibrary"
+ }
+}}
+
+{macro main()}
+
+<div id="testForTextInputAutoselect">
+
+ {@html:TextInput {
+ id: "texttest",
+ autoselect: true,
+ on : {
+ type : "textType",
+ click : "textClick"
+ },
+ bind : {
+ value: {
+ inside: data,
+ to: "location"
+ }
+ }
+ }/}
+
+ {@html:TextInput {
+ id: "texttestOne",
+ on : {
+ type : "textType",
+ click : "textClickWithoutAutoselect"
+ },
+ bind : {
+ value: {
+ inside: data,
+ to: "departure"
+ }
+ }
+ }/}
+
+</div>
+
+<div id="outsideDiv">&nbsp;</div>
+{/macro}
+{/Template}
View
40 test/aria/html/textinput/autoselect/AutoselectTestCaseTplScript.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 Amadeus s.a.s.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Aria.tplScriptDefinition({
+ $classpath : "test.aria.html.textinput.autoselect.AutoselectTestCaseTplScript",
+ $prototype : {
+ $dataReady : function () {
+ this.data = {
+ location : '',
+ departure : '',
+ click : 0,
+ clickNoAutoselect : 0
+ };
+ },
+
+ textType : function (value) {
+ return;
+ },
+
+ textClick : function () {
+ this.data.click++;
+ },
+
+ textClickWithoutAutoselect : function () {
+ this.data.clickNoAutoselect++;
+ }
+ }
+});