Skip to content

Instantly share code, notes, and snippets.

@dawsontoth
Created October 10, 2011 10:11
Show Gist options
  • Save dawsontoth/1274992 to your computer and use it in GitHub Desktop.
Save dawsontoth/1274992 to your computer and use it in GitHub Desktop.
iOS Text Field Suggestions in Appcelerator Titanium
/**
* This demonstrates how to show suggestions on a text field using just JavaScript with Appcelerator Titanium.
*
* You will need to download four images to get this to work:
*
* 1) http://dl.dropbox.com/u/16441391/Suggest/bg.png
* 2) http://dl.dropbox.com/u/16441391/Suggest/bg@2x.png
* 3) http://dl.dropbox.com/u/16441391/Suggest/separator.png
* 4) http://dl.dropbox.com/u/16441391/Suggest/separator@2x.png
*
* I suggest placing them in "images/suggest/", but you can configure where they go in the "config" method.
*
* By Dawson Toth, Appcelerator Inc.
*/
// STEP 1: Include our JavaScript module.
var suggest = require('suggest');
suggest.config({
imageDirectory: 'images/suggest/'
});
var win = Ti.UI.createWindow({
layout: 'vertical', backgroundColor: '#fff'
});
// STEP 2: We'll define four text field in a loop, down below.
var fields = [
'To', 'From', 'CC', 'BCC'
];
var customerEngineers = [ 'Dawson Toth', 'Pedro Enrique', 'Jon Alter', 'Alan Leard', 'Rick Blalock', 'Matthew Congrove' ];
function findCustomerEngineers(evt) {
var results = [];
// No text in the text field? That's fine, we won't show any suggestions.
if (evt.value.length == 0)
return results;
var searchFor = evt.value.toLowerCase();
for (var p in customerEngineers) {
if (customerEngineers[p].toLowerCase().split(searchFor).length > 1) {
results.push(customerEngineers[p]);
if (results.length > 3) {
break;
}
}
}
return results;
}
for (var f in fields) {
// STEP 3: We need to wrap the text field in a view; the suggestions will be added to the bottom of this.
var wrapper = Ti.UI.createView({
top: 0, left: 20, right: 20, height: 50
});
// STEP 4: Create the text field itself, and add it to the wrapper.
var text = Ti.UI.createTextField({
top: 20, left: 0, right: 0, height: 30,
hintText: fields[f], autocapitalization: 0,
borderStyle: Ti.UI.INPUT_BORDERSTYLE_ROUNDED
});
wrapper.add(text);
// STEP 5: Call the "bind" method of our module.
suggest.bind({
wrapper: wrapper,
control: text,
dataSource: findCustomerEngineers
});
// STEP 6: Add the wrapper to the window, open the window, and we're done!
win.add(wrapper);
}
win.open();
/**
* This module lets you set up suggestions on text fields.
*
* You will need to download four images to get this to work:
*
* 1) http://dl.dropbox.com/u/16441391/Suggest/bg.png
* 2) http://dl.dropbox.com/u/16441391/Suggest/bg@2x.png
* 3) http://dl.dropbox.com/u/16441391/Suggest/separator.png
* 4) http://dl.dropbox.com/u/16441391/Suggest/separator@2x.png
*
* I suggest placing them in "images/suggest/", but you can configure where they go in the "config" method.
*/
// Instance Variables
var baseImageDirectory;
/**
* Allows you to customize the behavior of the module.
* @param args A dictionary with the following keys: imageDirectory, controls where the images are loaded from.
*/
exports.config = function(args) {
baseImageDirectory = args.imageDirectory || '';
};
/**
* Sets up a text field for showing suggestions.
* @param args A dictionary with the following keys: control, wrapper, and dataSource.
*/
exports.bind = function(args) {
if (!args.control || !args.wrapper || !args.dataSource) {
throw 'Not all required parameters provided: "control", "wrapper", and "dataSource" must be specified!';
}
var visible = false, changed = false, latestValue = '';
var suggestions = Ti.UI.createView({
backgroundImage: baseImageDirectory + 'bg.png',
backgroundLeftCap: 9,
height: 43,
top: args.wrapper.size.height - 5,
left: 20, right: 20
});
var container = Ti.UI.createScrollView({
left: 5, right: 5, height: 43, width: 'auto',
layout: 'horizontal', contentWidth: 'auto'
});
suggestions.add(container);
function clickListener(evt) {
if (evt.source.isSuggestion) {
args.control.value = latestValue = evt.source.text;
visible = changed = false;
hide();
}
}
var children = [];
var originalHeight = args.wrapper.size.height || 0;
function show() {
args.wrapper.height = originalHeight + suggestions.height - 5;
args.wrapper.add(suggestions);
}
function hide() {
args.wrapper.remove(suggestions);
args.wrapper.height = originalHeight;
}
function sync() {
if (changed) {
changed = false;
var data = args.dataSource({ source: args.control, value: latestValue });
if (!data || data.length == 0) {
if (visible) {
visible = false;
hide();
}
}
else {
if (!visible) {
visible = true;
show();
}
for (var c = 0; c < children.length; c++) {
if (children[c].isSuggestion) {
children[c].removeEventListener('click', clickListener);
}
container.remove(children[c]);
}
children = [];
for (var i = 0; i < data.length; i++) {
var child = Ti.UI.createLabel({
isSuggestion: true,
text: data[i], font: { fontSize: 14 },
color: '#fff', shadowColor: '#000', shadowOffset: { x: 0, y: -1 },
width: 'auto', height: 14, top: 13, bottom: 16, left: 15, right: 15
});
child.addEventListener('click', clickListener);
children.push(child);
container.add(child);
if (i != data.length - 1) {
var separator = Ti.UI.createView({
backgroundImage: baseImageDirectory + 'separator.png',
width: 2, height: 36, top: 2
});
children.push(separator);
container.add(separator);
}
}
}
}
}
args.control.onChange = function(evt) {
if (evt.source.value != latestValue) {
latestValue = evt.source.value;
changed = true;
}
};
args.control.onFocus = function() {
args.control.onChangeIntervalID = setInterval(sync, 1000);
args.control.addEventListener('change', args.control.onChange);
// force a refresh
changed = true;
if (visible) {
show();
}
};
args.control.addEventListener('focus', args.control.onFocus);
args.control.onBlur = function() {
clearInterval(args.control.onChangeIntervalID);
args.control.removeEventListener('change', args.control.onChange);
if (visible) {
hide();
}
};
args.control.addEventListener('blur', args.control.onBlur);
};
/**
* Stops a text field from showing suggestions.
* @param args A dictionary with the following keys: control.
*/
exports.unbind = function(args) {
if (!args.control) {
throw 'Not all required parameters provided: "control" must be specified!';
}
clearInterval(args.control.onChangeIntervalID);
args.control.fireEvent('blur');
args.control.removeEventListener('focus', args.control.onFocus);
args.control.removeEventListener('blur', args.control.onBlur);
args.control.removeEventListener('change', args.control.onChange);
args.control.onChange
= args.control.onFocus
= args.control.onBlur
= args.control.onChangeIntervalID
= null;
};
@frootloose
Copy link

hi

I've tried to use this code, but I have a compilation error because appcelerator doesn't find the variable "exports"...
What is this variable ?

@dawsontoth
Copy link
Author

@frootloose Please look at the example app.js, and read up on CommonJS modules: http://developer.appcelerator.com/blog/2011/08/forging-titanium-episode-1-commonjs-modules.html

@frootloose
Copy link

works fine now, thank you !!!
actually the problem was an incorrect path in the "require" declaration, I found out understanding the concept of "module" with your link, thank you.

I see that if you have more than 2 suggestions, the suggestions list doesn't display it (the horizontal list is not large enough). is there any way to improve this ?

@dawsontoth
Copy link
Author

You tell me, you've got the code. Improve it.

@frootloose
Copy link

I didn't see that you coud scroll horizontally the suggestions. This allows the user to see all the suggestions, so it's fine. Your code is great : bravo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment