PageSourceSearch

https://www.icon-art.info/e2/js/extjs_common.js

js icon-art.info collected 2026-10-02 07:40:44 UTC 53,439 bytes, 1,608 lines download raw bytes

1if (Ext.BLANK_IMAGE_URL.substr(0, 5) != 'data:') { 
2	Ext.BLANK_IMAGE_URL = '/ext/resources/images/default/s.gif';
3} 
4
5// для всех Ajax вызовов задаем таймаут в 5 минут
6Ext.Ajax.timeout = 300000; 
7
8/*
9 * Сначала думали написать DBProvider для сохранения настроек в БД, но потом подумалось, что в 
10 * cookie даже правильнее: на большом мониторе и на нетбуке у одного и того же пользователя могут быть
11 * разные настройки колонок, например. Оставим пока так.
12 */
13/*
14var cp = new Ext.state.CookieProvider({
15	path		: state_cookie_path,
16	expires	: new Date(new Date().getTime()+(3600000*24*30)) // запоминаем на 30 дней
17});
18Ext.state.Manager.setProvider(cp);
19*/
20
21// здесь рендерер нужен, чтобы подсунуть стиль, позволяющий тексту в колонке идти в несколько строк
22function multiline_renderer(val, meta, rec, row, col, store) {
23	meta.css = 'multilineColumn';
24	return val;
25}
26
27function date_renderer(val, meta, rec, row, col, store) {
28	 return (val == null) ? '' : val.format(l.date_fmt); 
29}
30
31function short_date_renderer(val, meta, rec) {
32	 return (val == null) ? '' : val.format(l.date_fmt_short); 
33}
34
35function percent_renderer (val, meta, rec, row, col, store) {
36	return val + "%"; 
37}
38
39function percent_color_renderer (val, meta, rec, row, col, store) {
40	switch(val) {
41		case 0		: meta.css = 'pink_bg'; break;
42		case 100	: meta.css = 'green_bg'; break;
43		default		: meta.css = 'yellow_bg'; 
44	}
45	return Math.round(val) + "%"; 
46}
47
48// универсальная функция визуализации полей типа 0/1
49function true_false_renderer(val, meta, rec, row, col, store) {
50	return (val == '1') ? "<img src='/img/gr.png'>" : "<img src='/img/red.png'>";
51}
52
53function green_on_renderer(val, meta, rec, row, col, store) {
54	return (val == '1') ? "<img src='/img/gr.png'>" : '';
55}
56
57function red_on_renderer(val, meta, rec, row, col, store) {
58	return (val == '1') ? "<img src='/img/red.png'>" : '';
59}
60
61function light_renderer(val, meta, rec, row, col, store) {
62	switch(parseInt(val)) {
63		case 0: // не определено
64			meta.css = 'yellow_bg';
65			return '?';
66		case 1: // есть
67			return "<img src='/img/gr.png'>";
68		case 2: // нет
69			return "<img src='/img/red.png'>";
70	}
71}
72
73function format_number(nStr, delim) {
74	if (nStr < 1000) return nStr; // для чисел <1000 разделитель разрядов не нужен
75	delim = delim || '&nbsp;';
76	nStr += '';
77	x = nStr.split('.');
78	x1 = x[0];
79	x2 = x.length > 1 ? '.' + x[1] : '';
80	var rgx = /(\d+)(\d{3})/;
81	while (rgx.test(x1)) {
82		x1 = x1.replace(rgx, '$1' + delim + '$2');
83	}
84	return x1 + x2;
85}
86
87// универсальная функция визуализации кодов Грейс
88function code_inner_renderer(val, meta, rec, row, col, store) {
89	return (val === null) ? '—' : format_number(val, ' ');
90}
91
92function code_inner_with_passport_renderer(val, meta, rec, row, col, store) {
93	if (val === null) { 
94		return '—'; 
95	} else {
96		var code = (val == 0) ? '[' + format_number(rec.data.unit_id, ' ') + ']' : format_number(val, ' ');
97		var wo_ref = ''; 
98		if (typeof(wo_id) !== 'undefined') { wo_ref = '&wo_id=' + wo_id; }
99 		return "<a target='_blank' href='/unit_info.php?id=" 
100			+ rec.data.unit_id + wo_ref + "'>" + code + "</a>";
101	}
102}
103
104function number_renderer(val, meta, rec, row, col, store) {
105	return format_number(val, ' ');
106}
107
108function grp_renderer(val) {
109	return(val > 0) ? format_number(val, ' ') : '—';
110}
111
112function filesize_renderer(nSize) {
113	if (nSize > 1024*1024) {
114		sSize = (nSize/1024/1024).round(1) + ' Мб';
115	} else if (nSize > 1024) {
116		sSize = (nSize/1024).round() + ' кб';
117	} else {
118		sSize = nSize + ' байт';
119	}
120	return sSize;
121}
122
123function day_night_varbal_renderer(val, meta, rec, row, col, store) {
124	if (rec.get('mpr_day') == 1) {
125		return l.day;
126	} else if (rec.get('mpr_night') == 1) {
127		return l.night;
128	}
129}
130
131function day_night_renderer(val, meta, rec, row, col, store) {
132	if (rec.get('mpr_day') == 1) {
133		return l.day;
134	} else if (rec.get('mpr_night') == 1) {
135		return "<img src='/img/night.png'>";
136	}
137}
138
139function night_day_renderer(val, meta, rec, row, col, store) {
140	return (val == 1) ? "<img src='/img/night.png'>" : 'день';
141}
142
143function bearing_renderer(val, meta, rec, row, col, store) {
144	if (val > 180) { meta.css = 'yellow_bg'; }
145	return val;
146}
147
148function wo_period_renderer(val, meta, rec) {
149	if (rec.data['start_m'] !== null) {
150		var str = rec.data['start_m'] + ' ' + rec.data['start_year'];
151		if ((rec.data['end_m'] !== null) && (rec.data['wo_period_start'] != rec.data['wo_period_end'])) {
152			str += ' – ' + rec.data['end_m'] + ' ' + rec.data['end_year']
153		}
154	} else {
155		var str = '—';
156	}
157	return str;	
158}
159
160function wo_status_renderer(val, meta, rec) {
161	meta.css = rec.data['wos_css'];
162	return rec.data['wos_name'];	
163}
164
165function generic_afteredit_success(resp, opt, e) {
166	response = Ext.decode(resp.responseText);
167	if (response.success) {
168		e.record.commit();
169	} else {
170		e.record.reject();
171		Ext.Msg.show({ 
172			title	: l.error, 
173			msg		: response.errormsg, 
174			icon	: Ext.MessageBox.ERROR, 
175			buttons	: Ext.MessageBox.OK
176		});					
177	}	
178}
179
180function generic_afteredit_failure(resp, opt, e) {
181	e.record.reject(); 
182	Ext.Msg.show({ 
183		title	: l.error, 
184		msg		: l.server_away, 
185		icon	: Ext.MessageBox.ERROR, 
186		buttons	: Ext.MessageBox.OK
187	});	
188}
189
190/**
191 * Типовая обработка правки записи через EditorGrid.
192 * Пример вызова:
193 * generic_afteredit.createDelegate(this, ['ap_i18n.php', 'module_update'], true)
194 * Последнее true в createDelegate добавляет параметры в квадратных скобках к уже существующему
195 * параметру e -- без true параметр e будет перезаписан указанными параметрами.
196 * @param {} e
197 * @param {} url
198 * @param {} action
199 */
200function generic_afteredit(e, url, action) {
201	Ext.Ajax.request({ 
202		url		: url, 
203		params	: {
204			action	: action, 
205			id		: e.record.id, 
206			field	: e.field, 
207			value	: e.value 					
208		}, 
209		
210		success: generic_afteredit_success.createDelegate(this, [e], true), 
211		failure: generic_afteredit_failure.createDelegate(this, [e], true)
212	}); // конец описания ajax-вызова	
213} // function generic_afteredit
214
215
216
217/**
218 * Типовая функция обработки ощибки при ajax-вызове
219 * @param {} resp
220 * @param {} opt
221 */
222function generic_ajax_failure(resp, opt) {
223	Ext.Msg.show({ 
224		title	: l.error, 
225		msg		: l.server_away, 
226		icon	: Ext.MessageBox.ERROR, 
227		buttons	: Ext.MessageBox.OK
228	});	
229} // function generic_ajax_failure(resp, opt)
230
231/**
232 * Типовая функция для выдачи сообщения об ошибке с диалоговым окном с пиктограммой
233 * @param {} msg
234 */
235function generic_error(msg) {
236	Ext.Msg.show({ 
237		title	: l.error, 
238		msg		: msg, 
239		icon	: Ext.MessageBox.ERROR, 
240		buttons	: Ext.MessageBox.OK
241	});		
242} // 
243
244/**
245 * Типовая функция успешной обработки ajax-вызова. В поле success возвращается признак 
246 * успешности обработки на стороне сервера. Если произошли ошибки, то errormsg содержит
247 * информацию о них.
248 * @param {} resp
249 * @param {} opt
250 */
251function generic_ajax_success(resp, opt, ok_message) {
252	response = Ext.decode(resp.responseText);
253	if (response.success) {
254		if (ok_message !== undefined) {
255			Ext.Msg.show({ 
256				title	: l.ready, 
257				msg		: ok_message, 
258				icon	: Ext.MessageBox.INFO, 
259				buttons	: Ext.MessageBox.OK
260			});				
261		} else if (response.ok_message !== undefined) {
262			Ext.Msg.show({ 
263				title	: l.ready, 
264				msg		: response.ok_message, 
265				icon	: Ext.MessageBox.INFO, 
266				buttons	: Ext.MessageBox.OK
267			});				
268		}
269	} else {
270		Ext.Msg.show({ 
271			title	: l.error, 
272			msg		: response.errormsg, 
273			icon	: Ext.MessageBox.ERROR, 
274			buttons	: Ext.MessageBox.OK
275		});				
276	}	
277} // function generic_ajax_success
278
279/**
280 * Типовая функция обработки сабмита формы
281 * @param {} form
282 * @param {} action
283 */
284function generic_form_failure(form, action) {
285	switch (action.failureType) {
286		case Ext.form.Action.CLIENT_INVALID:
287			Ext.Msg.show({ 
288				title	: l.error, 
289				msg		: l.invalid_data, 
290				icon	: Ext.MessageBox.ERROR, 
291				buttons	: Ext.MessageBox.OK
292			});	
293			break;
294
295		case Ext.form.Action.CONNECT_FAILURE:
296			Ext.Msg.show({ 
297				title	: l.error, 
298				msg		: l.server_away, 
299				icon	: Ext.MessageBox.ERROR, 
300				buttons	: Ext.MessageBox.OK
301			});	
302			break;					
303		
304		case Ext.form.Action.SERVER_INVALID:
305			if (form.form_window_id !== undefined) { Ext.getCmp(form.form_window_id).close(); }
306			Ext.Msg.show({ 
307				title	: l.error, 
308				msg		: action.result.errormsg, 
309				icon	: Ext.MessageBox.ERROR, 
310				buttons	: Ext.MessageBox.OK
311			});
312	} // switch (action.failureType)	
313} // function generic_form_failure
314
315
316function ajax_silent(url, params) {
317	Ext.Ajax.request({ 
318		url		: url, 
319		params	: params, 
320		success	: generic_ajax_success,
321		failure	: generic_ajax_failure
322	}); // конец описания ajax-вызова		
323}
324
325/**
326 * 
327 * @param {} p
328 * grid - ссылка на таблицу либо id таблицы
329 * action - действие, которое надо передать серверному скрипту
330 * url - имя серверного скрипта
331 * id_key - по умолчанию операция над записями выполняется по их id. Если в качестве id надо взять какое-то 
332 * 			другое поле, то следует указать этот параметр. В остальных случаях параметр опускаем.
333 * alertText - текст предупреждающего сообщения, которое появится перед тем, как будет выполнена операция
334 * 				(пользователь в этот момент еще может отменить выполнение)
335 * success_evt	- событие, которое будет инициировано при успешном завершении операции
336 */
337function processRows(p) {
338	p = p || {};
339	
340	var grid = (Ext.isString(p.grid)) ? Ext.getCmp(p.grid) : p.grid;
341	var selected_rows = grid.getSelectionModel().getSelections();  
342
343	if (selected_rows.length > 0) {
344		var ids = [];
345		for(var i = 0; i < selected_rows.length; i++) {
346			ids[i] = selected_rows[i].id; 
347		}
348		
349		var params = p;
350		params['ids[]'] = ids;
351		
352		Ext.MessageBox.confirm('Подтверждение', 
353			p.confirm_msg + '<br>Выбрано записей: ' + selected_rows.length, 
354			function (btn, text) {
355			if (btn == 'no') return;
356			Ext.Ajax.request({
357				url		: p.url,
358				params	: params,
359				scope	: this,
360				success	: function(resp, opt) {
361					response = Ext.decode(resp.responseText);
362					if (response.success) {
363						grid.getSelectionModel().clearSelections();
364						grid.getStore().reload();
365						if (undefined != p.success_event) {
366							grid.fireEvent(p.success_event);
367						}
368					} else {
369						showMsg.error(response.errormsg);
370					}
371				},
372				failure	: generic_ajax_failure
373			}); // Ext.Ajax.request
374		}); // Ext.MessageBox.confirm			
375	} else {
376		Ext.MessageBox.alert('Операция не выполнена', 'Не найдено выбранных строк в таблице.'); 
377	}
378}
379
380function task_checker() {
381	var task = {
382		run: function(){
383			/*
384	    	var date_str = new Date().format('g:i:s A');
385
386			Ext.Msg.show({ 
387				title	: 'Задание выполнено', 
388				msg		: 'Готово ' + date_str, 
389				icon	: Ext.MessageBox.INFO, 
390				buttons	: Ext.MessageBox.OK,
391				fn		: function() { Ext.TaskMgr.start(task); }
392			});	
393			Ext.TaskMgr.stop(task);
394			*/
395		},
396		interval: 60000 // 1 минута
397	};
398	Ext.TaskMgr.start(task);	
399}
400
401/**
402 * Полоса и таймер прогресса
403 * Запуск: ProgressHandler.start(action, msg_text='Идет обработка', interval=7000);
404 * 		action должен быть описан в periodical.php и возвращать: success, progress_total, progress_current, msg (опц.);
405 * 		в главном php-скрипте перед циклом сохранить в сессии progress_total: $_SESSION[<name>]['progress_total']
406 * 		а в теле цикла на каждом шаге сохранять progress_current: $_SESSION[<name>]['progress_current']
407 * 		по достижении порога (напр. 100) сохранять сессию на диск: session_write_close(); session_name(); session_start();
408 * Остановка: ProgressHandler.stop();
409 */
410var ProgressHandler = {
411	interval	: 7000, // 7 сек
412	progress_bar: null,
413	action		: '',
414	msg_text	: 'Идет обработка...',
415
416	start		: function(action, msg_text, interval){
417		if (interval) this.interval = interval;
418		if (msg_text) this.msg_text = msg_text;
419		this.progress_bar = Ext.Msg.progress('Подождите', this.msg_text, '0 %');
420		this.action = action;
421		Ext.TaskMgr.start(this);
422	},
423	stop		: function(){
424		Ext.TaskMgr.stop(this);
425		this.progress_bar.hide();
426//			this.progress_bar.updateProgress(1, '100 %');
427//			var obj = this;
428//			setTimeout(function(){obj.progress_bar.hide()}, 200);// задержка, чтобы показать финал 100%
429	},
430	run			: function(){
431		Ext.Ajax.request({
432			url		: '/periodical.php',
433			params	: { action: this.action},
434			timeout	: this.interval - 500, // мс -- через timeout (должен быть чуть меньше interval-а) запрос снимается, чтобы висящие Ajax-ы не копились
435			scope	: this,
436			success	: function(resp) {
437				var response = Ext.decode(resp.responseText);
438				if (response.success && response.progress_total > 0) {
439					var msg = response.msg ? this.msg_text + '<br />' + response.msg : '';
440					var percent = response.progress_current / response.progress_total;
441					this.progress_bar.updateProgress(percent, Math.round(percent*100) + ' % ('
442						+ response.progress_current + ' из ' + response.progress_total + ')', msg
443					);
444				}
445			}
446		}); 	
447	}
448};
449
450/**
451*	config - объект со следующими полями:
452* point, id, marker, status, text
453*/
454function createMarker_v4(config) {
455	var markerOptions = { hasBalloon: false, hasHint: true, style: free_icon };
456	free_icon.iconStyle.href = "http://" + server_name + "/img/markers/" + config.marker + ".png";
457
458	var marker = new YMaps.Placemark(config.point, markerOptions);
459	marker.setHintContent(config.text);
460	marker.setHintOptions( { maxWidth: 200} );
461	
462	YMaps.Events.observe(marker, marker.Events.Click, function() {
463			switch (config.mode) {
464				case 'monitoring_bulk_photo':
465					marker_make_red_v4(config.id);
466					// скроллим запись с указанным индексом в поле зрения
467					var grid = Ext.getCmp('related_photos_grid');
468					if (grid) {
469						var gridEl = grid.getGridEl();
470						var rowEl = grid.getView().getRow(config.index);
471						rowEl.scrollIntoView(gridEl, false);
472						grid.getView().focusRow(parseInt(config.index));
473					} 
474					break;
475			} // switch (config.mode)
476	});
477	return marker; 
478}
479
480var previous_marker_id = null;
481var previous_marker_style = null;
482
483function marker_make_red_v4(id) {
484	if(previous_marker_id != null) {
485		if (GMarkers['m' + previous_marker_id]) {
486			GMarkers['m' + previous_marker_id].setStyle(previous_marker_style);
487			GMarkers['m' + previous_marker_id].setOptions({zIndex: YMaps.ZIndex.OVERLAY});		
488		}
489	}
490
491	previous_marker_id = id;	
492	previous_marker_style = GMarkers['m' + id].getStyle();
493	GMarkers['m' + id].setStyle(red_icon);
494	GMarkers['m' + id].setOptions({zIndex: YMaps.ZIndex.OVERLAY_ACTIVE});	
495}
496/**
497 * Объект для упрощения показа простых инф. окон
498 */
499var showMsg = {
500	warning	: function(message_text){
501		Ext.Msg.show({title: 'ВНИМАНИЕ!', msg: message_text, icon: Ext.Msg.WARNING, buttons: Ext.Msg.OK});
502	},
503	error	: function(message_text){
504		if (!message_text) var message_text = l.server_away;
505		Ext.Msg.show({title: 'Ошибка', msg: message_text, icon: Ext.Msg.ERROR, buttons: Ext.Msg.OK});
506	},
507	info	: function(message_text, title_text){
508		if (!title_text) var title_text = l.info;
509		Ext.Msg.show({title: title_text, msg: message_text, icon: Ext.Msg.INFO, buttons: Ext.Msg.OK});
510	}
511	//не получилось,
512	//question: function(message_text, ok_text){var result = ''; Ext.Msg.show({title: 'Вопрос', msg: message_text, icon: Ext.Msg.QUESTION, buttons: {ok: ok_text, cancel: 'Отмена'}, fn : function(btn){ result = btn; }}); return result;}
513}
514/**
515 * Функция клонирования простых объектов/массивов
516 * @param {} object_to_copy
517 * @return {} copied_object
518 */
519function clone_obj(obj) {
520	if (!obj || typeof obj !== 'object') return obj;
521	var copy = typeof obj.pop === 'function' ? [] : {};
522	var prop, value;
523	for (prop in obj) {
524		if (obj.hasOwnProperty(prop)) {
525			value = obj[prop];
526			if (value && typeof value === 'object') {
527				copy[prop] = clone_obj(value);
528			}else{
529				copy[prop] = value;
530			}
531		}
532	}
533	return copy;
534}
535/**
536 * Аналоги PHP функций
537 */
538function ucfirst(str) {	// Make a strings first character uppercase, original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
539	var f = str.charAt(0).toUpperCase();
540	return f + str.substr(1, str.length-1);
541}
542
543function nl2br(str) { // Inserts HTML line breaks before all newlines in a string, original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
544//	return str.replace(/([^>])\n/g, '$1<br/>');
545	return str.replace(/\n/g, '<br/>');
546}
547
548function trim(str) {
549	//http://blog.stevenlevithan.com/archives/faster-trim-javascript
550	return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
551}
552
553function in_array( needle, haystack, strict ) { // Checks if a value exists in an array, original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
554	var strict = !!strict;
555	for(var key in haystack){
556		if ( strict && haystack[key] === needle || !strict && haystack[key] == needle ) return true;
557	}
558	return false;
559}
560
561// это override нужен для того, чтобы справиться с поведением, заложенными внутри функции onLoad 
562// в определении объекта Ext.grid.GridView - для браузеров Gecko там заложен отсроченный вызов
563// scrollToTop, который происходит после нашей принудительной прокрутки грида, вызываемого в 
564// обработчике refresh. Поэтому вводим дополнительное событие scrolled_to_top и принудительную 
565// прокрутку грида включаем в обработчик этого нового события.
566Ext.override(Ext.grid.GridView, {
567	scrollToTop : function() {
568		var dom = this.scroller.dom;
569		dom.scrollTop = 0;
570		dom.scrollLeft = 0;
571		this.fireEvent('scrolled_to_top', this);
572	} 
573});
574
575// нам надо иметь возможность ослеживать нажение кнопки "Обновить" в гриде, чтобы синхронно обновлять карту
576Ext.override(Ext.PagingToolbar, {
577	doRefresh : function(){
578		this.doLoad(this.cursor);
579		this.fireEvent('do_refresh', this);
580	}	
581});
582
583// у загрузчика дерева нам надо задавать базовые параметры
584Ext.override(Ext.tree.TreeLoader, {
585	setBaseParam	: function(param, value) {
586		this.baseParams = this.baseParams || {};
587		this.baseParams[param] = value;
588	}
589});
590
591// важный FIX: если не переопределить beforeBlur, то список выбранных элементов очищается,
592// когда мы покидаем комбобокс
593if (Ext.ux && Ext.ux.form && Ext.ux.form.LovCombo) {
594	Ext.override(Ext.ux.form.LovCombo, { beforeBlur: Ext.emptyFn }); 
595}
596
597// уровни прав доступа
598var ar_levels = new Ext.data.ArrayStore({ 
599	fields: ['id', 'descr'], 
600	data : [
601		['1', 'нет доступа'], 
602		['2', 'только чтение'],
603		['3', 'чтение и запись']]
604});
605
606// добавление [?]-подсказки к строке
607function addToolTip(text){
608	return '<span class="green" title="' + text + '">[?]</span>';
609}
610
611/**
612 * Функция возвращает true, если вместо данных из центральной базы в соответствующем поле
613 * надо возвращать данные, полученные из файла, который прислал оператор
614 * @param {} cluster_param
615 * @param {} wo_param
616 * 
617 * Приоритеты таковы: 
618 *  0 = не определено, следовательно, брать из настроек кластера;
619 * 	1 = брать из центральной БД;
620 *  2 = брать из файла
621 *  
622 *  Возвращаем true, если данные надо брать из центральной БД
623 */
624function use_db_vs_suppl_data(cluster_param, wo_param) {
625	return 	(wo__data[wo_param] == 1) ? true : 
626			(wo__data[wo_param] == 2) ? false :
627			cluster__data.wo_priorities && cluster__data.wo_priorities[cluster_param] == 1
628}
629
630/**
631 * при выдаче учитываем настройки кластера и проекта: какие данные показывать -- из 
632 * центральной БД или те, что были загружены из операторского файла с АП
633 * @param {} val
634 * @param {} meta
635 * @param {} rec
636 * @param {} row
637 * @param {} col
638 * @param {} store
639 * @param {} cluster_param
640 * @param {} wo_param
641 * @param {} wo_param_name
642 * @return {}
643 */
644function db_vs_suppl_data_renderer(val, meta, rec, row, col, store, cluster_param, wo_param, wo_param_name) {
645	if (use_db_vs_suppl_data(cluster_param, wo_param)) {
646		if (val == '' || val == null) {
647			meta.css = 'straw_bg';
648			return rec.data[wo_param_name];
649		} else {
650			return val;
651		}
652	} else {
653		if (rec.data[wo_param_name] == '' || rec.data[wo_param_name] == null) {
654			meta.css = 'straw_bg';
655			return val;	
656		} else {
657			return rec.data[wo_param_name];
658		}
659	}
660} // db_vs_suppl_data_renderer
661
662function access_level_renderer(val, meta, rec) {
663	switch(val) {
664		case 1: return "<img src='/img/red.png'>"; // нет доступа
665		case 2: return "<img src='/img/orange.png'>"; // только чтение
666		case 3: return "<img src='/img/gr.png'>"; // чтение и запись
667	}	
668}
669
670var MAX_DUMP_DEPTH = 10;
671
672function dumpObj(obj, name, indent, depth) {
673	name = name || 'obj';
674	indent = indent || '..';
675	if (depth > MAX_DUMP_DEPTH) {
676		return indent + name + ": <Maximum Depth Reached>\n";
677	}
678	if (typeof obj == "object") {
679		var child = null;
680		var output = indent + name + "\n";
681		indent += indent;
682		for (var item in obj) {
683			try {
684				child = obj[item];
685			} catch (e) {
686				child = "<Unable to Evaluate>";
687			}
688			if (typeof child != "function"){
689				if (typeof child == "object") {
690					output += dumpObj(child, item, indent, depth + 1);
691				} else {
692					output += indent + item + ": " + child + "\n";
693				}
694			}
695		}
696		return output;
697	} else {
698		return obj;
699	}
700}
701function alertObj(obj) {alert(dumpObj(obj));}
702
703/* коды клавиш dec/hex:
704 * 	end - 35/23
705 * 	home - 36/24
706 * 	left - 37/25
707 * 	right - 39/27
708 *  up - 38/26
709 *  down - 40/28
710 * 	delete - 46/2E
711 * 	backspace - 08/08 (в наборе символов [...] можно использовать обозначение \b, вне набора он имеет другой смысл)
712 *  tab - 09/09
713 *  enter - 13/D
714 *  esc - 27/1B
715 *  space - 32/20
716 */
717// шаблон ввода чисел + клавиши (можно исп. в свойстве maskRe полей формы)
718var number_input_pattern = /[\d\.\b\x25\x27\x23\x24]/;
719var read_only_pattern = /[\x23\x24\x25\x26\x27\x28]/;
720
721/*
722Ext.Ajax.on('requestcomplete', function (conn, response, options) {
723// пробуем декодировать ответ как JSON; чтобы ошибка не выбивала выполнение кода попытка заключена в защищенный блок;
724// при ошибке выводим responseText
725	try {
726		var test = Ext.decode(response.responseText);
727	} catch (e) { 
728		var error = response.responseText;
729		(error.indexOf('SQL') > 0)	? error = 'SQL query error.'
730									: error = 'PHP error ' + error.slice(error.indexOf('on line'), error.indexOf('{'));
731		alert(error.replace(/<\/?[^>]+>/gim, ''));
732		response.responseText = '{}';
733	}
734});
735*/
736
737// эти статусы считаем "свободными"
738function isFreeStatus(val) {
739	return ((val == 20) || (val == 1) || (val == 24) || (val == 21) || (val == 25));
740}
741
742/**
743 * Функция проверки права пользователя. Если передано val, то value проверяется строго
744 * @param {} access_right_type
745 * @param {} val
746 * @return {}
747 */
748function hasAR(access_right_type, val){
749	if (val) return wuser_ar[access_right_type] && wuser_ar[access_right_type].value == val;
750	return wuser_ar[access_right_type] && wuser_ar[access_right_type].value > 0;
751}
752
753function isFreeCluster() { return (cluster_id == 17); }
754function isZeroCluster() { return (cluster.cluster_id == 0 || cluster.cluster_id == 1); }
755
756function multiRowProcess(action, confirm_msg, action_url) {
757	// ожидаем, что handler передаст ссылку на грид в this.
758	var grid = this;
759	var selected_rows = grid.getSelectionModel().getSelections();	
760
761	if (selected_rows.length > 0) {
762		var ids = [];
763		for(var i = 0; i < selected_rows.length; i++) {
764			ids[i] = selected_rows[i].id; 
765		}
766		Ext.MessageBox.confirm('Подтверждение', confirm_msg, function (btn, text) {
767			if (btn == 'no') return;
768			Ext.Ajax.request({
769				url		: action_url,
770				params	: {
771					'action'	: action,
772					'ids[]'		: ids
773				},
774				scope	: this,
775				success	: function(resp, opt) {
776					response = Ext.decode(resp.responseText);
777					if (response.success) {
778						grid.getSelectionModel().clearSelections();
779						grid.getStore().reload();
780					} else {
781						showMsg.error(response.errormsg);
782					}
783				},
784				failure	: generic_ajax_failure
785			}); // Ext.Ajax.request
786		}); // Ext.MessageBox.confirm			
787	} else {
788		Ext.MessageBox.alert('Операция не выполнена', 'Не найдено выбранных строк в таблице.'); 
789	}
790}
791
792
793// Добавляем возможность задавать заголовки для свернутых северной и южной панелей и графические
794// вертикальные заголовки для восточной и западной панелей.
795Ext.override(Ext.layout.BorderLayout, {
796	northTitleAdded	: false,
797	southTitleAdded	: false,
798	eastTitleAdded	: false,
799	westTitleAdded	: false,
800
801	doCollapsedTitle : function (ct) {
802	    // TDGi custom title for any region
803	    // new config options for north/south regions:
804	    //  collapsedTitle     : 'string'
805	    //  collapsedTitleCls :  'string'
806	
807	    // new config options for east/westregions:
808	    //  collapsedTitleImg   : 'string'
809	    //  collapsedTitleCls   :  'string'
810	
811	    var cDiv = ct.collapsedEl;
812	
813	    if (ct.collapsedTitle) {
814	        var tpl  = new Ext.Template('<div style="float: left;">{txt}</div>');
815	        var insertedHtml = tpl.insertFirst(cDiv,{ txt : ct.collapsedTitle });
816	        if (ct.collapsedTitleStyle) {
817	            insertedHtml.applyStyles(ct.collapsedTitleStyle);
818	        }
819	        
820	        if (ct.collapsedTitleCls) {
821	            Ext.get(insertedHtml).addClass(ct.collapsedTitleCls);
822	        }
823	        return(true)
824	    } else if (ct.collapsedTitleImg) {
825	        var tpl  = new Ext.Template('<img style="padding: 0px;" src="{src}"></img>');
826	
827	        var insertedHtml = tpl.append(cDiv,{ src : ct.collapsedTitleImg });
828	        if (ct.collapsedTitleStyle) {
829	            insertedHtml.applyStyles(ct.collapsedTitleStyle);
830	        }
831	        
832	        if (ct.collapsedTitleCls) {
833	            Ext.get(insertedHtml).addClass(ct.collapsedTitleCls);
834	        }
835	        return(true)
836	    }
837	    else {
838	        console.info('wtf');
839	        return(false);
840	    }
841	},
842        
843	// private
844    onLayout : function(ct, target){
845
846        var collapsed;
847        if(!this.rendered){
848            
849            target.position();
850            target.addClass('x-border-layout-ct');
851            var items = ct.items.items;
852            collapsed = [];
853            for(var i = 0, len = items.length; i < len; i++) {
854                var c = items[i];
855                var pos = c.region;
856                if(c.collapsed){
857                    collapsed.push(c);
858                }
859                c.collapsed = false;
860                if(!c.rendered){
861                    c.cls = c.cls ? c.cls +' x-border-panel' : 'x-border-panel';
862                    c.render(target, i);
863                }
864                this[pos] = pos != 'center' && c.split ?
865                    new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
866                    new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
867                this[pos].render(target, c);
868            }
869            this.rendered = true;
870        }
871
872        var size = target.getViewSize();
873        if(size.width < 20 || size.height < 20){ // display none?
874            if(collapsed){
875                this.restoreCollapsed = collapsed;
876            }
877            return;
878        }else if(this.restoreCollapsed){
879            collapsed = this.restoreCollapsed;
880            delete this.restoreCollapsed;
881        }
882
883        var w = size.width, h = size.height;
884        var centerW = w, centerH = h, centerY = 0, centerX = 0;
885
886        var n = this.north, s = this.south, west = this.west, e = this.east, c = this.center;
887        if(!c){
888            throw 'No center region defined in BorderLayout ' + ct.id;
889        }
890
891        if(n && n.isVisible()){
892            var b = n.getSize();
893            var m = n.getMargins();
894            b.width = w - (m.left+m.right);
895            b.x = m.left;
896            b.y = m.top;
897            centerY = b.height + b.y + m.bottom;
898            centerH -= centerY;
899            n.applyLayout(b);
900        
901            //TDGi custom title for north
902            //new config options for north region:
903            //  collapsedTitle    : 'string'
904            //  collapsedTitleCls :  'string'
905            
906            if (typeof n.collapsedEl != 'undefined' && n.collapsedTitle && this.northTitleAdded == false) {
907                if (this.doCollapsedTitle(n)) {
908                    this.northTitleAdded = true;
909                }
910            }                    
911        }
912        if(s && s.isVisible()){
913            var b = s.getSize();
914            var m = s.getMargins();
915            b.width = w - (m.left+m.right);
916            b.x = m.left;
917            var totalHeight = (b.height + m.top + m.bottom);
918            b.y = h - totalHeight + m.top;
919            centerH -= totalHeight;
920            s.applyLayout(b);
921
922            //TDGi custom title for south
923            //new config options for south region:
924            //  collapsedTitle    : 'string'
925            //  collapsedTitleCls :  'string'
926            if (typeof s.collapsedEl != 'undefined' && s.collapsedTitle && this.southTitleAdded == false) {
927                if (this.doCollapsedTitle(s)) {
928                    this.southTitleAdded = true;
929                }
930            }                
931        }
932        if(west && west.isVisible()){
933            var b = west.getSize();
934            var m = west.getMargins();
935            b.height = centerH - (m.top+m.bottom);
936            b.x = m.left;
937            b.y = centerY + m.top;
938            var totalWidth = (b.width + m.left + m.right);
939            centerX += totalWidth;
940            centerW -= totalWidth;
941            west.applyLayout(b);
942
943            //TDGi custom title for west
944            //new config options for wset region:
945            //  collapsedTitleImg : 'string'
946            //  collapsedTitleCls :  'string'
947            if (typeof west.collapsedEl != 'undefined' && west.collapsedTitleImg && this.westTitleAdded == false) {
948                if (this.doCollapsedTitle(west)) {
949                    this.westTitleAdded = true;
950                }
951            
952            }                
953            
954        }
955        if(e && e.isVisible()){
956            var b = e.getSize();
957            var m = e.getMargins();
958            b.height = centerH - (m.top+m.bottom);
959            var totalWidth = (b.width + m.left + m.right);
960            b.x = w - totalWidth + m.left;
961            b.y = centerY + m.top;
962            centerW -= totalWidth;
963            e.applyLayout(b);
964            //TDGi custom title for east
965            //new config options for east region:
966            //  collapsedTitleImg : 'string'
967            //  collapsedTitleCls :  'string'
968            if (typeof e.collapsedEl != 'undefined' && e.collapsedTitleImg && this.eastTitleAdded == false) {
969                if (this.doCollapsedTitle(e)) {
970                    this.eastTitleAdded = true;
971                }
972            }                
973            
974        }
975
976        var m = c.getMargins();
977        var centerBox = {
978            x: centerX + m.left,
979            y: centerY + m.top,
980            width: centerW - (m.left+m.right),
981            height: centerH - (m.top+m.bottom)
982        };
983        c.applyLayout(centerBox);
984
985        if(collapsed){
986            for(var i = 0, len = collapsed.length; i < len; i++){
987                collapsed[i].collapse(false);
988            }
989        }
990
991        if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
992            target.repaint();
993        }
994    }
995});
996
997Ext.namespace('WO.panels');
998
999WO.panels.Window_Register = Ext.extend(Ext.Window, {
1000	title_box		: null,
1001	
1002	action_fld		: null,
1003	family_name_fld	: null,
1004	first_name_fld	: null,
1005	patronymic_fld	: null,
1006	company_fld		: null,
1007	phone_fld		: null,
1008	email_fld		: null,
1009	pass_fld		: null,
1010	pass2_fld		: null,
1011	form_panel		: null,
1012	
1013	reg_btn			: null,
1014	cancel_btn		: null,
1015	buttons_panel	: null,
1016	
1017	initComponent	: function() {
1018		this.title_box = new Ext.Container({
1019			width	: 550,
1020			height	: 50,
1021			cls		: 'reg_wnd_title',
1022			html	: "Регистрация на WebOutdoor"
1023		});
1024		this.action_fld = new Ext.form.Hidden({
1025			name			: 'action',
1026			value			: 'register'
1027		});
1028		this.family_name_fld = new Ext.form.TextField({ 
1029			name			: 'family_name',
1030			fieldLabel 		: '<b>Фамилия</b>', 
1031			allowBlank 		: false,
1032			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1033		});
1034		this.first_name_fld = new Ext.form.TextField({ 
1035			name			: 'first_name',
1036			fieldLabel 		: '<b>Имя</b>', 
1037			allowBlank 		: false,
1038			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1039		});
1040		this.patronymic_fld = new Ext.form.TextField({ 
1041			name			: 'patronymic',
1042			fieldLabel 		: 'Отчество',
1043			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1044		});
1045		this.company_fld = new Ext.form.TextField({ 
1046			name			: 'company',
1047			fieldLabel 		: 'Компания',
1048			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1049		});
1050		this.phone_fld = new Ext.form.TextField({ 
1051			name			: 'phone',
1052			fieldLabel 		: '<b>Контактный телефон</b>', 
1053			allowBlank 		: false,
1054			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1055		});
1056		this.email_fld = new Ext.form.TextField({ 
1057			name			: 'email',
1058			fieldLabel 		: '<b>E-mail</b>', 
1059			allowBlank 		: false,
1060		   	vtype			: 'email',
1061			maskRe			: /[a-z0-9_\.\-@\b\x25\x27\x23\x24\x09]/i,
1062			autoCreate		: {tag : 'input', type : 'text', size : '20', autocomplete : 'on'},
1063			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1064		});
1065		this.pass_fld = new Ext.form.TextField({
1066			name			: 'pass',
1067			fieldLabel		: '<b>Пароль</b>',
1068			inputType		: 'password',
1069			allowBlank 		: false,
1070			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1071		});
1072		this.pass2_fld = new Ext.form.TextField({
1073			name			: 'pass_cfm',
1074			fieldLabel		: '<b>Повтор пароля</b>',
1075			inputType		: 'password',
1076			allowBlank 		: false,
1077			listeners		: { specialkey : { scope : this, fn : this.submitOnEnter }}
1078		});		
1079		
1080		this.form_panel = new Ext.form.FormPanel({
1081			url			: '/login_ext.php',		
1082			labelWidth	: 230,
1083			width		: 550,
1084			border		: false,
1085			labelAlign	: 'right',
1086			defaults	: {
1087				anchor			: '95%',
1088			   	labelStyle		: 'font-size: 18px',
1089			   	style			: 'font-size: 18px',
1090			   	autoHeight		: true,
1091		   		validationDelay	: 1000			   	
1092			},
1093		  	items		: [this.action_fld, this.family_name_fld, this.first_name_fld, this.patronymic_fld, 
1094		  		this.company_fld, this.phone_fld, this.email_fld, this.pass_fld, this.pass2_fld]
1095		});
1096		
1097		this.reg_btn = new Ext.Button({
1098		  	text			: 'Регистрация',
1099		  	width			: 200,
1100		  	scale 			: 'large',
1101		  	cls 			: 'big_btn',
1102		  	scope			: this,
1103		  	handler			: this.submitRegistration
1104		});
1105		this.cancel_btn = new Ext.Button({
1106		  	text			: 'Отмена',
1107		  	width			: 200,
1108		  	scale 			: 'large',
1109		  	cls 			: 'big_btn',
1110		  	scope			: this,
1111		  	handler			: function() { this.hide(); }			
1112		});
1113		
1114		this.buttons_panel = new Ext.Container({
1115			layout	: { type : 'hbox', pack : 'center' },
1116			height	: 50,
1117			width	: 550,
1118			items	: [this.reg_btn, {
1119				xtype	: 'box',
1120				width	: 30				
1121			}, this.cancel_btn]
1122		});
1123	
1124		var config = {
1125			layout		: { type : 'vbox', align : 'center' },
1126		  	closable	: true,
1127		  	draggable	: false,
1128		  	resizable	: false,
1129		  	width		: 600,
1130		  	height 		: 500,
1131		 	modal 		: true,
1132		 	cls			: 'main_www_window',
1133		  	items		: [
1134		  		{ xtype : 'box', flex : 1 }, 
1135		  		this.title_box,
1136		  		{ xtype : 'box', flex : 1 }, 
1137		  		this.form_panel, 
1138		  		{ xtype : 'box', flex : 1 }, 
1139		  		this.buttons_panel, 
1140		  		{ xtype : 'box', flex : 1 }
1141		  	]			
1142		};
1143		Ext.apply(this, Ext.apply(this.initialConfig, config));
1144		WO.panels.Window_Register.superclass.initComponent.apply(this, arguments);		
1145	}, // initComponent
1146	
1147	/**
1148	 * Отправка регистрационных данных
1149	 */
1150	submitRegistration	: function() {
1151  		if (this.pass_fld.getValue() != this.pass2_fld.getValue()) {
1152  			showMsg.error('Пароль и его проверочное значение (повтор пароля) не совпадают!');	
1153  		} else {
1154			this.lockButtons(true);
1155			this.form_panel.getForm().submit({
1156				scope	: this,
1157				success	: function(form, action){ 
1158					this.lockButtons(false);					
1159					this.hide();
1160					showMsg.warning(action.result.msg);
1161					
1162					try { _gaq.push(["_trackPageview", "/index_mm.php#register_ok"]); } catch(e) {}	
1163					try {
1164						yaCounter.hit('/index_mm.php#register_ok', 'Регистрация');
1165						yaCounter.reachGoal('REGISTER_OK');
1166					} catch(e) {}					
1167				}, 
1168				failure	: function(form, action) {
1169					this.lockButtons(false);
1170					generic_form_failure(form, action);
1171				}
1172			});	
1173  		}		
1174	}, // submitRegistration
1175	
1176	submitOnEnter	: function(field, event) {
1177		if (event.getKey() == event.ENTER) { 
1178			this.submitRegistration();
1179		}
1180	}, // submitOnEnter
1181	
1182	/**
1183	 * Блокируем кнопки на время отправки формы, чтобы не было повторных срабатываний
1184	 * @param {} lock
1185	 */
1186	lockButtons	: function(lock) {
1187		if (lock) {
1188  			this.reg_btn.disable();
1189  			this.cancel_btn.disable();
1190  			this.reg_btn.setText('Подождите...');			
1191		} else {
1192			this.reg_btn.enable();
1193			this.cancel_btn.enable();
1194			this.reg_btn.setText('Регистрация');			
1195		}
1196	} // lockButtons
1197});
1198
1199WO.panels.Window_Login = Ext.extend(Ext.Window, {
1200	redirect		: '',
1201	logo			: null,
1202	action_fld		: null,
1203	email_fld		: null,
1204	pass_fld		: null,
1205	remember_me_chk	: null,
1206	remember_me		: null,
1207	fields			: null,
1208	buttons			: null,
1209	buttons2		: null,
1210	reg_invite		: null,
1211
1212	initComponent	: function() {
1213		this.logo = new Ext.Container({
1214			width	: 250,
1215			height	: 100,
1216			html	: "<img src='/img/woot.png'>"
1217		});
1218		this.action_fld = new Ext.form.Hidden({
1219			name			: 'action',
1220			value			: 'login'
1221		});
1222		this.email_fld = new Ext.form.TextField({
1223			name			: 'email',
1224			fieldLabel		: 'E-mail',
1225		   	labelStyle		: 'font-size: 18px',
1226		   	style			: 'font-size: 18px',
1227		   	autoHeight		: true,
1228		   	allowBlank		: false,
1229		   	validationDelay	: 1000, 
1230		   	vtype			: 'email',
1231		   	maskRe			: /[a-z0-9_\.\-@\b\x25\x27\x23\x24\x09]/i,
1232		   	autoCreate		: {tag : 'input', type : 'text', size : '20', autocomplete : 'on'},
1233		   	listeners		: { specialkey	: { scope : this, fn : this.submitOnEnter }}
1234		});	
1235		this.pass_fld = new Ext.form.TextField({
1236			name			: 'pass',
1237			fieldLabel		: 'Пароль',
1238			inputType		: 'password',
1239		   	labelStyle		: 'font-size: 18px',
1240		   	style			: 'font-size: 18px',
1241		   	autoHeight		: true,
1242		   	allowBlank		: false,
1243		   	validationDelay	: 1000,
1244		   	listeners		: { specialkey	: { scope : this, fn : this.submitOnEnter }}
1245		});
1246		this.remember_me_chk = new Ext.form.Checkbox({ 
1247			name			: 'autologin',
1248			checked 		: true 
1249		});
1250		this.remember_me = new Ext.form.CompositeField({
1251			fieldLabel		: '',
1252			items			: [this.remember_me_chk, {
1253				xtype		: 'displayfield',
1254				value		: 'запомнить меня на этом компьютере'
1255			}]
1256		});
1257
1258		this.fields = new Ext.form.FormPanel({
1259			url			: '/login_ext.php',
1260			labelWidth	: 80,
1261			width		: 350,
1262			border		: false,
1263			labelAlign	: 'right',
1264			defaults	: { anchor : '95%' },
1265		  	items		: [this.action_fld, this.email_fld, this.pass_fld, this.remember_me]
1266		});
1267
1268		this.buttons = new Ext.Container({
1269			layout	: { type : 'hbox', pack : 'center' },
1270			height	: 50,
1271			width	: 400,
1272			items	: [{
1273			  	xtype			: 'button',
1274			  	text			: 'Войти',
1275			  	width			: 100,
1276			  	scale 			: 'large',
1277			  	cls 			: 'big_btn',
1278			  	scope			: this,
1279			  	handler			: this.submitLogin
1280			}, {
1281				xtype			: 'box',
1282				width			: 30
1283			}, {
1284			  	xtype			: 'button',
1285			  	text			: 'Отмена',
1286			  	width			: 100,
1287			  	scale 			: 'large',
1288			  	cls 			: 'big_btn',
1289			  	scope			: this,
1290			  	handler			: function(){ this.hide(); }
1291			}]
1292		});
1293		this.buttons2 = new Ext.Container({
1294			layout	: { type : 'hbox', pack : 'center' },
1295			height	: 30,
1296			width	: 400,
1297			items	: [{
1298			  	xtype			: 'button',
1299			  	text			: 'Вспомнить пароль',
1300			  	width			: 170,
1301			  	scope			: this,
1302			  	handler			: this.recoverPass
1303			}]
1304		});
1305
1306		this.form_holder = new Ext.Container({
1307			layout	: { type : 'vbox', align : 'center' },
1308			height 	: 190,
1309			items	: [
1310				this.fields,
1311				{ xtype : 'box', flex : 1 }, 
1312				this.buttons,
1313				{ xtype : 'box', flex : 1 },
1314				this.buttons2
1315			]
1316		});
1317		
1318		this.reg_invite = new Ext.Container({
1319			width	: 300,
1320			height	: 30,
1321			style	: 'font-size: 18px; text-align: center; cursor: pointer; ',
1322			html	: "Нет логина? <a class='agreen'>Зарегистрируйтесь!</a>",
1323		    listeners: {
1324		    	// box и его потомки не имеет события click, поэтому после рендера навешиваем его
1325		    	// на HTML элемент, в котором box содержится
1326		    	// single : true тоже важно, потому как обработчик надо назначить всего один раз
1327		        'render': { scope: this, single : true, fn : function() {
1328	                this.reg_invite.getEl().on('click', function() { 
1329	                	this.goRegister();
1330	                }, this);
1331		   		}} // render
1332		    } // listeners				
1333		});		
1334
1335		var config = {
1336			layout		: { type : 'vbox', align : 'center' },
1337		  	closable	: true,
1338		  	draggable	: false,
1339		  	resizable	: false,
1340		  	width		: 425,
1341		  	height 		: 410,
1342		 	modal 		: true,
1343		 	cls			: 'main_www_window',
1344		  	items		: [
1345		  		{ xtype : 'box', flex : 1 },
1346		  		this.logo, 
1347		  		{ xtype : 'box', flex : 1 }, 
1348		  		this.form_holder,
1349		  		{ xtype : 'box', flex : 1 },
1350		  		this.reg_invite,
1351		  		{ xtype : 'box', flex : 1 }
1352		  	]
1353		};
1354		Ext.apply(this, Ext.apply(this.initialConfig, config));
1355		WO.panels.Window_Login.superclass.initComponent.apply(this, arguments);
1356	}, // initComponent
1357
1358	submitLogin		: function() {
1359		if (!this.pass_fld.getValue()) this.pass_fld.markInvalid();
1360		if (this.email_fld.isValid() && this.pass_fld.getValue()) {
1361			this.form_holder.setDisabled(true);
1362			this.fields.getForm().submit({
1363				scope	: this,
1364				success	: function(form, action){ 
1365					this.hide();
1366					if (this.redirect === undefined) {
1367						window.location.reload();
1368					} else {
1369						window.location.href = this.redirect;
1370					}
1371				},
1372				failure	: function(form, action){
1373					this.form_holder.setDisabled(false);
1374					generic_form_failure(form, action);
1375				}
1376			});						
1377		}
1378	},// submitLogin
1379
1380	submitOnEnter	: function(field, event) {
1381		if (event.getKey() == event.ENTER) { 
1382			this.submitLogin();
1383		}
1384	},// submitOnEnter
1385
1386	recoverPass		: function(btn, evt) {
1387		if (this.email_fld.isValid()) {
1388			btn.setText('Подождите, пожалуйста...');
1389			this.form_holder.setDisabled(true);
1390			Ext.Ajax.request({ 
1391				url		: '/login_ext.php', 
1392				params	: {
1393					action	: 'recover_pass_01', 
1394					email	: this.email_fld.getValue()
1395				}, 
1396				scope	: this,
1397				success	: function(resp, opt) {
1398					response = Ext.decode(resp.responseText);
1399					if (response.success) {
1400						this.hide();
1401						showMsg.info(response.msg, 'Восстановление пароля');
1402					} else {
1403						btn.setText(btn.initialConfig.text);
1404						this.form_holder.setDisabled(false);
1405						showMsg.error(response.errormsg);
1406					}											
1407				},
1408				failure	: function(resp, opt) {
1409					btn.setText(btn.initialConfig.text);
1410					this.form_holder.setDisabled(false);
1411					generic_ajax_failure(resp, opt);
1412				}
1413			}); // конец описания ajax-вызова						
1414		} else {
1415			showMsg.error('Укажите адрес электронной почты, для которого надо восстановить пароль.');
1416		}
1417	}, // recoverPass
1418
1419	goRegister	: function() {
1420		this.hide();
1421		RegisterExt();
1422	}
1423});
1424
1425function RegisterExt() {
1426	var win = new WO.panels.Window_Register();
1427	win.show();
1428}
1429
1430function LoginExt(redirect) {
1431	var win = new WO.panels.Window_Login({redirect: redirect});
1432	win.show();
1433}
1434
1435WO.panels.Window_Payment = Ext.extend(Ext.Window, {
1436	title_box		: null,
1437	action_fld		: null,
1438	wuser_fld		: null,
1439	sum_fld			: null,
1440	note_fld		: null,
1441	fields			: null,
1442	buttons			: null,
1443	buttons2		: null,
1444
1445	initComponent	: function() {
1446		this.title_box = new Ext.Container({
1447			width	: 400,
1448			height	: 50,
1449			cls		: 'reg_wnd_title',
1450			html	: "Пополнение счета"
1451		});
1452		this.action_fld = new Ext.form.Hidden({
1453			name			: 'action',
1454			value			: 'robokassa_payment'
1455		});
1456		this.wuser_fld = new Ext.form.DisplayField({
1457			fieldLabel		: 'Учетная запись',
1458		   	labelStyle		: 'font-size: 18px',
1459		   	style			: 'font-size: 18px',
1460		   	autoHeight		: true,	
1461		   	value			: wuser_data.wuser_first_name + ' ' + wuser_data.wuser_family_name
1462		});
1463		this.sum_fld = new Ext.form.NumberField({
1464			name			: 'sum',
1465			fieldLabel		: 'Сумма платежа',
1466		   	labelStyle		: 'font-size: 18px',
1467		   	style			: 'font-size: 18px',
1468		   	autoHeight		: true,
1469		   	allowBlank		: false,
1470		   	width			: 100,
1471		   	minValue		: 100,
1472		   	maxValue		: 50000,
1473		   	value			: 1000,
1474		   	listeners		: { specialkey	: { scope : this, fn : this.submitOnEnter }}
1475		});	
1476		this.note_fld = new Ext.form.DisplayField({
1477			hideLabel		: true,
1478			value			: '<br />Сумма платежа должна быть не менее 100 р. и не более 50000 р.' +
1479					'<br /><br />Для совершения платежа вы будете перенаправлены на сайт платежной ' +
1480					'системы Robokassa.'
1481		});
1482
1483		this.fields = new Ext.form.FormPanel({
1484			url			: '/index_mm.php',
1485			labelWidth	: 150,
1486			width		: 400,
1487			border		: false,
1488		  	items		: [this.action_fld, this.wuser_fld, {
1489		  		xtype	: 'compositefield',
1490			   	labelStyle		: 'font-size: 18px',
1491			   	style			: 'font-size: 18px',		  		
1492		  		items	: [this.sum_fld, {
1493		  			xtype	: 'label',
1494		  			text	: 'рублей'
1495		  		}]
1496		  	}, this.note_fld]
1497		});
1498
1499		this.buttons = new Ext.Container({
1500			layout	: { type : 'hbox', pack : 'center' },
1501			height	: 50,
1502			width	: 450,
1503			items	: [{
1504			  	xtype			: 'button',
1505			  	text			: 'Пополнить счет',
1506			  	width			: 180,
1507			  	scale 			: 'large',
1508			  	cls 			: 'big_btn',
1509			  	scope			: this,
1510			  	handler			: this.submitPayment
1511			}, {
1512				xtype			: 'box',
1513				width			: 30
1514			}, {
1515			  	xtype			: 'button',
1516			  	text			: 'Отмена',
1517			  	width			: 180,
1518			  	scale 			: 'large',
1519			  	cls 			: 'big_btn',
1520			  	scope			: this,
1521			  	handler			: function(){ this.hide(); }
1522			}]
1523		});
1524
1525		this.form_holder = new Ext.Container({
1526			layout	: { type : 'vbox', align : 'center' },
1527			height 	: 220,
1528			items	: [
1529				this.fields,
1530				{ xtype : 'box', flex : 1 }, 
1531				this.buttons
1532			]
1533		});
1534
1535		var config = {
1536			layout		: { type : 'vbox', align : 'center' },
1537		  	closable	: true,
1538		  	draggable	: false,
1539		  	resizable	: false,
1540		  	width		: 500,
1541		  	height 		: 340,
1542		 	modal 		: true,
1543		 	cls			: 'main_www_window',
1544		  	items		: [
1545		  		{ xtype : 'box', flex : 1 },
1546		  		this.title_box, 
1547		  		{ xtype : 'box', flex : 1 }, 
1548		  		this.form_holder,
1549		  		{ xtype : 'box', flex : 1 }
1550		  	]
1551		};
1552		Ext.apply(this, Ext.apply(this.initialConfig, config));
1553		WO.panels.Window_Payment.superclass.initComponent.apply(this, arguments);
1554	}, // initComponent
1555
1556	submitPayment		: function() {
1557		this.form_holder.setDisabled(true);
1558		this.fields.getForm().submit({
1559			scope	: this,
1560			success	: function(form, action){ 
1561				this.hide();
1562				Go(action.result.url);
1563			},
1564			failure	: function(form, action){
1565				this.form_holder.setDisabled(false);
1566				generic_form_failure(form, action);
1567			}
1568		});						
1569	},// submitPayment
1570
1571	submitOnEnter	: function(field, event) {
1572		if (event.getKey() == event.ENTER) { this.submitPayment(); }
1573	}// submitOnEnter
1574});
1575
1576function goRobokassa() {
1577	if (wuser_id > 0) {
1578		var win = new WO.panels.Window_Payment();
1579		win.show();
1580	} else {
1581		LoginExt('/mm_viewer.php');
1582	}	
1583}
1584
1585function log(msg) {
1586	if (window.console) {
1587		var ddd = new Date(); 
1588		var min = ddd.getMinutes();
1589		var sec = ddd.getSeconds();
1590		if (min < 10) { min = "0" + min; }
1591		if (sec < 10) { sec = "0" + sec; }
1592		console.log(ddd.getHours() + ':' + min + ':' + sec + '.'  + ddd.getMilliseconds() + ' ' + msg);	
1593	}
1594}
1595
1596function checkcolumn_green_renderer(val, meta, rec) {
1597	if (val > 0) { meta.css += ' green_bg'; }
1598    // + родной рендерер
1599    meta.css += ' x-grid3-check-col-td';
1600    return String.format('<div class="x-grid3-check-col{0}">&#160;</div>', val ? '-on' : '');
1601}
1602
1603function cellSelectable() {
1604	return new Ext.Template('<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} x-selectable {css}" style="{style}" tabIndex="0" {cellAttr}>',
1605	   '<div title="{cellTitle}" class="x-grid3-cell-inner x-grid3-col-{id}" {attr}>{value}</div>',
1606	   '</td>'
1607	);
1608}

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.