diff --git a/brjs-sdk/sdk/libs/javascript/br-presenter/src/br/presenter/control/selectionfield/JQueryAutoCompleteControl.js b/brjs-sdk/sdk/libs/javascript/br-presenter/src/br/presenter/control/selectionfield/JQueryAutoCompleteControl.js index b22c3853b..2674aae9b 100644 --- a/brjs-sdk/sdk/libs/javascript/br-presenter/src/br/presenter/control/selectionfield/JQueryAutoCompleteControl.js +++ b/brjs-sdk/sdk/libs/javascript/br-presenter/src/br/presenter/control/selectionfield/JQueryAutoCompleteControl.js @@ -47,6 +47,7 @@ function JQueryAutoCompleteControl() { /** @private */ this.m_eElement = {}; + this.m_jQueryInput = null; this.m_bOpenOnFocus = false; this.m_sAppendTo = 'body'; this._viewOpened = false; @@ -77,31 +78,31 @@ JQueryAutoCompleteControl.prototype.setPresentationNode = function(oPresentation throw new InvalidControlModelError('JQueryAutoCompleteControl', 'AutoCompleteSelectionField'); } - this.m_eElement.value = oPresentationNode.value.getValue(); + this.m_eElement.value = oPresentationNode.value.getFormattedValue(); this._valueChangedListener = oPresentationNode.value.addUpdateListener(this, '_valueChanged'); this.m_oPresentationNode = oPresentationNode; }; JQueryAutoCompleteControl.prototype.onViewReady = function() { - var oJqueryInput = jQuery(this.m_eElement); + this.m_jQueryInput = jQuery(this.m_eElement); var self = this; - this._viewOpened = true; - - oJqueryInput.keydown(function(event, oUi) { - if (event.which == 13) // Enter. - { - self._setValue(self.m_oPresentationNode, this, oUi); - event.stopImmediatePropagation(); - event.preventDefault(); - this.blur(); - return false; - } - }); - oJqueryInput.autocomplete({ + this.m_jQueryInput.autocomplete({ + delay: this.delay || 0, minLength: self.m_nMinCharAmount || 0, autoFocus: true, appendTo: self.m_sAppendTo, + open: function() { + // ensure menu is on top of elements + self.m_jQueryInput.autocomplete('widget').css('z-index', 999999); + + self.m_jQueryInput.addClass('autocomplete-menu-open'); + + return false; + }, + close: function() { + self.m_jQueryInput.removeClass('autocomplete-menu-open'); + }, source: function(request, response) { var sTerm = request.term; self.m_oPresentationNode.getAutoCompleteList(sTerm, function(pValues) { @@ -110,9 +111,6 @@ JQueryAutoCompleteControl.prototype.onViewReady = function() { }, select: function(event, oUi) { self._setValue(self.m_oPresentationNode, this, oUi); - // don't propagate this to the keydown (if it's triggered by an enter) - event.stopImmediatePropagation(); - event.preventDefault(); // if the selection is triggered by a click, not by pressing enter, then blur if (self.m_bBlurAfterClick === true) { var ie8LeftClick = !event.button && !event.which; @@ -126,55 +124,91 @@ JQueryAutoCompleteControl.prototype.onViewReady = function() { } }); + this._onDocumentFocus = this._onDocumentFocus.bind(this); + this.m_jQueryInput.on('focus', this._onDocumentFocus); + + this._onScroll = this._onScroll.bind(this); + jQuery( document.body ).on('mousewheel wheel', this._onScroll); + + this._viewOpened = true; +}; + +JQueryAutoCompleteControl.prototype._onScroll = function(wheelEvent) { + var isEventTargetChildOfAutoComplete = this.m_jQueryInput.autocomplete('widget')[0].contains(wheelEvent.target); + + if( isEventTargetChildOfAutoComplete === false ) { + this.m_jQueryInput.autocomplete('close'); + } +}; + +JQueryAutoCompleteControl.prototype._onDocumentFocus = function() { + this.m_jQueryInput.select(); + if (this.m_bOpenOnFocus) { - oJqueryInput.focus(function(e) { - jQuery(this).autocomplete('search', oJqueryInput.val() || ''); - }); + this.m_jQueryInput.autocomplete('search', this.m_jQueryInput.val() || ''); } }; JQueryAutoCompleteControl.prototype._setValue = function(oPresentationNode, oInput, oUi) { - if (oPresentationNode.isValidOption(oInput.value)) { - oPresentationNode.value.setValue(oInput.value); - this.m_eElement.value = oInput.value; - } else if (oUi) { - oPresentationNode.value.setValue(oUi.item.value); - this.m_eElement.value = oUi.item.value; + var isValidOption = oPresentationNode.isValidOption( oInput.value ); + + if ( isValidOption || oUi ) { + var presentationNodeValue = isValidOption ? oInput.value : oUi.item.value; + + oPresentationNode.value.setValue( presentationNodeValue ); + this.m_eElement.value = ( this.clearTextAfterValidInput ? '' : oPresentationNode.value.getFormattedValue()); } }; JQueryAutoCompleteControl.prototype._valueChanged = function() { - this.m_eElement.value = this.m_oPresentationNode.value.getValue(); + this.m_eElement.value = this.m_oPresentationNode.value.getFormattedValue(); }; /** * @private */ JQueryAutoCompleteControl.prototype.setOptions = function(mOptions) { - if (mOptions && mOptions.openOnFocus !== undefined && mOptions.openOnFocus !== 'false') { + mOptions = mOptions || {}; + + if (mOptions.openOnFocus !== undefined && mOptions.openOnFocus !== 'false') { this.m_bOpenOnFocus = true; } - if (mOptions && mOptions.appendTo !== undefined) { + if (mOptions.appendTo !== undefined) { this.m_sAppendTo = mOptions.appendTo; } - if (mOptions && mOptions.minCharAmount !== undefined) { + if (mOptions.minCharAmount !== undefined) { this.m_nMinCharAmount = mOptions.minCharAmount; } - if ( mOptions && mOptions.blurAfterClick !== undefined) { + if (mOptions.blurAfterClick !== undefined) { this.m_bBlurAfterClick = mOptions.blurAfterClick; } + if (mOptions.delay !== undefined) { + this.delay = mOptions.delay; + } + if (mOptions.clearTextAfterValidInput !== undefined) { + this.clearTextAfterValidInput = mOptions.clearTextAfterValidInput; + } }; /** * Destroy created listeners and jQuery autocomplete plugin */ JQueryAutoCompleteControl.prototype.destroy = function() { + // if onOpen is never called the control wouldn't be initialised, hence we must guard against that + if(this._viewOpened) { + this.m_jQueryInput.off('focus', this._onDocumentFocus); + jQuery( document.body ).off('mousewheel wheel', this._onScroll); + this.m_jQueryInput.autocomplete('destroy'); + this.m_jQueryInput.off(); + } + if (this._valueChangedListener) { this.m_oPresentationNode.value.removeListener(this._valueChangedListener); } - if (this._viewOpened === true) { - jQuery(this.m_eElement).autocomplete('destroy').off(); - } -} + + this._valueChangedListener = null; + this.m_jQueryInput = null; + this.m_eElement = null; +}; module.exports = JQueryAutoCompleteControl; diff --git a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/resources/html/test-form.html b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/resources/html/test-form.html index 4049c0c63..a9cbbc572 100644 --- a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/resources/html/test-form.html +++ b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/resources/html/test-form.html @@ -13,7 +13,10 @@ - + + + +
@@ -32,9 +35,10 @@ -
-
-
-
+
+
+
+
+
diff --git a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/src-test/br/presenter/TestPresentationModel.js b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/src-test/br/presenter/TestPresentationModel.js index 808a280df..d7e457ce8 100644 --- a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/src-test/br/presenter/TestPresentationModel.js +++ b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/src-test/br/presenter/TestPresentationModel.js @@ -49,7 +49,7 @@ TestPresentationModel = function() this.labelValueSelectionField.visible.setValue(true); this.jquerySelectionField = new AutoCompleteSelectionField("BB", { - list: ["AA", "BB", "CC"], + list: ["AA", "BB", "CC", "FormatMe"], getList: function(sTerm, fCallback) { var pResult = []; for (var i = 0; i < this.list.length; i++) @@ -65,7 +65,13 @@ TestPresentationModel = function() return ArrayUtility.inArray(this.list, sOption); } }); - + + this.jquerySelectionField.value.addFormatter({ + format: function (value) { + return value === "FormatMe" ? "Formatted" : value; + } + }) + this.multiSelectBox = new MultiSelectionField(["a","b","c", "d"], ["a","c"]); this.multiSelectBox.controlName.setValue("aMultiSelectionField"); diff --git a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/tests/control/selectionfield/JQueryAutoCompleteControlAdapterTest.js b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/tests/control/selectionfield/JQueryAutoCompleteControlAdapterTest.js deleted file mode 100644 index 7e22fe5bf..000000000 --- a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/tests/control/selectionfield/JQueryAutoCompleteControlAdapterTest.js +++ /dev/null @@ -1,67 +0,0 @@ -(function() { - var GwtTestRunner = require("br/test/GwtTestRunner"); - GwtTestRunner.initialize(); - - describe("View to model interactions for JQueryAutoCompleteControlAdapter", function() { - fixtures( require("br/presenter/PresenterFixtureFactory") ); - - it("starts enabled and visible by default", function() { - given("demo.viewOpened = true"); - then("demo.view.(#jqueryAutoCompleteBox).enabled = true"); - and("demo.view.(#jqueryAutoCompleteBox).isVisible = true"); - }); - - it("has the correct initial value", function() { - given("demo.viewOpened = true"); - then("demo.view.(#jqueryAutoCompleteBox).value = 'BB'"); - }); - - it("correctly auto completes a valid input option", function() { - given("demo.viewOpened = true"); - when("demo.model.jquerySelectionField.value => ''"); - and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'A'"); - then("demo.view.(#autocomplete-container li:eq(0)).text = 'AA'"); - }); - - it("shows no options for invalid text", function() { - given("demo.viewOpened = true"); - when("demo.model.jquerySelectionField.value => ''"); - and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'D'"); - then("demo.view.(#autocomplete-container li).count = '0'"); - }); - - it("allows clicking on option to set the value", function() { - given("demo.viewOpened = true"); - when("demo.model.jquerySelectionField.value => ''"); - and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'A'"); - and("demo.view.(#autocomplete-container li:eq(0) a).clicked => true"); - then("demo.model.jquerySelectionField.value = 'AA'"); - and("demo.view.(#jqueryAutoCompleteBox).value = 'AA'"); - }); - - it("does not display any options if minCharAmount is set to 2", function() { - given("demo.viewOpened = true"); - when("demo.model.jquerySelectionField.value => ''"); - and("demo.view.(#jqueryAutoCompleteBox2).typedValue => 'A'"); - then("demo.view.(#autocomplete-container2 li).count = '0'"); - }); - - it("does display options if minCharAmount is set to 2 and typed text is at least 2 chars long", function() { - given("demo.viewOpened = true"); - when("demo.model.jquerySelectionField.value => ''"); - and("demo.view.(#jqueryAutoCompleteBox2).typedValue => 'AA'"); - then("demo.view.(#autocomplete-container2 li:eq(0)).text = 'AA'"); - }); - - it("does blur the input after selection is made by click if blurAfterClick is set to true", function() { - given("demo.viewOpened = true"); - when("demo.model.jquerySelectionField.value => ''"); - and("demo.view.(#jqueryAutoCompleteBox3).typedValue => 'A'"); - and("demo.view.(#autocomplete-container li:eq(0) a).clicked => true"); - then("demo.model.jquerySelectionField.value = 'AA'"); - and("demo.view.(#jqueryAutoCompleteBox3).value = 'AA'"); - and("demo.view.(#jqueryAutoCompleteBox3).focused = false"); - }); - - }); -})(); diff --git a/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/tests/control/selectionfield/JQueryAutoCompleteControlTest.js b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/tests/control/selectionfield/JQueryAutoCompleteControlTest.js new file mode 100644 index 000000000..73acaa24d --- /dev/null +++ b/brjs-sdk/sdk/libs/javascript/br-presenter/test-acceptance/tests/control/selectionfield/JQueryAutoCompleteControlTest.js @@ -0,0 +1,132 @@ +(function() { + var GwtTestRunner = require("br/test/GwtTestRunner"); + GwtTestRunner.initialize(); + + describe("View to model interactions for JQueryAutoCompleteControlAdapter", function() { + fixtures( require("br/presenter/PresenterFixtureFactory") ); + + it("starts enabled and visible by default", function() { + given("demo.viewOpened = true"); + then("demo.view.(#jqueryAutoCompleteBox).enabled = true"); + and("demo.view.(#jqueryAutoCompleteBox).isVisible = true"); + }); + + it("has the correct initial value", function() { + given("demo.viewOpened = true"); + then("demo.view.(#jqueryAutoCompleteBox).value = 'BB'"); + and("demo.view.(#jqueryAutoCompleteBox).doesNotHaveClass = 'autocomplete-menu-open'"); + }); + + it("correctly auto completes a valid input option", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'A'"); + then("demo.view.(#autocomplete-container li:eq(0)).text = 'AA'"); + }); + + it("shows no options for invalid text", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'D'"); + then("demo.view.(#autocomplete-container li).count = '0'"); + }); + + it("allows clicking on option to set the value", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'A'"); + and("demo.view.(#autocomplete-container li:eq(0) a).clicked => true"); + then("demo.model.jquerySelectionField.value = 'AA'"); + and("demo.view.(#jqueryAutoCompleteBox).value = 'AA'"); + }); + + it("sets the formatted value to the textbox", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'F'"); + and("demo.view.(#autocomplete-container li:eq(0) a).clicked => true"); + then("demo.view.(#jqueryAutoCompleteBox).value = 'Formatted'"); + }); + + it("hides the dropdown when the page is scrolled", function() { + given("test.continuesFrom = 'correctly auto completes a valid input option'"); + when("test.page.(div:first).mouseWheel => '20'"); + then("demo.view.(#autocomplete-container li).isVisible = false"); + }); + + it("does not hide the dropdown when the menu is scrolled", function() { + given("test.continuesFrom = 'correctly auto completes a valid input option'"); + when("demo.view.(#autocomplete-container .ui-menu-item:first).mouseWheel => '20'"); + then("demo.view.(#autocomplete-container li).isVisible = true"); + }); + + it("adds a class to the input and when it opens", function() { + given("test.continuesFrom = 'correctly auto completes a valid input option'"); + then("demo.view.(#jqueryAutoCompleteBox).hasClass = 'autocomplete-menu-open'"); + }); + + it("removes a class from the input when it closes", function() { + given("test.continuesFrom = 'hides the dropdown when the page is scrolled'"); + then("demo.view.(#jqueryAutoCompleteBox).doesNotHaveClass = 'autocomplete-menu-open'"); + }); + + it("does not display any options if minCharAmount is set to 2", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox2).typedValue => 'A'"); + then("demo.view.(#autocomplete-container2 li).count = '0'"); + }); + + it("does display options if minCharAmount is set to 2 and typed text is at least 2 chars long", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox2).typedValue => 'AA'"); + then("demo.view.(#autocomplete-container2 li:eq(0)).text = 'AA'"); + }); + + it("does not blur the input after selection if blurAfterClick is not provided", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox).typedValue => 'A'"); + and("demo.view.(#autocomplete-container li:eq(0) a).clicked => true"); + then("demo.model.jquerySelectionField.value = 'AA'"); + and("demo.view.(#jqueryAutoCompleteBox).value = 'AA'"); + and("demo.view.(#jqueryAutoCompleteBox).focused = true"); + }); + + it("does blur the input after selection is made by click if blurAfterClick is set to true", function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox3).typedValue => 'A'"); + and("demo.view.(#autocomplete-container3 li:eq(0) a).clicked => true"); + then("demo.model.jquerySelectionField.value = 'AA'"); + and("demo.view.(#jqueryAutoCompleteBox3).value = 'AA'"); + and("demo.view.(#jqueryAutoCompleteBox3).focused = false"); + }); + + it("does not show the menu immediately when a delay option is given", function() { + given("demo.viewOpened = true"); + and("time.timeMode = 'Manual'"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox4).typedValue => 'A'"); + then("demo.view.(#autocomplete-container4 li).count = '0'"); + }); + + it("shows the menu after some time when a delay option is given", function() { + given("test.continuesFrom = 'does not show the menu immediately when a delay option is given'"); + when("time.passedBy => 200"); + then("demo.view.(#autocomplete-container4 li).isVisible = true"); + and("demo.view.(#autocomplete-container4 li:eq(0)).text = 'AA'"); + }); + + it('clears the textbox after a valid option was chosen', function() { + given("demo.viewOpened = true"); + when("demo.model.jquerySelectionField.value => ''"); + and("demo.view.(#jqueryAutoCompleteBox5).typedValue => 'A'"); + and("demo.view.(#autocomplete-container5 li:eq(0) a).clicked => true"); + then("demo.model.jquerySelectionField.value = 'AA'"); + and("demo.view.(#jqueryAutoCompleteBox5).value = ''"); + }); + + }); +})(); diff --git a/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/ViewFixture.js b/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/ViewFixture.js index 9adc87155..6b63620d7 100644 --- a/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/ViewFixture.js +++ b/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/ViewFixture.js @@ -71,6 +71,7 @@ function ViewFixture(viewSelector) { var MouseOut = require('br/test/viewhandler/MouseOut'); var MouseOver = require('br/test/viewhandler/MouseOver'); var MouseUp = require('br/test/viewhandler/MouseUp'); + var MouseWheel = require('br/test/viewhandler/MouseWheel'); var OnKeyUp = require('br/test/viewhandler/OnKeyUp'); var Options = require('br/test/viewhandler/Options'); var Readonly = require('br/test/viewhandler/Readonly'); @@ -112,6 +113,7 @@ function ViewFixture(viewSelector) { mouseOut: new MouseOut(), mouseOver: new MouseOver(), mouseUp: new MouseUp(), + mouseWheel: new MouseWheel(), onKeyUp: new OnKeyUp(), options: new Options(), readonly: new Readonly(), diff --git a/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/viewhandler/MouseWheel.js b/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/viewhandler/MouseWheel.js new file mode 100644 index 000000000..5092fcc5a --- /dev/null +++ b/brjs-sdk/sdk/libs/javascript/br-test/src/br/test/viewhandler/MouseWheel.js @@ -0,0 +1,39 @@ +'use strict'; + +/** + * @module br/test/viewhandler/MouseWheel + */ + +var br = require('br/Core'); +var Errors = require('br/Errors'); +var ViewFixtureHandler = require('br/test/viewhandler/ViewFixtureHandler'); +var Utils = require('br/test/Utils'); + +/** + * @class + * @alias module:br/test/viewhandler/MouseWheel + * @implements module:br/test/viewhandler/ViewFixtureHandler + * + * @classdesc + * MouseWheel instances of ViewFixtureHandler can be used to trigger mousewheel event for a view element. + * Example usage: + * + *
when("test.page.(#aRealButton).mouseWheel => true");
+ */ +function MouseWheel() { +} +br.implement(MouseWheel, ViewFixtureHandler); + +MouseWheel.prototype.set = function(eElement, mValues) { + var event = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel" + document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel" + "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox + + Utils.fireMouseEvent(eElement, 'wheel', mValues); +}; + +MouseWheel.prototype.get = function(eElement) { + throw new Errors.InvalidTestError("The mouseWheel event cannot be used in a doGiven or doThen"); +}; + +module.exports = MouseWheel;