PageSourceSearch

https://erus.ru/Content/js/jq.dm.lookup2-1.0.js?v20230628

js erus.ru collected 2026-10-07 19:34:02 UTC 21,686 bytes, 656 lines download raw bytes

1(function ($) {
2
3	'use strict';
4	function _tryInit()
5	{
6		var $el					= $(this);
7		if (!$el.data('DmLookupObject'))
8		{
9			// Нет еще объекта - пробуем инициализировать....
10			if ($el.data('NoDmLookup2')) return false; // Уже пробовали :(
11
12			if ($el.data('url') || $el.data('json-url')) // Если есть data-url, значит скорее всего оно, однако TODO: прибумать более надежный способ детекта
13			{
14				// Ok, создаем
15				$el.data('DmLookupObject', new DmLookup2(this));
16			} else
17			{
18				// Создадим "Заглушку" для некоторых операций
19				$el.data('DmLookupObject', new DmNoLookup2(this));
20			}
21		}
22		return true;
23	}
24
25	$.fn.extend( // jQuery plugin
26	{
27		DmLookup2: function (param)
28		{
29			if(param===undefined)
30				return this.each(_tryInit);
31
32			this.each(function()
33			{
34				if (_tryInit.apply(this))
35				{
36					var lo		= $(this).data('DmLookupObject');
37					if (!lo) { console.warn('DmLookupObject is null for ', this); return; }
38					for (var p in param)
39					{
40						lo[p](param[p]);
41					}
42				}
43			});
44		},
45		// TODO: Obsolete?
46		lookuptext: function (val)
47		{
48			// вставляем данные в input/span (в зависимости от типа lookup'а) и в attr('title')
49			if($(this).is("div"))
50				return this.attr('title', val);
51			if($(this).is("input"))
52				return this.val(val).attr('title', val);
53			else
54				return this.text(val).attr('title', val);
55		}
56	});
57
58	var _shared					= {								// Тут данные, общие для всех lookupов
59		active					: null,							// Активная панель с подсказками
60	};
61
62	function _closeActive(newActive)
63	{
64		if (_shared.active===newActive) return;
65
66		if (_shared.active) _shared.active();
67
68		_shared.active			= newActive;
69	}
70
71	$(document).click(function () { _closeActive(null); });		// Один обработчик на все лукапы
72
73	// Заглушка при отсутствии лукапа
74	function DmNoLookup2(container)
75	{
76		var root				= $(container);					// Главный контрол
77		var _link;
78		
79		//#region === Public ===
80
81		this.isLookup			= false;
82
83		this.clear				=
84		this.enable				= 
85		this.readonly			=
86		this.addTargets			=
87		this.setUrl				=
88		this.insertData			=
89		this.val				= function(){};
90
91		this.setLink			= function (url)
92		{
93			if (!_link)
94			{
95				_link			= $('<a class="link" title="Открыть в новом окне" target="_blank"><span class="icon c-info"></span></a>');
96				_link.appendTo(root);
97			}
98
99			if (url) _link.attr('href', url).show(); else _link.hide();
100		};
101
102		//#endregion
103	}
104
105	// Класс поведения для контролов с lookup 
106	function DmLookup2(input)
107	{
108		//#region === Private ===
109
110		var field				= $(input);						// Главный контрол, который будет выступать в качестве строки поиска
111		var targets				= field.data('targets');		// Забираем карту раскладки из атрибута data-targets='...'
112		var prepModel			= field.data('prepare-model');	// Функция подготовки модели для раскладки полей TODO: проверить актуальность
113		var popupUrl			= field.data('url');			// URL для показа popup с вариантами
114		var jsonUrl				= field.data('json-url');		// URL для показа подсказок вариантов
115		var freeValue			= field.data('free-value');		// Если допустимо указать произвольное значение в поле ввода, то это свойство должно содержать имя поля модели, куда положить значение
116		var uitrigger			= field.data('uitrigger');		// Имя события изменения данных для jQuery обработчиков
117		var event				= field.data('event');			// Имя события изменения данных для native обработчиков, включая Vuejs
118
119		var clearbutton			= $('.lookup_clear', field);	// Кнопка очистки полей
120		var lookupButton		= $('span.lookup_popup',field);	// Кнопка открытия popup
121
122		var container			= field.closest('form');
123
124		var editvaluebox		= field.find("input.lookup_value,textarea.lookup_value");	// Редактируемое значение (существует только при редактируемом lookup'е)
125		var valuebox			= editvaluebox;					// поле со значением, которому могут выставить disabled = true
126		if (valuebox.length==0)
127		{
128			freeValue			= null;							// У нас не может быть произвольного значения
129			// нередактируемый лукап - пытаемся найти поле со значением
130			var fid				= field.attr('id');
131			if (fid)
132				valuebox		= field.find('#'+fid.replace(/_lookup$/, ''));
133		}
134		
135		var setter				= $.ETC.SetterBuilder(targets, container, prepModel);		// Функция раскладки данных
136		var _cancel				= function () {};
137		var _isDisabled;
138		var _setData;
139		var _inPopup			= field.closest('.dmlookup2popup').length===1;
140		var _link;
141
142		function _fire(val)
143		{
144			if(uitrigger)
145				field.trigger(uitrigger, val);
146
147			if (event)
148				field[0].dispatchEvent(new CustomEvent(event, { bubbles: true, detail : val }));
149		}
150
151		//#endregion
152
153		//#region === Public ===
154		
155		this.isLookup			= true;
156
157		// Очистить содержимое
158		this.clear				= function()	{ _setData(); };
159
160		// Активировать для изменения
161		this.enable				= function(en)	{ field[en?'removeClass':'addClass']('disable'); };
162
163		// readonly
164		this.readonly			= function(en)	{ field[en?'addClass':'removeClass']('readonly'); };
165
166		// Добавить поля для раскладки
167		this.addTargets			= function(ntargets)
168		{
169			var t				= targets;
170			for(var nt in ntargets)
171			{
172				if(t[nt]==null)
173				{
174					t[nt]		= ntargets[nt];
175				} else
176				{
177					var v		= ntargets[nt];
178					if(!$.isArray(t[nt]))
179						t[nt]	= [t[nt]];
180					if($.isArray(v))
181						t[nt]	= t[nt].concat(v);
182					else
183						t[nt].push(v);
184				}
185			}
186			setter				= $.ETC.SetterBuilder(t, container, prepModel);
187		};
188
189		// Изменить url для lookup
190		this.setUrl				= function (url) { popupUrl		= url; };
191
192		this.setLink			= function (url)
193		{
194			if (!_link)
195			{
196				_link			= $('<a class="link" title="Открыть в новом окне" target="_blank"><span class="icon c-info"></span></a>');
197				_link.prependTo(field);
198			}
199
200			if (url) _link.attr('href', url).show(); else _link.hide();
201		};
202		
203		// Вставить кастомные данные в lookup
204		// Example: $('#Model_ItemRegion').DmLookup2({'insertData':{'Dis
204playText':'testtext'}});
205		// Недостающие данные (Key, Version, DisplayText etc.) останутся нетронутыми
206		this.insertData			= function(val)
207		{
208			if (_isDisabled()) return;
209			setter(val, false);									//заменяем только те значения, которые передали
210			lastValue			= editvaluebox.val();
211			_fire(val);
212		};
213
214		this.val				= function (nv)
215		{
216			if (nv == undefined)
217				return lastvalue;
218			_setData(nv);
219			return this;
220		};
221
222		//#endregion
223
224		//#region === Shared part ===
225		
226		var lastValue			= editvaluebox.val();			// Выбранное значение в строке поиска
227					
228		_setData				= function(val)
229		{
230			setter(val, true);
231			lastValue			= editvaluebox.val();
232			_fire(val);
233		};
234
235		// функция проверки на запрет редактирование (классы 'disable' или 'readonly') 
236		_isDisabled				= function()
237		{
238			// проверяем, активен ди lookup2
239			return field.hasClass('disable') || field.hasClass('readonly') || valuebox.prop('disabled');
240		};
241
242		clearbutton.click(function(e)
243		{
244			$.ETC.FormModel.inPlaceEdit.freeze(clearbutton, true);
245			if (!_isDisabled())
246			{
247				_setData();
248				_cancel();
249			}
250			setTimeout(function()
251			{
252				field.focus();
253				editvaluebox.focus();
254				$.ETC.FormModel.inPlaceEdit.freeze(clearbutton, false);
255			}, 10);
256
257			return stop(e);
258		});
259
260		//#endregion
261
262		//#region === Popup part ===
263		
264		// Показ вариантов в Popupе
265		editvaluebox.click(function () { return false; });
266		lookupButton.click(_popup);
267
268		if (editvaluebox.length==0)
269		{
270			field.keydown(function(event)						// обработка нажатий клавиатуры
271			{
272				if (field.is(":focus"))
273				{
274					switch(event.keyCode)
275					{				
276						case 13:								// Enter - открыть lookup
277						case 32:								// Space - открыть lookup
278							_popup();
279							break;
280						case 46:								// Delete - очистить lookup
281							clearbutton.trigger('click');
282							break;
283					}
284				}
285			});
286		}
287
288		// Открыть lookup
289		function _popup()
290		{
291			if (_isDisabled())
292				return;
293
294			$.ETC.FormModel.inPlaceEdit.freeze(field, true);
295
296			field.addClass('retrive');
297			
298			// передаем управление обработчику popup
299			$.ETC.Popup({ url: popupUrl, callback: _popupCallback, nested: _inPopup });
300		}
301
302		// обрабатыываем клик по popup - забираем данные в lookup
303		function _popupCallback(val)
304		{
305			if (val)
306				_setData(val); // раскладываем данные в поля
307
308			field.removeClass('retrive');
309			field.focus();
310			editvaluebox.focus();
311
312			$.ETC.FormModel.inPlaceEdit.freeze(field, false);
313		}
314
315		//#endregion
316
317		//#region === Hints part ===
318
319		if (jsonUrl && editvaluebox.length!=0)					// У нас есть все нужное для hintов
320		{
321			(function()
322			{
323			var TYPETIMEOUT		= 500;							// Задержка (мс) поиска после окончания ввода пользователем.
324			var MAXHINTSHEIGHT	= 300;							// Максимальная высота pupup блока, при привышении которого включается вертикальный скроллинг.
325			var MINHINTSWIDTH	= 100;
326
327			var findvalue		= lastValue;					// Текущее значение строки поиска
328			var waiting			= false;						// Индикатор режима ожидания получения списка
329			var requestid		= 0;							// Идентификатор запроса
330			var tid				= null;							// ID таймера показа подсказок, используется для сброса при непрерывном наборе текста
331			var list			= null;							// Полученный список
332			var current			= -1;							// Индекс активного элемента в списке list, нужен для управления с клавиатуры
333			
334			// Hints элемент
335			var hintsvisible	= false;
336			var hintsdiv		= $('<div class="hints" tabindex="0">');
337			var itemsdiv		= $('<div>');
338			hintsdiv.append(itemsdiv);
339			editvaluebox.after(hintsdiv);
340			hintsdiv.on('focusin', function() { editvaluebox.focus(); });
341
342			function _tryGetList()
343			{
344				clearTimeout(tid);
345
346				if (findvalue != editvaluebox.val())
347				{
348					// Если значение в поле поменялось - запускаем поиск
349					findvalue	= editvaluebox.val();
350					editvaluebox.attr('class', 'noselected search');
351					hintsdiv.addClass('retrive')
352					itemsdiv.html('Загружаю...');
353
354					requestid++;
355					trace('Start retrive by "', findvalue, '", RID=', requestid, '...');
356					waiting		= true;							// Устанавливаем признак поиска
357					_setCurrent(-1);							// Сбрасываем индекс выделенного элемента
358					list		= null;
359					if (_loadData(jsonUrl, requestid, findvalue, _onretrive))	// Запускаем поиск и передаем строку для поиска + функцию обработки полученного списка
360						_showHints();
361				} else if (list != null)
362				{
363					editvaluebox.attr('class', 'noselected');
364					_showHints();
365				}
366			}
367
368			// Функция загрузки данных списка по указанному URL
369			function _loadData(url, rid, v, onretrive)
370			{
371				$.ajax({
372					url: url.replace(/(%7B|%7b|{)findvalue(%7D|%7d|})/g, encodeURIComponent(v)),
373					type: 'POST',
374					dataType: 'json',
375					cache: false,
376					async: true
377				}).done(function (json)
378				{
379					onretrive(rid, json);
380				}).fail(function (msg)
381				{
382					onretrive(rid, null, msg.responseText);
383				});
384
385				return true;
386			}
387
388			// Показать подсказки
389			function _showHints()
390			{
391				if (hintsvisible) return;
392
393				_closeActive(_cancel);
394
395				hintsvisible	= true;
396				var w			= field.outerWidth();
397				w				= w < MINHINTSWIDTH ? MINHINTSWIDTH : w;
398				hintsdiv
399					.css('width', w)
400					.slideDown('fast');
401			}
402
403			// Обработка полученного списка
404			function _onretrive(reqid, arr, error)
405			{
406				trace('onretrive(', reqid, '?=', requestid, ', arr, error)');
407				if (!waiting) return;				// Если внезапно оказалось, что ничего не ждем, то валим отсюда
408				if (requestid != reqid) return;		// Если пришел ответ на устаревший запрос - валим отсюда
409				waiting			= false;		// сбрасываем признак поиска
410
411				if (arr == null || arr.length == 0)		// Пришла пустота или была ошибка :(
412				{
413					list		= null;
414					hintsdiv.attr('class', error ? 'hints error' : 'hints empty');
415					itemsdiv.html($.Dm.Form.clearHtml(error || 'Ничего не найдено')); // TODO: Нужно отвязать от $.Dm.Form
416					trace('Result is ', error || null);
417				} else								// Пришли данные :)
418				{
419					list		= arr;
420					hintsdiv.attr('class', 'hints scrolled');
421
422					// Создаем HTML списка
423					var el, item, html = [];
424					for (var i = 0; i < list.length; i++)
425					{
426						item	= list[i];
427						if (item.text == null && item.DisplayText != null)
428							item.text	= item.DisplayText;
429
430						html.push('<div data-index="', i, '" class="hint">', item.html || item.text, '</div>');
431					}
432
433					// Показываем HTML
434					itemsdiv.html(html.join(''));
435					
436					// Показываем подсказки, иначе не рассчитается высота
437					_showHints();
438					// Смотрим на размер списка и если он огромный, то включаем скролл
439					var h		= itemsdiv.outerHeight();
440					trace('Founded ', list.length, ' elements, h = ', h, ' <= ', MAXHINTSHEIGHT);
441					if (h <= MAXHINTSHEIGHT)
442						hintsdiv.removeClass('scrolled');
443				}
444
445				editvaluebox.attr('class', 'noselected');		// Стиль - не выбрано значение
446				_setCurrent(findvalue && !freeValue ? 0 : -1);	// Встаем на первый элемент, если что-то искали
447				if (findvalue != editvaluebox.val())			// Если пока мы получали данные у нас что-то ввели, то запускаем процесс с начала
448				{
449					trace('schedule hints by onretrive...');
450					clearTimeout(tid);
451					tid			= setTimeout(_tryGetList, TYPETIMEOUT);
452				}
453			}
454
455			// Установить индекс на указанный
456			function _setCurrent(c)
457			{
458				var kids		= itemsdiv.children();
459				var nc			= (list ? (c < kids.length ? c : -1) : -1);
460				if (nc == current)
461					return;
462
463				if (0 <= current && current < kids.length)
464					$(kids[current]).attr('class', 'hint');
465
466				current			= nc;
467				if (0 <= current && current < kids.length)
468				{
469					var cur		= $(kids[current]);
470					cur.attr('class', 'hint selected');
471					if (hintsdiv.hasClass('scrolled'))
472					{
473						var curscr		= hintsdiv.scrollTop();
474						var pos			= (cur.position().top + curscr);
475						hintsdiv.scrollTop(pos);
476					}
477				}
478			}
479
480			function _newFreeValue()
481			{
482				var res			= {};
483				res[freeValue]	= editvaluebox.val();
484				return res;
485			}
486
487			_cancel				= function(closeonly)
488			{
489				if (!hintsvisible) return;
490
491				hintsvisible	= false;
492				hintsdiv.fadeOut('fast');
493				waiting			= false;
494				clearTimeout(tid);
495
496				editvaluebox.attr('class', 'selected');
497
498				if (closeonly!==true)
499				{
500					if (freeValue)
501					{
502						_setData(_newFreeValue());
503					} else
504					{
505						editvaluebox.val(lastValue);
506						findvalue		= lastValue;
507					}
508				}
509				_setCurrent(-1);
510			};
511
512			// Обработчик клика на элементе в списке
513			hintsdiv.click(function (e)
514			{
515				e.preventDefault();
516				e.stopPropagation();
517				var el			= $(e.target);
518				var item		= el.closest('.hint');
519				var index		= item.data('index');
520				trace('Index = ', index);
521				if (index != null)
522				{
523					_setData(list[index]);
524					_cancel(true);
525				}
526				return false;
527			});
528
529		// Обработчики кнопок
530			editvaluebox
531				.keyup(function(e)
532				{
533					clearTimeout(tid);
534
535					switch (e.keyCode)
536					{
537						case 9: // Tab - Выходим из поля и у нас либо не закончили ввод, либо закончили и пошли дальше
538							if (list != null && current >= 0 && current < list.length)
539								_setData(list[current]);
540							else if (editvaluebox.val()=='')
541								_setData();
542							else if (freeValue)
543								_setData(_newFreeValue());
544
545							_cancel();
546							break;
547						case 27: // Esc - вернуть предыдущее введенное значение
548							_cancel();
549							return false;
550						case 38: // вверх
551							if (list != null && current > 0)
552								_setCurrent(current - 1);
553
554							return false;
555						case 40: // вниз
556							if (list != null && current < list.length - 1)
557								_setCurrent(current + 1);
558
559							return false;
560						case 13: // Enter - текущий активный элемент становится значением
561							if (list != null && current >= 0 && current < list.length)
562							{
563								_setData(list[current]);
564								editvaluebox.focus();
565							} else if (editvaluebox.val()=='')
566								_setData();
567							else if (freeValue)
568								_setData(_newFreeValue());
569
570							if (hintsvisible)
571							{
572								e.stopPropagation();
573								_cancel();
574								return false;
575							} else
576								return true;
577						default:
578							trace('schedule hints by keyup...');
579							clearTimeout(tid);
580							tid = setTimeout(_tryGetList, TYPETIMEOUT);
581					}
582				})
583				.attr('class', 'selected');
584
585			trace('DmLookup2.hints for ', field[0].outerHTML, ' created');
586			})();
587		}
588
589		//#endregion
590
591		// Установить параметр в url
592		// ТОЛЬКО ДЛЯ СОВМЕСТИМОСТИ - УДАЛИТЬ И ИСПОЛЬЗОВАТЬ ОТДЕЛЬНЫЙ ОБЪЕКТ ДЛЯ URL
593		this.setUrlParam		= function(param)
594		{
595			popupUrl			= _setParam(popupUrl, param);
596			field.data('url', popupUrl);
597		};
598
599		this.setJsonUrlParam    = function (param)
600		{
601			jsonUrl				= _setParam(jsonUrl, param);
602			field.data('json-url', jsonUrl);
603		};
604
605		function _setParam(url, param)
606		{
607			url					= url.split('?');
608			var params			= url.length > 1 ? url[1].split('&') : [];
609			var n				= param.n + '=';
610			var nl				= n.length;
611
612			//поиск по параметрам нашего
613			for (var i = 0; i < params.length; i++)
614			{
615				if (params[i].substr(0, nl)===n)
616				{
617					params[i]	= n + encodeURIComponent(param.v); 
618					i			= -1;
619					break;
620				}
621			}
622
623			// если это таки новый параметр, то добавляем его в конец
624			if (i!==-1)
625				params.push(n + encodeURIComponent(param.v));
626
627			//собираем опять url
628			return url[0]+'?'+params.join('&');
629		}
630	}
631})(jQuery);
632
633// ТОЛЬКО ДЛЯ СОВМЕСТИМОСТИ - УДАЛИТЬ И ИСПОЛЬЗОВАТЬ $(...).DmLookup2({ clear: null, enable: true|false ... })
634var Lookup						= function(prefix)
635{
636	var el						= $('#' + prefix);
637	this.key = $("#" + prefix + "_Key");//TODO временное для совместимости
638
639	// Очистить содержимое
640	this.clear					= function()		{ el.DmLookup2({ clear: null }); };
641
642	// Активировать для изменения
643	this.enable					= function(en)		{ el.DmLookup2({ enable: en }); };
644
645	// readonly
646	this.readonly				= function(en)		{ el.DmLookup2({ readonly: en }); };
647
648	// Изменить url для lookup
649	this.changeLookupUrl		= function(url)		{ el.DmLookup2({ setUrl: url }); };
650
651	// Добавить поля для раскладки
652	this.addLookupTarget		= function(key, id)	{ var t = {}; t[key] = id; el.DmLookup2({ addTargets: t }); };
653
654	// Установить параметр в url
655	this.setUrlQueryParam		= function(pn, pv)	{ el.DmLookup2({ setUrlParam: { n: pn, v: pv } }); };
656}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.