<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>TextArea Autocomplete</title>
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" data-sap-ui-theme="sap_belize"
data-sap-ui-libs="sap.m" data-sap-ui-bindingSyntax="complex" data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async" data-sap-ui-resourceroots='{
"com.demo.textarea.autocomplete": "./"
}'>
</script>
<script>
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name: "com.demo.textarea.autocomplete"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content">
</body>
</html>
# SAP UI5 TextArea control with suggestion/autocomplete feature
## Introduction
Enables SAP UI5 [TextArea](https://sapui5.hana.ondemand.com/#/api/sap.m.TextArea) control to support Autocomplete/Suggestion feature

## Sample code
## Dependencies
- [jquery-textcomplete](https://github.com/yuku/jquery-textcomplete) plugin
> **_Note_**: Download jquery-textcomplete plugin from this git repo, as it contains some changes(explained below) to support [sap.ui.define](https://sapui5.hana.ondemand.com/#/api/sap.ui/overview) AMD syntax
>
> **_Changes done to jquery-textcomplete plugin file_**:
>
> - Commented lines 2-12
> - Added new line 13
>
> 
## Usage
- Download jquery-textcomplete javascript file from folder _libs/jquery.textcomplete.js_. Call this file from _manifest.json_
- Provide **_id_** for SAP UI5 TextArea XML tag (in this sample _id_ is _'textArea_01'_)

- Call the plugin function on _Textarea_ html element only after rendering of the element is done
## How it works
_jquery-textcomplete_ Plugin contains below methods and will be executed in sequence
- **match**: Supports regular expression or function. When user types anything in the UI5 TextArea element it is continuously checked against the regular expression and triggers search when a match is found
- **search**: This takes two input parameters explained below
- _query_: Regular expression match found in the method _match_ (previous method) of plugin
- _fnCallback_: Callback method provided by plugin. This method needs to be called after data retrieval is done. _eg_: Get data from backend/model/ajax call etc, then filter data using _query_ and finally call the callback method
- **template**: Template to display results. [HTML list](https://www.w3schools.com/html/html_lists.asp) is displayed to the user. Each record from the method _search_ is passed as input to this method and return the template to be displayed
- **replace**: If user selects an entry from the displayed suggestion list, then while placing the text in _UI5 TextArea_ element the result can be modified. _eg_: In this current sample code countries are shown, when user selects a country from the list it is converted to uppercase and placed in the _UI5 TextArea_ element
{
"_version": "1.8.0",
"sap.app": {
"id": "com.demo.textarea.autocomplete",
"type": "application",
"i18n": "i18n/i18n.properties",
"title": "TextArea Autocomplete",
"description": "TextArea Autocomplete",
"applicationVersion": {
"version": "1.0.0"
}
},
"sap.ui": {
"technology": "UI5",
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"rootView": {
"viewName": "com.demo.textarea.autocomplete.view.App",
"type": "XML",
"async": true,
"id": "app"
},
"dependencies": {
"minUI5Version": "1.30",
"libs": {
"sap.m": {},
"sap.viz": {}
}
},
"models": {
"countriesData": {
"type": "sap.ui.model.json.JSONModel",
"uri": "model/countries.json"
}
},
"resources": {
"js": [{ "uri": "libs/jquery.textcomplete.js" }],
"css": [{ "uri": "styles/autocomplete.css" }]
}
}
}
sap.ui.define(
["sap/ui/core/UIComponent", "sap/ui/model/json/JSONModel"],
function(UIComponent, JSONModel) {
"use strict";
return UIComponent.extend("com.demo.textarea.autocomplete.Component", {
metadata: {
manifest: "json"
},
init: function() {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
}
});
}
);
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
"use strict";
return Controller.extend("com.demo.textarea.autocomplete.controller.App", {
onInit: function() {
var that = this;
this.getView().byId("textArea_01").onAfterRendering = function() {
// call autocomplete plugin function after rendering of textarea is completed
that.enableAutoComplete();
};
},
onBeforeRendering: function() {},
onAfterRendering: function() {},
enableAutoComplete: function() {
var that = this;
var oControl = this.getView().byId("textArea_01");
// get textarea htmltag from UI5 control
var jQueryTextArea = jQuery("#" + oControl.getId()).find("textarea");
jQueryTextArea.textcomplete([
{
// #1 - Regular experession used to trigger search
match: /(\b(\w+))$/, // --> triggers search for every char typed
// #2 - Function called at every new key stroke
search: function(query, fnCallback) {
var pData = Promise.resolve(
// jQuery.ajax({
// url: "./model/countries.json",
// method: "GET"
// })
that
.getOwnerComponent()
.getModel("countriesData")
.getData()
);
pData.then(function(oResult) {
fnCallback(
oResult.data.filter(function(oRecord) {
// filter results based on query
return oRecord.name
.toUpperCase()
.startsWith(query.toUpperCase());
})
);
});
},
// #3 - Template used to display each result (also supports HTML tags)
template: function(hit) {
// Returns the highlighted version of the name attribute
return hit.name;
},
// #4 - Template used to display the selected result in the textarea
replace: function(hit) {
return hit.name.toUpperCase();
}
}
]);
}
});
});
(function(factory) {
// if (typeof define === "function" && define.amd) {
// // AMD. Register as an anonymous module.
// define(["jquery"], factory);
// } else if (typeof module === "object" && module.exports) {
// var $ = require("jquery");
// module.exports = factory($);
// } else {
// // Browser globals
// factory(jQuery);
// }
factory(jQuery); // Added for UI5
})(function(jQuery) {
/*!
* jQuery.textcomplete
*
* Repository: https://github.com/yuku-t/jquery-textcomplete
* License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE)
* Author: Yuku Takahashi
*/
if (typeof jQuery === "undefined") {
throw new Error("jQuery.textcomplete requires jQuery");
}
+(function($) {
"use strict";
var warn = function(message) {
if (console.warn) {
console.warn(message);
}
};
var id = 1;
$.fn.textcomplete = function(strategies, option) {
var args = Array.prototype.slice.call(arguments);
return this.each(function() {
var self = this;
var $this = $(this);
var completer = $this.data("textComplete");
if (!completer) {
option || (option = {});
option._oid = id++; // unique object id
completer = new $.fn.textcomplete.Completer(this, option);
$this.data("textComplete", completer);
}
if (typeof strategies === "string") {
if (!completer) return;
args.shift();
completer[strategies].apply(completer, args);
if (strategies === "destroy") {
$this.removeData("textComplete");
}
} else {
// For backward compatibility.
// TODO: Remove at v0.4
$.each(strategies, function(obj) {
$.each(["header", "footer", "placement", "maxCount"], function(
name
) {
if (obj[name]) {
completer.option[name] = obj[name];
warn(name + "as a strategy param is deprecated. Use option.");
delete obj[name];
}
});
});
completer.register(
$.fn.textcomplete.Strategy.parse(strategies, {
el: self,
$el: $this
})
);
}
});
};
})(jQuery);
+(function($) {
"use strict";
// Exclusive execution control utility.
//
// func - The function to be locked. It is executed with a function named
// `free` as the first argument. Once it is called, additional
// execution are ignored until the free is invoked. Then the last
// ignored execution will be replayed immediately.
//
// Examples
//
// var lockedFunc = lock(function (free) {
// setTimeout(function { free(); }, 1000); // It will be free in 1 sec.
// console.log('Hello, world');
// });
// lockedFunc(); // => 'Hello, world'
// lockedFunc(); // none
// lockedFunc(); // none
// // 1 sec past then
// // => 'Hello, world'
// lockedFunc(); // => 'Hello, world'
// lockedFunc(); // none
//
// Returns a wrapped function.
var lock = function(func) {
var locked, queuedArgsToReplay;
return function() {
// Convert arguments into a real array.
var args = Array.prototype.slice.call(arguments);
if (locked) {
// Keep a copy of this argument list to replay later.
// OK to overwrite a previous value because we only replay
// the last one.
queuedArgsToReplay = args;
return;
}
locked = true;
var self = this;
args.unshift(function replayOrFree() {
if (queuedArgsToReplay) {
// Other request(s) arrived while we were locked.
// Now that the lock is becoming available, replay
// the latest such request, then call back here to
// unlock (or replay another request that arrived
// while this one was in flight).
var replayArgs = queuedArgsToReplay;
queuedArgsToReplay = undefined;
replayArgs.unshift(replayOrFree);
func.apply(self, replayArgs);
} else {
locked = false;
}
});
func.apply(this, args);
};
};
var isString = function(obj) {
return Object.prototype.toString.call(obj) === "[object String]";
};
var uniqueId = 0;
var initializedEditors = [];
function Completer(element, option) {
this.$el = $(element);
this.id = "textcomplete" + uniqueId++;
this.strategies = [];
this.views = [];
this.option = $.extend({}, Completer.defaults, option);
if (
!this.$el.is("input[type=text]") &&
!this.$el.is("input[type=search]") &&
!this.$el.is("textarea") &&
!element.isContentEditable &&
element.contentEditable != "true"
) {
throw new Error(
"textcomplete must be called on a Textarea or a ContentEditable."
);
}
// use ownerDocument to fix iframe / IE issues
if (element === element.ownerDocument.activeElement) {
// element has already been focused. Initialize view objects immediately.
this.initialize();
} else {
// Initialize view objects lazily.
var self = this;
this.$el.one("focus." + this.id, function() {
self.initialize();
});
// Special handling for CKEditor: lazy init on instance load
if (
(!this.option.adapter || this.option.adapter == "CKEditor") &&
typeof CKEDITOR != "undefined" &&
this.$el.is("textarea")
) {
CKEDITOR.on("instanceReady", function(event) {
//For multiple ckeditors on one page: this needs to be executed each time a ckeditor-instance is ready.
if ($.inArray(event.editor.id, initializedEditors) == -1) {
//For multiple ckeditors on one page: focus-eventhandler should only be added once for every editor.
initializedEditors.push(event.editor.id);
event.editor.on("focus", function(event2) {
//replace the element with the Iframe element and flag it as CKEditor
self.$el = $(event.editor.editable().$);
if (!self.option.adapter) {
self.option.adapter = $.fn.textcomplete["CKEditor"];
}
self.option.ckeditor_instance = event.editor; //For multiple ckeditors on one page: in the old code this was not executed when adapter was alread set. So we were ALWAYS working with the FIRST instance.
self.initialize();
});
}
});
}
}
}
Completer.defaults = {
appendTo: "body",
className: "", // deprecated option
dropdownClassName: "dropdown-menu textcomplete-dropdown",
maxCount: 10,
zIndex: "100",
rightEdgeOffset: 30
};
$.extend(Completer.prototype, {
// Public properties
// -----------------
id: null,
option: null,
strategies: null,
adapter: null,
dropdown: null,
$el: null,
$iframe: null,
// Public methods
// --------------
initialize: function() {
var element = this.$el.get(0);
// check if we are in an iframe
// we need to alter positioning logic if using an iframe
if (
this.$el.prop("ownerDocument") !== document &&
window.frames.length
) {
for (
var iframeIndex = 0;
iframeIndex < window.frames.length;
iframeIndex++
) {
if (
this.$el.prop("ownerDocument") ===
window.frames[iframeIndex].document
) {
this.$iframe = $(window.frames[iframeIndex].frameElement);
break;
}
}
}
// Initialize view objects.
this.dropdown = new $.fn.textcomplete.Dropdown(
element,
this,
this.option
);
var Adapter, viewName;
if (this.option.adapter) {
Adapter = this.option.adapter;
} else {
if (
this.$el.is("textarea") ||
this.$el.is("input[type=text]") ||
this.$el.is("input[type=search]")
) {
viewName =
typeof element.selectionEnd === "number"
? "Textarea"
: "IETextarea";
} else {
viewName = "ContentEditable";
}
Adapter = $.fn.textcomplete[viewName];
}
this.adapter = new Adapter(element, this, this.option);
},
destroy: function() {
this.$el.off("." + this.id);
if (this.adapter) {
this.adapter.destroy();
}
if (this.dropdown) {
this.dropdown.destroy();
}
this.$el = this.adapter = this.dropdown = null;
},
deactivate: function() {
if (this.dropdown) {
this.dropdown.deactivate();
}
},
// Invoke textcomplete.
trigger: function(text, skipUnchangedTerm) {
if (!this.dropdown) {
this.initialize();
}
text != null || (text = this.adapter.getTextFromHeadToCaret());
var searchQuery = this._extractSearchQuery(text);
if (searchQuery.length) {
var term = searchQuery[1];
// Ignore shift-key, ctrl-key and so on.
if (skipUnchangedTerm && this._term === term && term !== "") {
return;
}
this._term = term;
this._search.apply(this, searchQuery);
} else {
this._term = null;
this.dropdown.deactivate();
}
},
fire: function(eventName) {
var args = Array.prototype.slice.call(arguments, 1);
this.$el.trigger(eventName, args);
return this;
},
register: function(strategies) {
Array.prototype.push.apply(this.strategies, strategies);
},
// Insert the value into adapter view. It is called when the dropdown is clicked
// or selected.
//
// value - The selected element of the array callbacked from search func.
// strategy - The Strategy object.
// e - Click or keydown event object.
select: function(value, strategy, e) {
this._term = null;
this.adapter.select(value, strategy, e);
this.fire("change").fire("textComplete:select", value, strategy);
this.adapter.focus();
},
// Private properties
// ------------------
_clearAtNext: true,
_term: null,
// Private methods
// ---------------
// Parse the given text and extract the first matching strategy.
//
// Returns an array including the strategy, the query term and the match
// object if the text matches an strategy; otherwise returns an empty array.
_extractSearchQuery: function(text) {
for (var i = 0; i < this.strategies.length; i++) {
var strategy = this.strategies[i];
var context = strategy.context(text);
if (context || context === "") {
var matchRegexp = $.isFunction(strategy.match)
? strategy.match(text)
: strategy.match;
if (isString(context)) {
text = context;
}
var match = text.match(matchRegexp);
if (match) {
return [strategy, match[strategy.index], match];
}
}
}
return [];
},
// Call the search method of selected strategy..
_search: lock(function(free, strategy, term, match) {
var self = this;
strategy.search(
term,
function(data, stillSearching) {
if (!self.dropdown.shown) {
self.dropdown.activate();
}
if (self._clearAtNext) {
// The first callback in the current lock.
self.dropdown.clear();
self._clearAtNext = false;
}
self.dropdown.setPosition(self.adapter.getCaretPosition());
self.dropdown.render(self._zip(data, strategy, term));
if (!stillSearching) {
// The last callback in the current lock.
free();
self._clearAtNext = true; // Call dropdown.clear at the next time.
}
},
match
);
}),
// Build a parameter for Dropdown#render.
//
// Examples
//
// this._zip(['a', 'b'], 's');
// //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }]
_zip: function(data, strategy, term) {
return $.map(data, function(value) {
return { value: value, strategy: strategy, term: term };
});
}
});
$.fn.textcomplete.Completer = Completer;
})(jQuery);
+(function($) {
"use strict";
var $window = $(window);
var include = function(zippedData, datum) {
var i, elem;
var idProperty = datum.strategy.idProperty;
for (i = 0; i < zippedData.length; i++) {
elem = zippedData[i];
if (elem.strategy !== datum.strategy) continue;
if (idProperty) {
if (elem.value[idProperty] === datum.value[idProperty]) return true;
} else {
if (elem.value === datum.value) return true;
}
}
return false;
};
var dropdownViews = {};
$(document).on("click", function(e) {
var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown;
$.each(dropdownViews, function(key, view) {
if (key !== id) {
view.deactivate();
}
});
});
var commands = {
SKIP_DEFAULT: 0,
KEY_UP: 1,
KEY_DOWN: 2,
KEY_ENTER: 3,
KEY_PAGEUP: 4,
KEY_PAGEDOWN: 5,
KEY_ESCAPE: 6
};
// Dropdown view
// =============
// Construct Dropdown object.
//
// element - Textarea or contenteditable element.
function Dropdown(element, completer, option) {
this.$el = Dropdown.createElement(option);
this.completer = completer;
this.id = completer.id + "dropdown";
this._data = []; // zipped data.
this.$inputEl = $(element);
this.option = option;
// Override setPosition method.
if (option.listPosition) {
this.setPosition = option.listPosition;
}
if (option.height) {
this.$el.height(option.height);
}
var self = this;
$.each(
[
"maxCount",
"placement",
"footer",
"header",
"noResultsMessage",
"className"
],
function(_i, name) {
if (option[name] != null) {
self[name] = option[name];
}
}
);
this._bindEvents(element);
dropdownViews[this.id] = this;
}
$.extend(Dropdown, {
// Class methods
// -------------
createElement: function(option) {
var $parent = option.appendTo;
if (!($parent instanceof $)) {
$parent = $($parent);
}
var $el = $("<ul></ul>")
.addClass(option.dropdownClassName)
.attr("id", "textcomplete-dropdown-" + option._oid)
.css({
display: "none",
left: 0,
position: "absolute",
zIndex: option.zIndex
})
.appendTo($parent);
return $el;
}
});
$.extend(Dropdown.prototype, {
// Public properties
// -----------------
$el: null, // jQuery object of ul.dropdown-menu element.
$inputEl: null, // jQuery object of target textarea.
completer: null,
footer: null,
header: null,
id: null,
maxCount: null,
placement: "",
shown: false,
data: [], // Shown zipped data.
className: "",
// Public methods
// --------------
destroy: function() {
// Don't remove $el because it may be shared by several textcompletes.
this.deactivate();
this.$el.off("." + this.id);
this.$inputEl.off("." + this.id);
this.clear();
this.$el.remove();
this.$el = this.$inputEl = this.completer = null;
delete dropdownViews[this.id];
},
render: function(zippedData) {
var contentsHtml = this._buildContents(zippedData);
var unzippedData = $.map(zippedData, function(d) {
return d.value;
});
if (zippedData.length) {
var strategy = zippedData[0].strategy;
if (strategy.id) {
this.$el.attr("data-strategy", strategy.id);
} else {
this.$el.removeAttr("data-strategy");
}
this._renderHeader(unzippedData);
this._renderFooter(unzippedData);
if (contentsHtml) {
this._renderContents(contentsHtml);
this._fitToBottom();
this._fitToRight();
this._activateIndexedItem();
}
this._setScroll();
} else if (this.noResultsMessage) {
this._renderNoResultsMessage(unzippedData);
} else if (this.shown) {
this.deactivate();
}
},
setPosition: function(pos) {
// Make the dropdown fixed if the input is also fixed
// This can't be done during init, as textcomplete may be used on multiple elements on the same page
// Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed
var position = "absolute";
// Check if input or one of its parents has positioning we need to care about
this.$inputEl.add(this.$inputEl.parents()).each(function() {
if ($(this).css("position") === "absolute")
// The element has absolute positioning, so it's all OK
return false;
if ($(this).css("position") === "fixed") {
pos.top -= $window.scrollTop();
pos.left -= $window.scrollLeft();
position = "fixed";
return false;
}
});
this.$el.css(this._applyPlacement(pos));
this.$el.css({ position: position }); // Update positioning
return this;
},
clear: function() {
this.$el.html("");
this.data = [];
this._index = 0;
this._$header = this._$footer = this._$noResultsMessage = null;
},
activate: function() {
if (!this.shown) {
this.clear();
this.$el.show();
if (this.className) {
this.$el.addClass(this.className);
}
this.completer.fire("textComplete:show");
this.shown = true;
}
return this;
},
deactivate: function() {
if (this.shown) {
this.$el.hide();
if (this.className) {
this.$el.removeClass(this.className);
}
this.completer.fire("textComplete:hide");
this.shown = false;
}
return this;
},
isUp: function(e) {
return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P
},
isDown: function(e) {
return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N
},
isEnter: function(e) {
var modifiers = e.ctrlKey || e.altKey || e.metaKey || e.shiftKey;
return (
!modifiers &&
(e.keyCode === 13 ||
e.keyCode === 9 ||
(this.option.completeOnSpace === true && e.keyCode === 32))
); // ENTER, TAB
},
isPageup: function(e) {
return e.keyCode === 33; // PAGEUP
},
isPagedown: function(e) {
return e.keyCode === 34; // PAGEDOWN
},
isEscape: function(e) {
return e.keyCode === 27; // ESCAPE
},
// Private properties
// ------------------
_data: null, // Currently shown zipped data.
_index: null,
_$header: null,
_$noResultsMessage: null,
_$footer: null,
// Private methods
// ---------------
_bindEvents: function() {
this.$el.on(
"mousedown." + this.id,
".textcomplete-item",
$.proxy(this._onClick, this)
);
this.$el.on(
"touchstart." + this.id,
".textcomplete-item",
$.proxy(this._onClick, this)
);
this.$el.on(
"mouseover." + this.id,
".textcomplete-item",
$.proxy(this._onMouseover, this)
);
this.$inputEl.on("keydown." + this.id, $.proxy(this._onKeydown, this));
},
_onClick: function(e) {
var $el = $(e.target);
e.preventDefault();
e.originalEvent.keepTextCompleteDropdown = this.id;
if (!$el.hasClass("textcomplete-item")) {
$el = $el.closest(".textcomplete-item");
}
var datum = this.data[parseInt($el.data("index"), 10)];
this.completer.select(datum.value, datum.strategy, e);
var self = this;
// Deactive at next tick to allow other event handlers to know whether
// the dropdown has been shown or not.
setTimeout(function() {
self.deactivate();
if (e.type === "touchstart") {
self.$inputEl.focus();
}
}, 0);
},
// Activate hovered item.
_onMouseover: function(e) {
var $el = $(e.target);
e.preventDefault();
if (!$el.hasClass("textcomplete-item")) {
$el = $el.closest(".textcomplete-item");
}
this._index = parseInt($el.data("index"), 10);
this._activateIndexedItem();
},
_onKeydown: function(e) {
if (!this.shown) {
return;
}
var command;
if ($.isFunction(this.option.onKeydown)) {
command = this.option.onKeydown(e, commands);
}
if (command == null) {
command = this._defaultKeydown(e);
}
switch (command) {
case commands.KEY_UP:
e.preventDefault();
this._up();
break;
case commands.KEY_DOWN:
e.preventDefault();
this._down();
break;
case commands.KEY_ENTER:
e.preventDefault();
this._enter(e);
break;
case commands.KEY_PAGEUP:
e.preventDefault();
this._pageup();
break;
case commands.KEY_PAGEDOWN:
e.preventDefault();
this._pagedown();
break;
case commands.KEY_ESCAPE:
e.preventDefault();
this.deactivate();
break;
}
},
_defaultKeydown: function(e) {
if (this.isUp(e)) {
return commands.KEY_UP;
} else if (this.isDown(e)) {
return commands.KEY_DOWN;
} else if (this.isEnter(e)) {
return commands.KEY_ENTER;
} else if (this.isPageup(e)) {
return commands.KEY_PAGEUP;
} else if (this.isPagedown(e)) {
return commands.KEY_PAGEDOWN;
} else if (this.isEscape(e)) {
return commands.KEY_ESCAPE;
}
},
_up: function() {
if (this._index === 0) {
this._index = this.data.length - 1;
} else {
this._index -= 1;
}
this._activateIndexedItem();
this._setScroll();
},
_down: function() {
if (this._index === this.data.length - 1) {
this._index = 0;
} else {
this._index += 1;
}
this._activateIndexedItem();
this._setScroll();
},
_enter: function(e) {
var datum = this.data[
parseInt(this._getActiveElement().data("index"), 10)
];
this.completer.select(datum.value, datum.strategy, e);
this.deactivate();
},
_pageup: function() {
var target = 0;
var threshold =
this._getActiveElement().position().top - this.$el.innerHeight();
this.$el.children().each(function(i) {
if ($(this).position().top + $(this).outerHeight() > threshold) {
target = i;
return false;
}
});
this._index = target;
this._activateIndexedItem();
this._setScroll();
},
_pagedown: function() {
var target = this.data.length - 1;
var threshold =
this._getActiveElement().position().top + this.$el.innerHeight();
this.$el.children().each(function(i) {
if ($(this).position().top > threshold) {
target = i;
return false;
}
});
this._index = target;
this._activateIndexedItem();
this._setScroll();
},
_activateIndexedItem: function() {
this.$el.find(".textcomplete-item.active").removeClass("active");
this._getActiveElement().addClass("active");
},
_getActiveElement: function() {
return this.$el.children(".textcomplete-item:nth(" + this._index + ")");
},
_setScroll: function() {
var $activeEl = this._getActiveElement();
var itemTop = $activeEl.position().top;
var itemHeight = $activeEl.outerHeight();
var visibleHeight = this.$el.innerHeight();
var visibleTop = this.$el.scrollTop();
if (
this._index === 0 ||
this._index == this.data.length - 1 ||
itemTop < 0
) {
this.$el.scrollTop(itemTop + visibleTop);
} else if (itemTop + itemHeight > visibleHeight) {
this.$el.scrollTop(itemTop + itemHeight + visibleTop - visibleHeight);
}
},
_buildContents: function(zippedData) {
var datum, i, index;
var html = "";
for (i = 0; i < zippedData.length; i++) {
if (this.data.length === this.maxCount) break;
datum = zippedData[i];
if (include(this.data, datum)) {
continue;
}
index = this.data.length;
this.data.push(datum);
html +=
'<li class="textcomplete-item" data-index="' + index + '"><a>';
html += datum.strategy.template(datum.value, datum.term);
html += "</a></li>";
}
return html;
},
_renderHeader: function(unzippedData) {
if (this.header) {
if (!this._$header) {
this._$header = $(
'<li class="textcomplete-header"></li>'
).prependTo(this.$el);
}
var html = $.isFunction(this.header)
? this.header(unzippedData)
: this.header;
this._$header.html(html);
}
},
_renderFooter: function(unzippedData) {
if (this.footer) {
if (!this._$footer) {
this._$footer = $('<li class="textcomplete-footer"></li>').appendTo(
this.$el
);
}
var html = $.isFunction(this.footer)
? this.footer(unzippedData)
: this.footer;
this._$footer.html(html);
}
},
_renderNoResultsMessage: function(unzippedData) {
if (this.noResultsMessage) {
if (!this._$noResultsMessage) {
this._$noResultsMessage = $(
'<li class="textcomplete-no-results-message"></li>'
).appendTo(this.$el);
}
var html = $.isFunction(this.noResultsMessage)
? this.noResultsMessage(unzippedData)
: this.noResultsMessage;
this._$noResultsMessage.html(html);
}
},
_renderContents: function(html) {
if (this._$footer) {
this._$footer.before(html);
} else {
this.$el.append(html);
}
},
_fitToBottom: function() {
var windowScrollBottom = $window.scrollTop() + $window.height();
var height = this.$el.height();
if (this.$el.position().top + height > windowScrollBottom) {
// only do this if we are not in an iframe
if (!this.completer.$iframe) {
this.$el.offset({ top: windowScrollBottom - height });
}
}
},
_fitToRight: function() {
// We don't know how wide our content is until the browser positions us, and at that point it clips us
// to the document width so we don't know if we would have overrun it. As a heuristic to avoid that clipping
// (which makes our elements wrap onto the next line and corrupt the next item), if we're close to the right
// edge, move left. We don't know how far to move left, so just keep nudging a bit.
var tolerance = this.option.rightEdgeOffset; // pixels. Make wider than vertical scrollbar because we might not be able to use that space.
var lastOffset = this.$el.offset().left,
offset;
var width = this.$el.width();
var maxLeft = $window.width() - tolerance;
while (lastOffset + width > maxLeft) {
this.$el.offset({ left: lastOffset - tolerance });
offset = this.$el.offset().left;
if (offset >= lastOffset) {
break;
}
lastOffset = offset;
}
},
_applyPlacement: function(position) {
// If the 'placement' option set to 'top', move the position above the element.
if (this.placement.indexOf("top") !== -1) {
// Overwrite the position object to set the 'bottom' property instead of the top.
position = {
top: "auto",
bottom:
this.$el.parent().height() - position.top + position.lineHeight,
left: position.left
};
} else {
position.bottom = "auto";
delete position.lineHeight;
}
if (this.placement.indexOf("absleft") !== -1) {
position.left = 0;
} else if (this.placement.indexOf("absright") !== -1) {
position.right = 0;
position.left = "auto";
}
return position;
}
});
$.fn.textcomplete.Dropdown = Dropdown;
$.extend($.fn.textcomplete, commands);
})(jQuery);
+(function($) {
"use strict";
// Memoize a search function.
var memoize = function(func) {
var memo = {};
return function(term, callback) {
if (memo[term]) {
callback(memo[term]);
} else {
func.call(this, term, function(data) {
memo[term] = (memo[term] || []).concat(data);
callback.apply(null, arguments);
});
}
};
};
function Strategy(options) {
$.extend(this, options);
if (this.cache) {
this.search = memoize(this.search);
}
}
Strategy.parse = function(strategiesArray, params) {
return $.map(strategiesArray, function(strategy) {
var strategyObj = new Strategy(strategy);
strategyObj.el = params.el;
strategyObj.$el = params.$el;
return strategyObj;
});
};
$.extend(Strategy.prototype, {
// Public properties
// -----------------
// Required
match: null,
replace: null,
search: null,
// Optional
id: null,
cache: false,
context: function() {
return true;
},
index: 2,
template: function(obj) {
return obj;
},
idProperty: null
});
$.fn.textcomplete.Strategy = Strategy;
})(jQuery);
+(function($) {
"use strict";
var now =
Date.now ||
function() {
return new Date().getTime();
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// `wait` msec.
//
// This utility function was originally implemented at Underscore.js.
var debounce = function(func, wait) {
var timeout, args, context, timestamp, result;
var later = function() {
var last = now() - timestamp;
if (last < wait) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
result = func.apply(context, args);
context = args = null;
}
};
return function() {
context = this;
args = arguments;
timestamp = now();
if (!timeout) {
timeout = setTimeout(later, wait);
}
return result;
};
};
function Adapter() {}
$.extend(Adapter.prototype, {
// Public properties
// -----------------
id: null, // Identity.
completer: null, // Completer object which creates it.
el: null, // Textarea element.
$el: null, // jQuery object of the textarea.
option: null,
// Public methods
// --------------
initialize: function(element, completer, option) {
this.el = element;
this.$el = $(element);
this.id = completer.id + this.constructor.name;
this.completer = completer;
this.option = option;
if (this.option.debounce) {
this._onKeyup = debounce(this._onKeyup, this.option.debounce);
}
this._bindEvents();
},
destroy: function() {
this.$el.off("." + this.id); // Remove all event handlers.
this.$el = this.el = this.completer = null;
},
// Update the element with the given value and strategy.
//
// value - The selected object. It is one of the item of the array
// which was callbacked from the search function.
// strategy - The Strategy associated with the selected value.
select: function(/* value, strategy */) {
throw new Error("Not implemented");
},
// Returns the caret's relative coordinates from body's left top corner.
getCaretPosition: function() {
var position = this._getCaretRelativePosition();
var offset = this.$el.offset();
// Calculate the left top corner of `this.option.appendTo` element.
var $parent = this.option.appendTo;
if ($parent) {
if (!($parent instanceof $)) {
$parent = $($parent);
}
var parentOffset = $parent.offsetParent().offset();
offset.top -= parentOffset.top;
offset.left -= parentOffset.left;
}
position.top += offset.top;
position.left += offset.left;
return position;
},
// Focus on the element.
focus: function() {
this.$el.focus();
},
// Private methods
// ---------------
_bindEvents: function() {
this.$el.on("keyup." + this.id, $.proxy(this._onKeyup, this));
},
_onKeyup: function(e) {
if (this._skipSearch(e)) {
return;
}
this.completer.trigger(this.getTextFromHeadToCaret(), true);
},
// Suppress searching if it returns true.
_skipSearch: function(clickEvent) {
switch (clickEvent.keyCode) {
case 9: // TAB
case 13: // ENTER
case 16: // SHIFT
case 17: // CTRL
case 18: // ALT
case 33: // PAGEUP
case 34: // PAGEDOWN
case 40: // DOWN
case 38: // UP
case 27: // ESC
return true;
}
if (clickEvent.ctrlKey)
switch (clickEvent.keyCode) {
case 78: // Ctrl-N
case 80: // Ctrl-P
return true;
}
}
});
$.fn.textcomplete.Adapter = Adapter;
})(jQuery);
+(function($) {
"use strict";
// Textarea adapter
// ================
//
// Managing a textarea. It doesn't know a Dropdown.
function Textarea(element, completer, option) {
this.initialize(element, completer, option);
}
$.extend(Textarea.prototype, $.fn.textcomplete.Adapter.prototype, {
// Public methods
// --------------
// Update the textarea with the given value and strategy.
select: function(value, strategy, e) {
var pre = this.getTextFromHeadToCaret();
var post = this.el.value.substring(this.el.selectionEnd);
var newSubstr = strategy.replace(value, e);
var regExp;
if (typeof newSubstr !== "undefined") {
if ($.isArray(newSubstr)) {
post = newSubstr[1] + post;
newSubstr = newSubstr[0];
}
regExp = $.isFunction(strategy.match)
? strategy.match(pre)
: strategy.match;
pre = pre.replace(regExp, newSubstr);
this.$el.val(pre + post);
this.el.selectionStart = this.el.selectionEnd = pre.length;
}
},
getTextFromHeadToCaret: function() {
return this.el.value.substring(0, this.el.selectionEnd);
},
// Private methods
// ---------------
_getCaretRelativePosition: function() {
var p = $.fn.textcomplete.getCaretCoordinates(
this.el,
this.el.selectionStart
);
return {
top: p.top + this._calculateLineHeight() - this.$el.scrollTop(),
left: p.left - this.$el.scrollLeft(),
lineHeight: this._calculateLineHeight()
};
},
_calculateLineHeight: function() {
var lineHeight = parseInt(this.$el.css("line-height"), 10);
if (isNaN(lineHeight)) {
// http://stackoverflow.com/a/4515470/1297336
var parentNode = this.el.parentNode;
var temp = document.createElement(this.el.nodeName);
var style = this.el.style;
temp.setAttribute(
"style",
"margin:0px;padding:0px;font-family:" +
style.fontFamily +
";font-size:" +
style.fontSize
);
temp.innerHTML = "test";
parentNode.appendChild(temp);
lineHeight = temp.clientHeight;
parentNode.removeChild(temp);
}
return lineHeight;
}
});
$.fn.textcomplete.Textarea = Textarea;
})(jQuery);
+(function($) {
"use strict";
var sentinelChar = "吶";
function IETextarea(element, completer, option) {
this.initialize(element, completer, option);
$("<span>" + sentinelChar + "</span>")
.css({
position: "absolute",
top: -9999,
left: -9999
})
.insertBefore(element);
}
$.extend(IETextarea.prototype, $.fn.textcomplete.Textarea.prototype, {
// Public methods
// --------------
select: function(value, strategy, e) {
var pre = this.getTextFromHeadToCaret();
var post = this.el.value.substring(pre.length);
var newSubstr = strategy.replace(value, e);
var regExp;
if (typeof newSubstr !== "undefined") {
if ($.isArray(newSubstr)) {
post = newSubstr[1] + post;
newSubstr = newSubstr[0];
}
regExp = $.isFunction(strategy.match)
? strategy.match(pre)
: strategy.match;
pre = pre.replace(regExp, newSubstr);
this.$el.val(pre + post);
this.el.focus();
var range = this.el.createTextRange();
range.collapse(true);
range.moveEnd("character", pre.length);
range.moveStart("character", pre.length);
range.select();
}
},
getTextFromHeadToCaret: function() {
this.el.focus();
var range = document.selection.createRange();
range.moveStart("character", -this.el.value.length);
var arr = range.text.split(sentinelChar);
return arr.length === 1 ? arr[0] : arr[1];
}
});
$.fn.textcomplete.IETextarea = IETextarea;
})(jQuery);
// NOTE: TextComplete plugin has contenteditable support but it does not work
// fine especially on old IEs.
// Any pull requests are REALLY welcome.
+(function($) {
"use strict";
// ContentEditable adapter
// =======================
//
// Adapter for contenteditable elements.
function ContentEditable(element, completer, option) {
this.initialize(element, completer, option);
}
$.extend(ContentEditable.prototype, $.fn.textcomplete.Adapter.prototype, {
// Public methods
// --------------
// Update the content with the given value and strategy.
// When an dropdown item is selected, it is executed.
select: function(value, strategy, e) {
var pre = this.getTextFromHeadToCaret();
// use ownerDocument instead of window to support iframes
var sel = this.el.ownerDocument.getSelection();
var range = sel.getRangeAt(0);
var selection = range.cloneRange();
selection.selectNodeContents(range.startContainer);
var content = selection.toString();
var post = content.substring(range.startOffset);
var newSubstr = strategy.replace(value, e);
var regExp;
if (typeof newSubstr !== "undefined") {
if ($.isArray(newSubstr)) {
post = newSubstr[1] + post;
newSubstr = newSubstr[0];
}
regExp = $.isFunction(strategy.match)
? strategy.match(pre)
: strategy.match;
pre = pre.replace(regExp, newSubstr).replace(/ $/, " "); //   necessary at least for CKeditor to not eat spaces
range.selectNodeContents(range.startContainer);
range.deleteContents();
// create temporary elements
var preWrapper = this.el.ownerDocument.createElement("div");
preWrapper.innerHTML = pre;
var postWrapper = this.el.ownerDocument.createElement("div");
postWrapper.innerHTML = post;
// create the fragment thats inserted
var fragment = this.el.ownerDocument.createDocumentFragment();
var childNode;
var lastOfPre;
while ((childNode = preWrapper.firstChild)) {
lastOfPre = fragment.appendChild(childNode);
}
while ((childNode = postWrapper.firstChild)) {
fragment.appendChild(childNode);
}
// insert the fragment & jump behind the last node in "pre"
range.insertNode(fragment);
range.setStartAfter(lastOfPre);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
},
// Private methods
// ---------------
// Returns the caret's relative position from the contenteditable's
// left top corner.
//
// Examples
//
// this._getCaretRelativePosition()
// //=> { top: 18, left: 200, lineHeight: 16 }
//
// Dropdown's position will be decided using the result.
_getCaretRelativePosition: function() {
var range = this.el.ownerDocument
.getSelection()
.getRangeAt(0)
.cloneRange();
var wrapperNode = range.endContainer.parentNode;
var node = this.el.ownerDocument.createElement("span");
range.insertNode(node);
range.selectNodeContents(node);
range.deleteContents();
setTimeout(function() {
wrapperNode.normalize();
}, 0);
var $node = $(node);
var position = $node.offset();
position.left -= this.$el.offset().left;
position.top += $node.height() - this.$el.offset().top;
position.lineHeight = $node.height();
// special positioning logic for iframes
// this is typically used for contenteditables such as tinymce or ckeditor
if (this.completer.$iframe) {
var iframePosition = this.completer.$iframe.offset();
position.top += iframePosition.top;
position.left += iframePosition.left;
// We need to get the scrollTop of the html-element inside the iframe and not of the body-element,
// because on IE the scrollTop of the body-element (this.$el) is always zero.
position.top -= $(
this.completer.$iframe[0].contentWindow.document
).scrollTop();
}
$node.remove();
return position;
},
// Returns the string between the first character and the caret.
// Completer will be triggered with the result for start autocompleting.
//
// Example
//
// // Suppose the html is '<b>hello</b> wor|ld' and | is the caret.
// this.getTextFromHeadToCaret()
// // => ' wor' // not '<b>hello</b> wor'
getTextFromHeadToCaret: function() {
var range = this.el.ownerDocument.getSelection().getRangeAt(0);
var selection = range.cloneRange();
selection.selectNodeContents(range.startContainer);
return selection.toString().substring(0, range.startOffset);
}
});
$.fn.textcomplete.ContentEditable = ContentEditable;
})(jQuery);
// NOTE: TextComplete plugin has contenteditable support but it does not work
// fine especially on old IEs.
// Any pull requests are REALLY welcome.
+(function($) {
"use strict";
// CKEditor adapter
// =======================
//
// Adapter for CKEditor, based on contenteditable elements.
function CKEditor(element, completer, option) {
this.initialize(element, completer, option);
}
$.extend(CKEditor.prototype, $.fn.textcomplete.ContentEditable.prototype, {
_bindEvents: function() {
var $this = this;
this.option.ckeditor_instance.on(
"key",
function(event) {
var domEvent = event.data;
$this._onKeyup(domEvent);
if ($this.completer.dropdown.shown && $this._skipSearch(domEvent)) {
return false;
}
},
null,
null,
1
); // 1 = Priority = Important!
// we actually also need the native event, as the CKEditor one is happening to late
this.$el.on("keyup." + this.id, $.proxy(this._onKeyup, this));
}
});
$.fn.textcomplete.CKEditor = CKEditor;
})(jQuery);
// The MIT License (MIT)
//
// Copyright (c) 2015 Jonathan Ong me@jongleberry.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// https://github.com/component/textarea-caret-position
(function($) {
// The properties that we copy into a mirrored div.
// Note that some browsers, such as Firefox,
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
// so we have to do every single property specifically.
var properties = [
"direction", // RTL support
"boxSizing",
"width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
"height",
"overflowX",
"overflowY", // copy the scrollbar for IE
"borderTopWidth",
"borderRightWidth",
"borderBottomWidth",
"borderLeftWidth",
"borderStyle",
"paddingTop",
"paddingRight",
"paddingBottom",
"paddingLeft",
// https://developer.mozilla.org/en-US/docs/Web/CSS/font
"fontStyle",
"fontVariant",
"fontWeight",
"fontStretch",
"fontSize",
"fontSizeAdjust",
"lineHeight",
"fontFamily",
"textAlign",
"textTransform",
"textIndent",
"textDecoration", // might not make a difference, but better be safe
"letterSpacing",
"wordSpacing",
"tabSize",
"MozTabSize"
];
var isBrowser = typeof window !== "undefined";
var isFirefox = isBrowser && window.mozInnerScreenX != null;
function getCaretCoordinates(element, position, options) {
if (!isBrowser) {
throw new Error(
"textarea-caret-position#getCaretCoordinates should only be called in a browser"
);
}
var debug = (options && options.debug) || false;
if (debug) {
var el = document.querySelector(
"#input-textarea-caret-position-mirror-div"
);
if (el) {
el.parentNode.removeChild(el);
}
}
// mirrored div
var div = document.createElement("div");
div.id = "input-textarea-caret-position-mirror-div";
document.body.appendChild(div);
var style = div.style;
var computed = window.getComputedStyle
? getComputedStyle(element)
: element.currentStyle; // currentStyle for IE < 9
// default textarea styles
style.whiteSpace = "pre-wrap";
if (element.nodeName !== "INPUT") style.wordWrap = "break-word"; // only for textarea-s
// position off-screen
style.position = "absolute"; // required to return coordinates properly
if (!debug) style.visibility = "hidden"; // not 'display: none' because we want rendering
// transfer the element's properties to the div
properties.forEach(function(prop) {
style[prop] = computed[prop];
});
if (isFirefox) {
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
if (element.scrollHeight > parseInt(computed.height))
style.overflowY = "scroll";
} else {
style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
}
div.textContent = element.value.substring(0, position);
// the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
if (element.nodeName === "INPUT")
div.textContent = div.textContent.replace(/\s/g, "\u00a0");
var span = document.createElement("span");
// Wrapping must be replicated *exactly*, including when a long word gets
// onto the next line, with whitespace at the end of the line before (#7).
// The *only* reliable way to do that is to copy the *entire* rest of the
// textarea's content into the <span> created at the caret position.
// for inputs, just '.' would be enough, but why bother?
span.textContent = element.value.substring(position) || "."; // || because a completely empty faux span doesn't render at all
div.appendChild(span);
var coordinates = {
top: span.offsetTop + parseInt(computed["borderTopWidth"]),
left: span.offsetLeft + parseInt(computed["borderLeftWidth"])
};
if (debug) {
span.style.backgroundColor = "#aaa";
} else {
document.body.removeChild(div);
}
return coordinates;
}
$.fn.textcomplete.getCaretCoordinates = getCaretCoordinates;
})(jQuery);
return jQuery;
});
{
"data": [
{ "name": "Afghanistan", "code": "AF" },
{ "name": "Åland Islands", "code": "AX" },
{ "name": "Albania", "code": "AL" },
{ "name": "Algeria", "code": "DZ" },
{ "name": "American Samoa", "code": "AS" },
{ "name": "AndorrA", "code": "AD" },
{ "name": "Angola", "code": "AO" },
{ "name": "Anguilla", "code": "AI" },
{ "name": "Antarctica", "code": "AQ" },
{ "name": "Antigua and Barbuda", "code": "AG" },
{ "name": "Argentina", "code": "AR" },
{ "name": "Armenia", "code": "AM" },
{ "name": "Aruba", "code": "AW" },
{ "name": "Australia", "code": "AU" },
{ "name": "Austria", "code": "AT" },
{ "name": "Azerbaijan", "code": "AZ" },
{ "name": "Bahamas", "code": "BS" },
{ "name": "Bahrain", "code": "BH" },
{ "name": "Bangladesh", "code": "BD" },
{ "name": "Barbados", "code": "BB" },
{ "name": "Belarus", "code": "BY" },
{ "name": "Belgium", "code": "BE" },
{ "name": "Belize", "code": "BZ" },
{ "name": "Benin", "code": "BJ" },
{ "name": "Bermuda", "code": "BM" },
{ "name": "Bhutan", "code": "BT" },
{ "name": "Bolivia", "code": "BO" },
{ "name": "Bosnia and Herzegovina", "code": "BA" },
{ "name": "Botswana", "code": "BW" },
{ "name": "Bouvet Island", "code": "BV" },
{ "name": "Brazil", "code": "BR" },
{ "name": "British Indian Ocean Territory", "code": "IO" },
{ "name": "Brunei Darussalam", "code": "BN" },
{ "name": "Bulgaria", "code": "BG" },
{ "name": "Burkina Faso", "code": "BF" },
{ "name": "Burundi", "code": "BI" },
{ "name": "Cambodia", "code": "KH" },
{ "name": "Cameroon", "code": "CM" },
{ "name": "Canada", "code": "CA" },
{ "name": "Cape Verde", "code": "CV" },
{ "name": "Cayman Islands", "code": "KY" },
{ "name": "Central African Republic", "code": "CF" },
{ "name": "Chad", "code": "TD" },
{ "name": "Chile", "code": "CL" },
{ "name": "China", "code": "CN" },
{ "name": "Christmas Island", "code": "CX" },
{ "name": "Cocos (Keeling) Islands", "code": "CC" },
{ "name": "Colombia", "code": "CO" },
{ "name": "Comoros", "code": "KM" },
{ "name": "Congo", "code": "CG" },
{ "name": "Congo, The Democratic Republic of the", "code": "CD" },
{ "name": "Cook Islands", "code": "CK" },
{ "name": "Costa Rica", "code": "CR" },
{ "name": "Cote D'Ivoire", "code": "CI" },
{ "name": "Croatia", "code": "HR" },
{ "name": "Cuba", "code": "CU" },
{ "name": "Cyprus", "code": "CY" },
{ "name": "Czech Republic", "code": "CZ" },
{ "name": "Denmark", "code": "DK" },
{ "name": "Djibouti", "code": "DJ" },
{ "name": "Dominica", "code": "DM" },
{ "name": "Dominican Republic", "code": "DO" },
{ "name": "Ecuador", "code": "EC" },
{ "name": "Egypt", "code": "EG" },
{ "name": "El Salvador", "code": "SV" },
{ "name": "Equatorial Guinea", "code": "GQ" },
{ "name": "Eritrea", "code": "ER" },
{ "name": "Estonia", "code": "EE" },
{ "name": "Ethiopia", "code": "ET" },
{ "name": "Falkland Islands (Malvinas)", "code": "FK" },
{ "name": "Faroe Islands", "code": "FO" },
{ "name": "Fiji", "code": "FJ" },
{ "name": "Finland", "code": "FI" },
{ "name": "France", "code": "FR" },
{ "name": "French Guiana", "code": "GF" },
{ "name": "French Polynesia", "code": "PF" },
{ "name": "French Southern Territories", "code": "TF" },
{ "name": "Gabon", "code": "GA" },
{ "name": "Gambia", "code": "GM" },
{ "name": "Georgia", "code": "GE" },
{ "name": "Germany", "code": "DE" },
{ "name": "Ghana", "code": "GH" },
{ "name": "Gibraltar", "code": "GI" },
{ "name": "Greece", "code": "GR" },
{ "name": "Greenland", "code": "GL" },
{ "name": "Grenada", "code": "GD" },
{ "name": "Guadeloupe", "code": "GP" },
{ "name": "Guam", "code": "GU" },
{ "name": "Guatemala", "code": "GT" },
{ "name": "Guernsey", "code": "GG" },
{ "name": "Guinea", "code": "GN" },
{ "name": "Guinea-Bissau", "code": "GW" },
{ "name": "Guyana", "code": "GY" },
{ "name": "Haiti", "code": "HT" },
{ "name": "Heard Island and Mcdonald Islands", "code": "HM" },
{ "name": "Holy See (Vatican City State)", "code": "VA" },
{ "name": "Honduras", "code": "HN" },
{ "name": "Hong Kong", "code": "HK" },
{ "name": "Hungary", "code": "HU" },
{ "name": "Iceland", "code": "IS" },
{ "name": "India", "code": "IN" },
{ "name": "Indonesia", "code": "ID" },
{ "name": "Iran, Islamic Republic Of", "code": "IR" },
{ "name": "Iraq", "code": "IQ" },
{ "name": "Ireland", "code": "IE" },
{ "name": "Isle of Man", "code": "IM" },
{ "name": "Israel", "code": "IL" },
{ "name": "Italy", "code": "IT" },
{ "name": "Jamaica", "code": "JM" },
{ "name": "Japan", "code": "JP" },
{ "name": "Jersey", "code": "JE" },
{ "name": "Jordan", "code": "JO" },
{ "name": "Kazakhstan", "code": "KZ" },
{ "name": "Kenya", "code": "KE" },
{ "name": "Kiribati", "code": "KI" },
{ "name": "Korea, Democratic People'S Republic of", "code": "KP" },
{ "name": "Korea, Republic of", "code": "KR" },
{ "name": "Kuwait", "code": "KW" },
{ "name": "Kyrgyzstan", "code": "KG" },
{ "name": "Lao People'S Democratic Republic", "code": "LA" },
{ "name": "Latvia", "code": "LV" },
{ "name": "Lebanon", "code": "LB" },
{ "name": "Lesotho", "code": "LS" },
{ "name": "Liberia", "code": "LR" },
{ "name": "Libyan Arab Jamahiriya", "code": "LY" },
{ "name": "Liechtenstein", "code": "LI" },
{ "name": "Lithuania", "code": "LT" },
{ "name": "Luxembourg", "code": "LU" },
{ "name": "Macao", "code": "MO" },
{ "name": "Macedonia, The Former Yugoslav Republic of", "code": "MK" },
{ "name": "Madagascar", "code": "MG" },
{ "name": "Malawi", "code": "MW" },
{ "name": "Malaysia", "code": "MY" },
{ "name": "Maldives", "code": "MV" },
{ "name": "Mali", "code": "ML" },
{ "name": "Malta", "code": "MT" },
{ "name": "Marshall Islands", "code": "MH" },
{ "name": "Martinique", "code": "MQ" },
{ "name": "Mauritania", "code": "MR" },
{ "name": "Mauritius", "code": "MU" },
{ "name": "Mayotte", "code": "YT" },
{ "name": "Mexico", "code": "MX" },
{ "name": "Micronesia, Federated States of", "code": "FM" },
{ "name": "Moldova, Republic of", "code": "MD" },
{ "name": "Monaco", "code": "MC" },
{ "name": "Mongolia", "code": "MN" },
{ "name": "Montserrat", "code": "MS" },
{ "name": "Morocco", "code": "MA" },
{ "name": "Mozambique", "code": "MZ" },
{ "name": "Myanmar", "code": "MM" },
{ "name": "Namibia", "code": "NA" },
{ "name": "Nauru", "code": "NR" },
{ "name": "Nepal", "code": "NP" },
{ "name": "Netherlands", "code": "NL" },
{ "name": "Netherlands Antilles", "code": "AN" },
{ "name": "New Caledonia", "code": "NC" },
{ "name": "New Zealand", "code": "NZ" },
{ "name": "Nicaragua", "code": "NI" },
{ "name": "Niger", "code": "NE" },
{ "name": "Nigeria", "code": "NG" },
{ "name": "Niue", "code": "NU" },
{ "name": "Norfolk Island", "code": "NF" },
{ "name": "Northern Mariana Islands", "code": "MP" },
{ "name": "Norway", "code": "NO" },
{ "name": "Oman", "code": "OM" },
{ "name": "Pakistan", "code": "PK" },
{ "name": "Palau", "code": "PW" },
{ "name": "Palestinian Territory, Occupied", "code": "PS" },
{ "name": "Panama", "code": "PA" },
{ "name": "Papua New Guinea", "code": "PG" },
{ "name": "Paraguay", "code": "PY" },
{ "name": "Peru", "code": "PE" },
{ "name": "Philippines", "code": "PH" },
{ "name": "Pitcairn", "code": "PN" },
{ "name": "Poland", "code": "PL" },
{ "name": "Portugal", "code": "PT" },
{ "name": "Puerto Rico", "code": "PR" },
{ "name": "Qatar", "code": "QA" },
{ "name": "Reunion", "code": "RE" },
{ "name": "Romania", "code": "RO" },
{ "name": "Russian Federation", "code": "RU" },
{ "name": "RWANDA", "code": "RW" },
{ "name": "Saint Helena", "code": "SH" },
{ "name": "Saint Kitts and Nevis", "code": "KN" },
{ "name": "Saint Lucia", "code": "LC" },
{ "name": "Saint Pierre and Miquelon", "code": "PM" },
{ "name": "Saint Vincent and the Grenadines", "code": "VC" },
{ "name": "Samoa", "code": "WS" },
{ "name": "San Marino", "code": "SM" },
{ "name": "Sao Tome and Principe", "code": "ST" },
{ "name": "Saudi Arabia", "code": "SA" },
{ "name": "Senegal", "code": "SN" },
{ "name": "Serbia and Montenegro", "code": "CS" },
{ "name": "Seychelles", "code": "SC" },
{ "name": "Sierra Leone", "code": "SL" },
{ "name": "Singapore", "code": "SG" },
{ "name": "Slovakia", "code": "SK" },
{ "name": "Slovenia", "code": "SI" },
{ "name": "Solomon Islands", "code": "SB" },
{ "name": "Somalia", "code": "SO" },
{ "name": "South Africa", "code": "ZA" },
{ "name": "South Georgia and the South Sandwich Islands", "code": "GS" },
{ "name": "Spain", "code": "ES" },
{ "name": "Sri Lanka", "code": "LK" },
{ "name": "Sudan", "code": "SD" },
{ "name": "Suriname", "code": "SR" },
{ "name": "Svalbard and Jan Mayen", "code": "SJ" },
{ "name": "Swaziland", "code": "SZ" },
{ "name": "Sweden", "code": "SE" },
{ "name": "Switzerland", "code": "CH" },
{ "name": "Syrian Arab Republic", "code": "SY" },
{ "name": "Taiwan, Province of China", "code": "TW" },
{ "name": "Tajikistan", "code": "TJ" },
{ "name": "Tanzania, United Republic of", "code": "TZ" },
{ "name": "Thailand", "code": "TH" },
{ "name": "Timor-Leste", "code": "TL" },
{ "name": "Togo", "code": "TG" },
{ "name": "Tokelau", "code": "TK" },
{ "name": "Tonga", "code": "TO" },
{ "name": "Trinidad and Tobago", "code": "TT" },
{ "name": "Tunisia", "code": "TN" },
{ "name": "Turkey", "code": "TR" },
{ "name": "Turkmenistan", "code": "TM" },
{ "name": "Turks and Caicos Islands", "code": "TC" },
{ "name": "Tuvalu", "code": "TV" },
{ "name": "Uganda", "code": "UG" },
{ "name": "Ukraine", "code": "UA" },
{ "name": "United Arab Emirates", "code": "AE" },
{ "name": "United Kingdom", "code": "GB" },
{ "name": "United States", "code": "US" },
{ "name": "United States Minor Outlying Islands", "code": "UM" },
{ "name": "Uruguay", "code": "UY" },
{ "name": "Uzbekistan", "code": "UZ" },
{ "name": "Vanuatu", "code": "VU" },
{ "name": "Venezuela", "code": "VE" },
{ "name": "Viet Nam", "code": "VN" },
{ "name": "Virgin Islands, British", "code": "VG" },
{ "name": "Virgin Islands, U.S.", "code": "VI" },
{ "name": "Wallis and Futuna", "code": "WF" },
{ "name": "Western Sahara", "code": "EH" },
{ "name": "Yemen", "code": "YE" },
{ "name": "Zambia", "code": "ZM" },
{ "name": "Zimbabwe", "code": "ZW" }
]
}
.textcomplete-dropdown {
border: 1px solid #ddd;
background-color: white;
}
.textcomplete-dropdown li {
border-top: 1px solid #ddd;
padding: 2px 5px;
}
.textcomplete-dropdown li:first-child {
border-top: none;
}
.textcomplete-dropdown li:hover,
.textcomplete-dropdown .active {
background-color: rgb(110, 183, 219);
}
.textcomplete-dropdown {
list-style: none;
padding: 0;
margin: 0;
}
.textcomplete-dropdown a:hover {
cursor: pointer;
}
<mvc:View controllerName="com.demo.textarea.autocomplete.controller.App"
xmlns="sap.m"
xmlns:l="sap.ui.layout"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc">
<Page title="Demo - TextArea Autocomplete">
<content>
<FlexBox width="100%" direction="Column" backgroundDesign="Solid" displayInline="true">
<HBox width="100%">
<TextArea id="textArea_01" height="30%" cols="100"/>
</HBox>
</FlexBox>
</content>
</Page>
</mvc:View>