aboutsummaryrefslogtreecommitdiff
path: root/src/main/webapp/textext/textext.plugin.ajax.js
blob: 073f46ab65a02acb22198766d831308855c3eebc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/**
 * jQuery TextExt Plugin
 * http://textextjs.com
 *
 * @version 1.3.1
 * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
 * @license MIT License
 */
(function($)
{
	/**
	 * AJAX plugin is very useful if you want to load list of items from a data point and pass it
	 * to the Autocomplete or Filter plugins.
	 *
	 * Because it meant to be as a helper method for either Autocomplete or Filter plugin, without
	 * either of these two present AJAX plugin won't do anything.
	 *
	 * @author agorbatchev
	 * @date 2011/08/16
	 * @id TextExtAjax
	 */
	function TextExtAjax() {};

	$.fn.textext.TextExtAjax = TextExtAjax;
	$.fn.textext.addPlugin('ajax', TextExtAjax);

	var p = TextExtAjax.prototype,

		/**
		 * AJAX plugin options are grouped under `ajax` when passed to the `$().textext()` function. Be
		 * mindful that the whole `ajax` object is also passed to jQuery `$.ajax` call which means that
		 * you can change all jQuery options as well. Please refer to the jQuery documentation on how
		 * to set url and all other parameters. For example:
		 *
		 *     $('textarea').textext({
		 *         plugins: 'ajax',
		 *         ajax: {
		 *             url: 'http://...'
		 *         }
		 *     })
		 *
		 * **Important**: Because it's necessary to pass options to `jQuery.ajax()` in a single object,
		 * all jQuery related AJAX options like `url`, `dataType`, etc **must** be within the `ajax` object.
		 * This is the exception to general rule that TextExt options can be specified in dot or camel case 
		 * notation.
		 * 
		 * @author agorbatchev
		 * @date 2011/08/16
		 * @id TextExtAjax.options
		 */

		/**
		 * By default, when user starts typing into the text input, AJAX plugin will start making requests
		 * to the `url` that you have specified and will pass whatever user has typed so far as a parameter
		 * named `q`, eg `?q=foo`.
		 *
		 * If you wish to change this behaviour, you can pass a function as a value for this option which
		 * takes one argument (the user input) and should return a key/value object that will be converted
		 * to the request parameters. For example:
		 *
		 *     'dataCallback' : function(query)
		 *     {
		 *         return { 'search' : query };
		 *     } 
		 *
		 * @name ajax.data.callback
		 * @default null
		 * @author agorbatchev
		 * @date 2011/08/16
		 * @id TextExtAjax.options.data.callback
		 */
		OPT_DATA_CALLBACK = 'ajax.data.callback',
		
		/**
		 * By default, the server end point is constantly being reloaded whenever user changes the value
		 * in the text input. If you'd rather have the client do result filtering, you can return all
		 * possible results from the server and cache them on the client by setting this option to `true`.
		 *
		 * In such a case, only one call to the server will be made and filtering will be performed on
		 * the client side using `ItemManager` attached to the core.
		 *
		 * @name ajax.data.results
		 * @default false
		 * @author agorbatchev
		 * @date 2011/08/16
		 * @id TextExtAjax.options.cache.results
		 */
		OPT_CACHE_RESULTS = 'ajax.cache.results',
		
		/**
		 * The loading message delay is set in seconds and will specify how long it would take before
		 * user sees the message. If you don't want user to ever see this message, set the option value
		 * to `Number.MAX_VALUE`.
		 *
		 * @name ajax.loading.delay
		 * @default 0.5
		 * @author agorbatchev
		 * @date 2011/08/16
		 * @id TextExtAjax.options.loading.delay
		 */
		OPT_LOADING_DELAY = 'ajax.loading.delay',

		/**
		 * Whenever an AJAX request is made and the server takes more than the number of seconds specified
		 * in `ajax.loading.delay` to respond, the message specified in this option will appear in the drop
		 * down.
		 *
		 * @name ajax.loading.message
		 * @default "Loading..."
		 * @author agorbatchev
		 * @date 2011/08/17
		 * @id TextExtAjax.options.loading.message
		 */
		OPT_LOADING_MESSAGE = 'ajax.loading.message',

		/**
		 * When user is typing in or otherwise changing the value of the text input, it's undesirable to make
		 * an AJAX request for every keystroke. Instead it's more conservative to send a request every number
		 * of seconds while user is typing the value. This number of seconds is specified by the `ajax.type.delay`
		 * option.
		 *
		 * @name ajax.type.delay
		 * @default 0.5
		 * @author agorbatchev
		 * @date 2011/08/17
		 * @id TextExtAjax.options.type.delay
		 */
		OPT_TYPE_DELAY = 'ajax.type.delay',

		/**
		 * AJAX plugin dispatches or reacts to the following events.
		 *
		 * @author agorbatchev
		 * @date 2011/08/17
		 * @id TextExtAjax.events
		 */

		/**
		 * AJAX plugin reacts to the `getSuggestions` event dispatched by the Autocomplete plugin.
		 *
		 * @name getSuggestions
		 * @author agorbatchev
		 * @date 2011/08/17
		 * @id TextExtAjax.events.getSuggestions
		 */

		/**
		 * In the event of successful AJAX request, the AJAX coponent dispatches the `setSuggestions`
		 * event meant to be recieved by the Autocomplete plugin.
		 *
		 * @name setSuggestions
		 * @author agorbatchev
		 * @date 2011/08/17
		 * @id TextExtAjax.events.setSuggestions
		 */
		EVENT_SET_SUGGESTION = 'setSuggestions',

		/**
		 * AJAX plugin dispatches the `showDropdown` event which Autocomplete plugin is expecting.
		 * This is used to temporarily show the loading message if the AJAX request is taking longer
		 * than expected.
		 *
		 * @name showDropdown
		 * @author agorbatchev
		 * @date 2011/08/17
		 * @id TextExtAjax.events.showDropdown
		 */
		EVENT_SHOW_DROPDOWN = 'showDropdown',

		TIMER_LOADING = 'loading',

		DEFAULT_OPTS = {
			ajax : {
				typeDelay      : 0.5,
				loadingMessage : 'Loading...',
				loadingDelay   : 0.5,
				cacheResults   : false,
				dataCallback   : null
			}
		}
		;

	/**
	 * Initialization method called by the core during plugin instantiation.
	 *
	 * @signature TextExtAjax.init(core)
	 *
	 * @param core {TextExt} Instance of the TextExt core class.
	 *
	 * @author agorbatchev
	 * @date 2011/08/17
	 * @id TextExtAjax.init
	 */
	p.init = function(core)
	{
		var self = this;

		self.baseInit(core, DEFAULT_OPTS);

		self.on({
			getSuggestions : self.onGetSuggestions
		});

		self._suggestions = null;
	};

	/**
	 * Performas an async AJAX with specified options.
	 *
	 * @signature TextExtAjax.load(query)
	 *
	 * @param query {String} Value that user has typed into the text area which is
	 * presumably the query.
	 *
	 * @author agorbatchev
	 * @date 2011/08/14
	 * @id TextExtAjax.load
	 */
	p.load = function(query)
	{
		var self         = this,
			dataCallback = self.opts(OPT_DATA_CALLBACK) || function(query) { return { q : query } },
			opts
			;

		opts = $.extend(true,
			{
				data    : dataCallback(query),
				success : function(data) { self.onComplete(data, query) },
				error   : function(jqXHR, message) { console.error(message, query) }
			}, 
			self.opts('ajax')
		);

		$.ajax(opts);
	};

	/**
	 * Successful call AJAX handler. Takes the data that came back from AJAX and the
	 * original query that was used to make the call.
	 *
	 * @signature TextExtAjax.onComplete(data, query)
	 *
	 * @param data {Object} Data loaded from the server, should be an Array of strings
	 * by default or whatever data structure your custom `ItemManager` implements.
	 *
	 * @param query {String} Query string, ie whatever user has typed in.
	 *
	 * @author agorbatchev
	 * @date 2011/08/14
	 * @id TextExtAjax.onComplete
	 */
	p.onComplete = function(data, query)
	{
		var self   = this,
			result = data
			;
		
		self.dontShowLoading();

		// If results are expected to be cached, then we store the original
		// data set and return the filtered one based on the original query.
		// That means we do filtering on the client side, instead of the
		// server side.
		if(self.opts(OPT_CACHE_RESULTS) == true)
		{
			self._suggestions = data;
			result = self.itemManager().filter(data, query);
		}

		self.trigger(EVENT_SET_SUGGESTION, { result : result });
	};

	/**
	 * If show loading message timer was started, calling this function disables it,
	 * otherwise nothing else happens.
	 *
	 * @signature TextExtAjax.dontShowLoading()
	 *
	 * @author agorbatchev
	 * @date 2011/08/16
	 * @id TextExtAjax.dontShowLoading
	 */
	p.dontShowLoading = function()
	{
		this.stopTimer(TIMER_LOADING);
	};

	/**
	 * Shows message specified in `ajax.loading.message` if loading data takes more than
	 * number of seconds specified in `ajax.loading.delay`.
	 *
	 * @signature TextExtAjax.showLoading()
	 *
	 * @author agorbatchev
	 * @date 2011/08/15
	 * @id TextExtAjax.showLoading
	 */
	p.showLoading = function()
	{
		var self = this;

		self.dontShowLoading();
		self.startTimer(
			TIMER_LOADING,
			self.opts(OPT_LOADING_DELAY),
			function()
			{
				self.trigger(EVENT_SHOW_DROPDOWN, function(autocomplete)
				{
					autocomplete.clearItems();
					var node = autocomplete.addDropdownItem(self.opts(OPT_LOADING_MESSAGE));
					node.addClass('text-loading');
				});
			}
		);
	};

	/**
	 * Reacts to the `getSuggestions` event and begin loading suggestions. If
	 * `ajax.cache.results` is specified, all calls after the first one will use
	 * cached data and filter it with the `core.itemManager.filter()`.
	 *
	 * @signature TextExtAjax.onGetSuggestions(e, data)
	 *
	 * @param e {Object} jQuery event.
	 * @param data {Object} Data structure passed with the `getSuggestions` event
	 * which contains the user query, eg `{ query : "..." }`.
	 *
	 * @author agorbatchev
	 * @date 2011/08/15
	 * @id TextExtAjax.onGetSuggestions
	 */
	p.onGetSuggestions = function(e, data)
	{
		var self        = this,
			suggestions = self._suggestions,
			query       = (data || {}).query || ''
			;

		if(suggestions && self.opts(OPT_CACHE_RESULTS) === true)
			return self.onComplete(suggestions, query);
		
		self.startTimer(
			'ajax',
			self.opts(OPT_TYPE_DELAY),
			function()
			{
				self.showLoading();
				self.load(query);
			}
		);
	};
})(jQuery);