(function (global, p5VendorJs) {
	if (!p5VendorJs.React) throw "Missing React"
	if (!p5VendorJs.ReactDOM) throw "Missing ReactDOM"
	if (!p5VendorJs.createReactClass) throw "Missing createReactClass"
	var React = p5VendorJs.React
	var ReactDOM = p5VendorJs.ReactDOM
	var createReactClass = p5VendorJs.createReactClass
	var h = React.createElement
	var DBG = 0

	// global.p5VendorJs['P5UI__MyWidget'] = createReactClass({
	// 	componentWillMount: function () { console.log('MyWidget::componentWillMount...'); },
	// 	render: function () {
	// 		console.log('MyWidget::render... state, props', this.state, this.props);
	// 		var defaultProps = {}
	// 		return h('div', Object.assign(defaultProps, {
	// 			ref: function (node) { this.rootNode = node; }.bind(this),
	// 			// onSubmit: this.submit.bind(this)
	// 		}), this.props.children)
	// 	},
	// 	componentDidMount: function () { console.log('MyWidget::componentDidMount...'); },
	// 	componentWillReceiveProps: function (nextProps) { console.log('MyWidget::componentWillReceiveProps(nextProps)...', nextProps); },
	// 	shouldComponentUpdate: function (nextProps, nextState) { console.log('MyWidget::shouldComponentUpdate(nextProps, nextState)...', nextProps, nextState); },
	// 	componentWillUpdate: function (nextProps, nextState) { console.log('MyWidget::componentWillUpdate(nextProps, nextState)...', nextProps, nextState); },
	// 	componentDidUpdate: function (prevProps, prevState) { console.log('MyWidget::componentDidUpdate(prevProps, prevState)...', prevProps, prevState); },
	// 	componentWillUnmount: function () { console.log('MyWidget::componentWillUnmount...'); },
	// })

	var P5UI__RawHtml = createReactClass({
		componentDidMount: function () {
			if (this.props.rawHtml) {
				this.rootNode.innerHTML = this.props.rawHtml
			}
		},
		render: function () {
			return h('div', {
				ref: function (node) { this.rootNode = node; }.bind(this),
			})
		}
	})
	global.p5VendorJs['P5UI__RawHtml'] = P5UI__RawHtml

	var P5UI__FeatureEditForm = createReactClass({
		componentDidMount: function () {
			if(DBG)console.warn('P5UI__FeatureEditForm::componentDidMount this.rootNode', this.rootNode)
			jQuery(this.rootNode).find('textarea').autosize();
			jQuery(this.rootNode).find('.frm-help').popover({trigger:'hover'});
			jQuery(this.rootNode).find('.show-last-value input').on('input', function(e) {
				var input, btn, btnIco;
				input = jQuery(e.target);
				btn = input.next('.button-appendBack');
				btnIco = btn.find('.glyphicon');
				if (btn.attr('title') != input.val()) {
					btnIco.show();
				} else {
					btnIco.hide();
				}
			});
			jQuery(this.rootNode).find('.show-last-value').find('.button-appendBack').on('click', function(e) {
				var input, btn, btnIco;
				btn = jQuery(this);
				btnIco = btn.find('.glyphicon');
				input = btn.prev();
				if (input.is('input')) {
					if (btn.attr('title') != input.val()) {
						input.val(btn.attr('title'));
						btnIco.hide();
					}
				}
			});
		},
		submit: function (e) {
			if(DBG)console.warn('P5UI__FeatureEditForm::submit this.rootNode', this.rootNode)
			e.preventDefault()

			var formData = {};
			jQuery(this.rootNode).serializeArray().map(function(i) { formData[i.name] = i.value; });// TODO: edit Widget - send only updated fields
			// TODO: change Edit btn to return to edit record with fields -> show form

			var taskCont = jQuery(this.rootNode).parents('.AjaxTableTaskCnt').parent(); // jQuery('#{$this->_htmlID}_task').parent();
			//taskCont.empty();
			taskCont.children().fadeOut('slow');
			var alertCntWrap = jQuery('<div class="AjaxTableAlert AjaxTable-loading"></div>').prependTo(taskCont)
				, alertCnt = jQuery('<div class="container"></div>').prependTo(alertCntWrap);
			jQuery('<div class="alert alert-info"><div style="padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;"> zapisywanie ... </div></div>').appendTo(alertCnt);

			function notifyAjaxCallback(data) {
				var notify = {}, outMsg = '';
				notify.type = (data && data.type)? data.type : '';
				notify.msg = (data && data.msg)? data.msg : '';
				switch (notify.type) {
					case 'success':
						if (!notify.msg) notify.msg = 'Dane poprawnie zaktualizowane';
						break;
					case 'info':
						if (!notify.msg) notify.msg = 'Nie wprowadzono żadnych zmian';
						break;
					case 'error':
						if (!notify.msg) notify.msg = 'Wystąpiły błędy';
						break;
					case 'warning':
						notify.type = 'warn';
						if (!notify.msg) notify.msg = 'Wystąpiły błędy';
						break;
					default:
						notify.msg = 'Nieznany błąd';
						if (data && data.errorCode) notify.msg += ' ' + data.errorCode;
						notify.type = '';
				}
				jQuery.notify(notify.msg, notify.type);
				var alertType = ('error' == data.type) ? 'danger' : data.type;
				outMsg = '<div class="alert alert-' + alertType + '">' + notify.msg + '</div>';
				return outMsg;
			}

			var idRecord = this.props.idRecord
			var tableLabelHtml = this.props.tableLabelHtml
			superagent
				.post(this.props.ajaxSaveUrl)
				.type('json') // header ĺapplication/x-www-form-urlencoded' requires type('form');
				.send({
					namespace: this.props.namespace,
					primaryKey: this.props.idRecord,
					form: formData
				})
				.set('Accept', 'application/json')
				.end(function(err, res) {
					var payload;
					if (err || !res.ok || 'application/json' !== res.type) {
						payload = {type: 'warning', msg: res.body.msg || 'Wystąpiły błędy', body: res.body};
					} else {
						payload = {type: 'success', msg: res.body.msg || '', body: res.body};
					}
					var data = res.body;

					alertCntWrap.removeClass('AjaxTable-loading');
					alertCnt.empty();
					if (false === ['success', 'info'].indexOf(payload.type)) {
						jQuery(errorTxt).appendTo(alertCnt);
						var errLinks = jQuery('<div class="breadcrumb"></div>').appendTo(alertCnt);
						jQuery('<a href="#" onclick="return tableAjaxBackToTable();"> <i class="icon-arrow-left"></i> Wróć do tabeli '+tableLabelHtml+'</a>').appendTo(errLinks);
						var backToEditBtn = jQuery('<a href="#EDIT/'+idRecord+'/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-small"> <i class="icon-pencil"></i> Popraw dane '+idRecord+'</a>').appendTo(errLinks);
						backToEditBtn.on('click', function(){
							alertCnt.remove();
							taskCont.children().fadeIn('slow');
							return false;
						});
					} else {
						var outMsg = notifyAjaxCallback(payload);
						var out = '';
						out += outMsg;
						out += '<div class="breadcrumb">' +
								' <a href="#" onclick="return tableAjaxBackToTable();" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-arrow-left"></i> Wróć do tabeli '+tableLabelHtml+'</a>' +
								' <a href="#EDIT/'+idRecord+'/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-pencil"></i> Edytuj rekord '+idRecord+'</a>' +
							'</div>';
						jQuery(out).appendTo(alertCnt);

						// add rowFunctions from response
						if (data && data.rowFunctions && data.primaryKey) {
							var rowFunWrapNode = $('<div class="container"></div>').insertAfter(alertCnt);
							var rowFunListNode = $('<ul></ul>').appendTo(rowFunWrapNode);
							var keys = Object.keys(data.rowFunctions),
									total = keys.length,
									moreFuncBtnNode,
									moreFunctions = [],
									idx
							;
							moreFunctions = keys.splice(3);
							keys.forEach(function(key) {
								var cellNode = $('<li></li>');
								var funObj = data.rowFunctions[key],
										funcNode = p5UI_TableAjax_generateFunctionNode(funObj, data.primaryKey, {ico: true, label: true})
								;
								funcNode.appendTo(cellNode);
								cellNode.appendTo(rowFunListNode);
							});
						}
					}
				})
		},
		render: function () {
			if(DBG)console.warn('P5UI__FeatureEditForm::render this.props', this.props)
			var formProps = Object.assign({}, this.props)
			delete formProps.children
			return h('form', Object.assign(formProps, {
				ref: function (node) { this.rootNode = node; }.bind(this),
				onSubmit: this.submit.bind(this)
			}), this.props.children)
		}
	})
	global.p5VendorJs['P5UI__FeatureEditForm'] = P5UI__FeatureEditForm

	var P5UI__FeatureCreateForm = createReactClass({
		componentDidMount: function () {
			if(DBG)console.warn('P5UI__FeatureCreateForm::componentDidMount this.rootNode', this.rootNode)
			jQuery(this.rootNode).find('textarea').autosize();
			jQuery(this.rootNode).find('.frm-help').popover({trigger:'hover'});
			jQuery(this.rootNode).find('.show-last-value input').on('input', function(e) {
				var input, btn, btnIco;
				input = jQuery(e.target);
				btn = input.next('.button-appendBack');
				btnIco = btn.find('.glyphicon');
				if (btn.attr('title') != input.val()) {
					btnIco.show();
				} else {
					btnIco.hide();
				}
			});
			jQuery(this.rootNode).find('.show-last-value').find('.button-appendBack').on('click', function(e) {
				var input, btn, btnIco;
				btn = jQuery(this);
				btnIco = btn.find('.glyphicon');
				input = btn.prev();
				if (input.is('input')) {
					if (btn.attr('title') != input.val()) {
						input.val(btn.attr('title'));
						btnIco.hide();
					}
				}
			});
		},
		submit: function (e) {
			if(DBG)console.warn('P5UI__FeatureCreateForm::submit this.rootNode', this.rootNode)
			e.preventDefault()

			var formData = {};
			jQuery(this.rootNode).serializeArray().map(function(i) { formData[i.name] = i.value; });// TODO: edit Widget - send only updated fields
			if(DBG)console.log('P5UI__FeatureCreateForm::submit formData', formData);

			var taskContLast = jQuery('#' + this.props._htmlID + '_task'),
				taskCont = taskContLast.parent();
			taskContLast.fadeOut('slow');
			var taskCnt = jQuery('<div id="' + this.props._htmlID + '_task" class="AjaxTableTaskCnt AjaxTable-loading"></div>').appendTo(taskCont);
			jQuery('<div class="alert alert-danger"><div style="padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;"> save ... </div></div>').appendTo(taskCnt);

			function notifyAjaxCallback(data) {
				var notify = {}, outMsg = '';
				notify.type = (data && data.type)? data.type : '';
				notify.msg = (data && data.msg)? data.msg : '';
				switch (notify.type) {
					case 'success':
						if (!notify.msg) notify.msg = 'OK';
						break;
					case 'info':
						if (!notify.msg) notify.msg = '';
						break;
					case 'error':
						if (!notify.msg) notify.msg = 'Wystąpiły błędy';
						break;
					case 'warning':
						notify.type = 'warn';
						if (!notify.msg) notify.msg = 'Wystąpiły błędy';
						break;
					default:
						notify.msg = 'Nieznany błąd';
						if (data && data.errorCode) notify.msg += ' ' + data.errorCode;
						notify.type = '';
				}
				jQuery.notify(notify.msg, notify.type);
				var alertType = ('error' == data.type) ? 'danger' : data.type;
				outMsg = '<div class="alert alert-' + alertType + '">' + notify.msg + '</div>';
				return outMsg;
			}

			var tableLabelHtml = this.props.tableLabelHtml;
			global.fetch(this.props.ajaxSaveLegacyUrl, {
				headers: {
					'Content-Type': 'application/json'
				},
				method: 'POST',
				credentials: 'same-origin',
				body: JSON.stringify(formData),
			}).then(function (response) {
				return response.json()
			}).then(function (data) {
				taskCnt.removeClass('AjaxTable-loading');
				taskCnt.empty();
				if(DBG)console.log('P5UI__FeatureCreateForm:: data: ', data)
				if ('success' === data.type) {
					var msg = 'OK';
					if (data.id && data.id > 0) msg = 'Utworzono pomyślnie rekord: ID = ' + data.id;
					else if (data.msg) msg = data.msg;
					var out = '<div class="container">';
					out += '<div class="alert alert-success">' + msg + '</div>';
					out += '<div class="breadcrumb">' +
							' <a href="#" onclick="return tableAjaxBackToTable();" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-arrow-left"></i> Wróć do tabeli ' + tableLabelHtml + '</a>' +
							' <a href="#EDIT/' + data.id + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-pencil"></i> Edytuj rekord ' + data.id + '</a>' +
							' <a href="#CREATE/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-plus"></i> Dodaj nowy rekord</a></li>' +
						'</div>';
					out += '</div>';
					jQuery(out).appendTo(taskCnt);
				} else {
					var msg = (data.msg) ? data.msg : "Nieznany błąd!";
					var out = '<div class="container">' +
							'<div class="alert alert-danger">' +
								'<h4>Wystąpiły błędy!</h4>' + msg +
							'</div>';
					out += '<div class="breadcrumb">' +
								' <a class="create-fix" href="#CREATE/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-arrow-left"></i> Wróć do formularza i popraw dane</a></li>' +
							'</div>' +
						'</div>';
					jQuery(out).appendTo(taskCnt);
					var taskContLastNode = taskContLast
						, taskCntNode = taskCnt;
					taskCnt.find('.create-fix').click(function(){
						taskCntNode.remove();
						taskContLastNode.fadeIn('slow');
						return false;
					});
				}
			}).catch(function (e) {
				taskCnt.removeClass('AjaxTable-loading');
				taskCnt.empty();
				var out = '<div class="container">' +
						'<div class="alert alert-danger">' +
							'<h4>Wystąpiły błędy!</h4>' + e +
						'</div>';
				out += '<div class="breadcrumb">' +
							' <a class="create-fix" href="#CREATE/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-sm"> <i class="glyphicon glyphicon-arrow-left"></i> Wróć do formularza i popraw dane</a></li>' +
						'</div>' +
					'</div>';
				jQuery(out).appendTo(taskCnt);
				var taskContLastNode = taskContLast
					, taskCntNode = taskCnt;
				taskCnt.find('.create-fix').click(function(){
					taskCntNode.remove();
					taskContLastNode.fadeIn('slow');
					return false;
				});
				if(DBG)console.log('P5UI__FeatureCreateForm:: e: ', e)
			});
		},
		render: function () {
			if(DBG)console.warn('P5UI__FeatureCreateForm::render this.props', this.props)
			var formProps = Object.assign({}, this.props)
			delete formProps.children
			return h('form', Object.assign(formProps, {
				ref: function (node) { this.rootNode = node; }.bind(this),
				onSubmit: this.submit.bind(this)
			}), this.props.children)
		}
	})
	global.p5VendorJs['P5UI__FeatureCreateForm'] = P5UI__FeatureCreateForm

	var P5UI__Typespecial = createReactClass({
		componentDidMount: function () {
			if(DBG)console.log('TS::componentDidMount (['+this.props.idField+'] '+this.props.fieldNamespace+') ...')
			// console.log('TS::componentDidMount (['+this.props.idField+'] '+this.props.fieldNamespace+') this.rootNode', this.rootNode) // OK
			var fldNode = jQuery(document.getElementById(this.props.fieldName)) // jQuery('#{$fName}')
			var tsNode = jQuery(this.rootNode) // jQuery('#ts-{$fName}')
			var ajaxDataUrlBase = this.props.ajaxDataUrlBase
			if (!fldNode && !tsNode) {
				console.log("BUG Missing fldNode or tsNode in P5UI__Typespecial")
				return;
			}
			if (!ajaxDataUrlBase) {
				console.log("BUG Missing ajaxDataUrlBase in P5UI__Typespecial")
				return;
			}

			fldNode.attr('name', '');
			fldNode.hide();
			if (fldNode.parent().hasClass('show-last-value')) {
				fldNode.parent().hide();
			}
			tsNode.attr('name', this.props.frmFldName)
			tsNode.attr('tabindex', fldNode.attr('tabindex'))

			var getFetchCallback = ('TypespecialVariable' === this.props.type)
				? function (query, callback) {
						return function(res) {
							for (var i in res) {
								if (!res[i].name || res[i].id != res[i].name) {
									res[i].name = res[i].id + ': ' + res[i].name;
								}
							}
							callback(res);
						}
					}
				: function (query, callback) {
						return function(res) {
							var i, prefix, prefixLen;
							for (i in res) {
								prefix = '' + res[i].id;
								prefixLen = prefix.length;
								if (prefixLen > 0 && prefix !== res[i].name.substr(0, prefixLen)) {
									res[i].name = res[i].id + ': ' + res[i].name;
								}
								res[i]['\$order'] = 1 + (parseInt(i) || 0); // set order from request
							}
							callback(res);
						}
					}
			;
			var scoreCallback = ('TypespecialVariable' === this.props.type)
				? function(search) {
						var score = this.getScoreFunction(search);
						return function(item) {
							// console.log('score:item:', item, ', score:', score(item));
							if (search && search == item.id) {
								return 1;
							} else {
								return score(item);// score(item) * (1 + Math.min(item.watchers / 100, 1));
							}
						};
					}
				: function(query) {
						// console.log('Typespecial({$fName})::score: q(', query, ')');
						var score = this.getScoreFunction(query);
						return function(item) {
							var retScore = 0, lName = ('' + item.name).toLowerCase(), lQuery = query.toLowerCase();
							if (query.search(' ') > -1) {
								retScore = score(item);
							}
							else {
								if (lQuery + ':' == lName.substr(0, lQuery.length + 1)) {
									retScore = 100;
								} else if (lName.search(lQuery) > -1) {
									retScore = 90 - lName.search(lQuery);
								} else {
									retScore = 0;
								}
							}
							// console.log('Typespecial({$fName})::score: q(', lQuery, ') , retScore(', retScore, '), score(', score(item), '), item.name(', item.name, ') item.id:', item.id);
							return retScore;
						};
					}
			;

			tsNode.selectize({
				theme: 'typespecial',
				valueField: 'id',
				labelField: 'id',
				searchField: 'name',
				sortField: ('TypespecialVariable' === this.props.type) ? 'name' : '$order', // TODO: skip in Typespecial - default sort field is '$order'
				create: this.props.create,
				delimiter: ';',
				dataAttr: 'typespecial',
				preload: this.props.preload || false,
				options: this.props.options,
				render: {
					item: function(item, escape) {
						return '<div>' +
							'<span class=\"name\">' + escape(item.name || item.id) + '</span>' +
						'</div>';
					},
					option: function(item, escape) {
						return '<div>' +
							'<span class=\"title\">' +
								escape(item.name || item.id) +
							'</span>' +
						'</div>';
					}
				},
				onItemAdd: function(value, item) {// Invoked when an item is selected.
					var curSel = this.options[value] || null, fieldExport;
					if (curSel && curSel.exports) {
						for (var i in curSel.exports) {
							fieldExport = jQuery('#ts-f' + i);
							if (fieldExport.length) {
								//console.log('--- onItemAdd fieldExport.selectize(',fieldExport.selectize,')', fieldExport);
								if (fieldExport.get(0).selectize) {
									fieldExport.get(0).selectize.addOption({id:curSel.exports[i], name:curSel.exports[i]});
									fieldExport.get(0).selectize.setValue(curSel.exports[i]);
								} else {
									//jQuery('#f' + i).val(curSel.exports[i]);
								}
							} else {
								fieldExport = jQuery('#f' + i);
								if (fieldExport.is('input')) {
									jQuery('#f' + i).val(curSel.exports[i]);
								} else if (fieldExport.is('select')) {
									//TODO: add option and select //jQuery('#f' + i).val(curSel.exports[i]);
								}
							}
						}
					}
				},
				score: scoreCallback,
				load: function(query, callback) {
					// if (!query.length) return callback(); // empty query at preload action
					$.ajax({
						url: ajaxDataUrlBase,
						data: 'q=' + encodeURIComponent(query),
						type: 'POST',
						error: function() {
							callback();
						},
						success: getFetchCallback(query, callback),
					});
				}
			});
		},
		render: function () {
			if(DBG)console.log('TS::render (['+this.props.idField+'] '+this.props.fieldNamespace+') props', this.props)
			return h('div', {
				'className': "typespecial",
				'style': { 'max-width': '366px' }
			}, [
				h('select', {
					'ref': function (node) { this.rootNode = node; }.bind(this),
				}, this.props.children)
			])
		}
	})
	global.p5VendorJs['P5UI__Typespecial'] = P5UI__Typespecial

	var P5UI__FeatureRowFunctions = createReactClass({
		componentDidMount: function () {
			if(DBG)console.warn('P5UI__FeatureRowFunctions::componentDidMount this.rootNode', this.rootNode)
			if (this.props.rawHtml) {
				if(DBG)console.warn('P5UI__FeatureRowFunctions::componentDidMount this.props.rawHtml', this.props.rawHtml)
				this.rootNode.innerHTML = this.props.rawHtml
			}
		},
		render: function () {
			var showLabels = this.props.showLabels || false
			var functions = this.props.functions || {}
			if (!this.props.id) return h('div', { 'className': 'alert alert-danger' }, "Missing ID")
			if(DBG)console.warn('P5UI__FeatureRowFunctions::render this.props', this.props)
			return h('div', {
				ref: function (node) { this.rootNode = node; }.bind(this),
			}, [
				('hist' in functions)
					? h('a', { className: "btn btn-xs btn-link", href: functions.hist.href.replace("{0}", this.props.id), title: functions.hist.title }, [ h('span', { className: functions.hist.ico }), h('span', { style: {'padding': '0 6px'} }, functions.hist.title) ])
					: null
				,
				('files' in functions)
					? h('a', { className: "btn btn-xs btn-link", href: functions.files.href.replace("{0}", this.props.id), title: functions.files.title }, [ h('span', { className: functions.files.ico }), h('span', { style: {'padding': '0 6px'} }, functions.files.title) ])
					: null
				,
				('msgs' in functions)
					? h('a', { className: "btn btn-xs btn-link", href: functions.msgs.href.replace("{0}", this.props.id), title: functions.msgs.title }, [ h('span', { className: functions.msgs.ico }), h('span', { style: {'padding': '0 6px'} }, functions.msgs.title) ])
					: null
				,
				(this.props.viewMoreDropdown)
					? h(P5UI__MoreFunctionsDropdownAjax, this.props.viewMoreDropdown)
					: null
				,
			])
		}
	})
	global.p5VendorJs['P5UI__FeatureRowFunctions'] = P5UI__FeatureRowFunctions

	var P5UI__MoreFunctionsDropdownAjax = createReactClass({
		componentDidMount: function () {
			jQuery(this.rootNode).popover({
				container: 'body',
				placement: 'bottom',
				trigger: 'click',
				// title: e.data.col + '<a href="#" class="glyphicon glyphicon-remove pull-right" onclick="return hidePopover();"></a>',
				title: 'Więcej funkcji dla rekordu nr ' + this.props.primaryKey, // '<div style="display:block;position:relative;padding:0 20px 0 0;">' + (this.props.friendly || colName) + ' <button type="button" class="close" onclick="return hidePopover();" style="position:absolute;right:0;top:0;">&times;</button>' + '</div>',
				html: true,
				content: 'TODO: list...', // this.renderListToString.bind(this),
				template: '' +
					'<div class="popover" role="tooltip" style="max-width:600px;width:400px;">' +
						'<div class="arrow"></div>' +
						'<div style="display:block;position:relative;">' +
							'<div class="popover-title">' +
							'</div>' +
						'</div>' +
						'<div class="popover-content"></div>' +
					'</div>'
			})
			this.setState({ 'isOpen': false })
		},
		componentWillUnmount: function () {
			if(DBG)console.warn("P5UI__MoreFunctionsDropdownAjax::componentWillUnmount")
			jQuery(this.rootNode).popover('destroy')
		},
		componentWillMount: function() {
			this._closeDropdownIfClickedOutside = function (event) {
				if (!this.state.isOpen) return;

				var idHtmlPopover = jQuery(this.rootNode).attr('aria-describedby')
				if(DBG)console.warn("P5UI__MoreFunctionsDropdownAjax::_closeDropdownIfClickedOutside idHtmlPopover:", idHtmlPopover)
				if (!idHtmlPopover) return;

				var popoverNode = document.getElementById(idHtmlPopover)
				var isClickedOutside = p5UI__clickedOutsideElement(popoverNode, event)
				if(DBG)console.warn("P5UI__MoreFunctionsDropdownAjax::_closeDropdownIfClickedOutside isClickedOutside:", isClickedOutside, 'this.rootNode', this.rootNode)
				if (isClickedOutside) {
					if (jQuery(this.rootNode).data("bs.popover")) {
						jQuery(this.rootNode).popover('hide')
					} else {
						popoverNode.parentNode.removeChild(popoverNode) // DOM removed before 'hide'
					}
					this.setState({
						isOpen: false
					}, this._unbindCloseDropdownIfClickedOutside.bind(this));
				}
			}.bind(this);
			this._bindCloseDropdownIfClickedOutside = function () {
				if(DBG)console.warn("P5UI__MoreFunctionsDropdownAjax::_bindCloseDropdownIfClickedOutside", this._closeDropdownIfClickedOutside)
				if (!document.addEventListener && document.attachEvent) {
					document.attachEvent('onclick', this._closeDropdownIfClickedOutside);
				} else {
					document.addEventListener('click', this._closeDropdownIfClickedOutside);
				}
			}.bind(this);
			this._unbindCloseDropdownIfClickedOutside = function () {
				if(DBG)console.warn("P5UI__MoreFunctionsDropdownAjax::_unbindCloseDropdownIfClickedOutside", this._closeDropdownIfClickedOutside)
				if (!document.removeEventListener && document.detachEvent) {
					document.detachEvent('onclick', this._closeDropdownIfClickedOutside);
				} else {
					document.removeEventListener('click', this._closeDropdownIfClickedOutside);
				}
			}.bind(this);
		},
		shouldComponentUpdate: function (nextProps, nextState) {
			if (!nextState) return false
			if (!this.state) return false
			if (this.state.rowFunctions !== nextState.rowFunctions) {
				var idHtmlPopover = jQuery(this.rootNode).attr('aria-describedby')
				if(DBG)console.warn("P5UI__MoreFunctionsDropdownAjax::shouldComponentUpdate idHtmlPopover:", idHtmlPopover, 'nextState.rowFunctions', nextState.rowFunctions)
				if (idHtmlPopover && nextState.rowFunctions && nextState.rowFunctions.length) {
					jQuery(document.getElementById(idHtmlPopover)).find('.popover-content').html(this.renderListToString(nextState.rowFunctions))
				} else {
					jQuery(document.getElementById(idHtmlPopover)).find('.popover-content').html('<p class="text-muted">Brak dodatkowych funkcji</p>')
				}
			}
			return false
		},
		renderListToString: function (list) {
			if(DBG)console.warn('P5UI__MoreFunctionsDropdownAjax::renderListToString - this.state:', this.state)
			var primaryKey = this.props.id
			var out = '<ul class="list-unstyled">' + list.map(function (fun) {
				return '<li><a href="'+fun.href.replace('{0}', primaryKey)+'" style="margin:0 2px;" title="'+fun.title+'">' +
					(fun.ico ? '<span class="'+fun.ico+'"></span> ' : '') +
					(fun.label || fun.title) +
				'</a></li>'
			}).join('') + '</ul>'
			if(DBG)console.warn('P5UI__MoreFunctionsDropdownAjax::renderListToString - out:', out)
			return out
		},
		onAjaxFetch: function (data) {
			this.setState({ rowFunctions: data.rowFunctions })
		},
		handleClick: function (e) {
			e.preventDefault()
			if (!this.props.uri) throw "Missing uri in P5UI__MoreFunctionsDropdownAjax"
			if (this.state.isOpen) {
				jQuery(this.rootNode).popover('hide')
			} else {
				jQuery(this.rootNode).popover('show')
				var idHtmlPopover = jQuery(this.rootNode).attr('aria-describedby')
				if (idHtmlPopover) {
					jQuery(document.getElementById(idHtmlPopover)).find('.popover-content').html('<p>Loading...</p>')
				}
				// global.fetch()
				// setTimeout(this.onAjaxFetch.bind(this, { msg: "Funkcje", type: "success", rowFunctions: [
				//	 { id: "msgs", ico: "glyphicon glyphicon-envelope", href: "index.php?_route=TableMsgs&_task=tableRow&idTable=13051&idRow=83", label: 'Wiadomości <span class="badge">0</badge>', title: "Wiadomości (0)" },
				//	 { ico: "glyphicon glyphicon-file", href: "https://biuro.biall-net.pl/dev-pl/se-master/index.php?_route=UrlAction_Ant&typeName=default_db:TEST_PERMS&primaryKey=83", label: 'Druki', title: "Druki" },
				// ] }), 1000)
				_popoverCellAjaxXhr = jQuery.ajax({
					type: 'GET',
					url: this.props.uri,
					dataType: 'json',
					contentType: "application/json; charset=utf-8",
				})
				.done(function(data, textStatus, jqXHR){
					if (data && 'success' === data.type) {
						var rowFunctions = (data.rowFunctions && data.rowFunctions.length > 0)
							? data.rowFunctions
							: [];
						this.setState({ rowFunctions: data.rowFunctions })
					}
				}.bind(this))
				if (this.state.isOpen) this.setState({ 'isOpen': false })
				else this.setState({ 'isOpen': true }, this._bindCloseDropdownIfClickedOutside)
			}
		},
		render: function () {
			if(DBG)console.warn('P5UI__MoreFunctionsDropdownAjax::render this.props', this.props)
			return h('button', {
				className: 'btn btn-xs btn-link',
				ref: function (node) { this.rootNode = node; }.bind(this),
				onClick: this.handleClick.bind(this)
			}, [
				h('span', {'className':"glyphicon glyphicon-menu-hamburger"}),
				" Więcej "
			]);
		}
	})
	global.p5VendorJs['P5UI__MoreFunctionsDropdownAjax'] = P5UI__MoreFunctionsDropdownAjax


	function buildDom(dom, target) {
		ReactDOM.render(buildReactNodeRec(dom), target)
	}
	function buildReactNodeRec(dom) {
		if (null === dom) return null
		if ('string' === typeof dom) return dom
		var nodeReactType = dom[0]
		if ('P5UI__' === nodeReactType.substr(0, 'P5UI__'.length)) {
			if (nodeReactType in global.p5VendorJs) nodeReactType = global.p5VendorJs[nodeReactType]
		}
		return h(nodeReactType,
			convertAttrsToReact(dom[0], dom[1]),
			(dom[2] && 'function' === typeof dom[2].map)
				? dom[2].map(buildReactNodeRec)
				: dom[2]
		)
	}
	function convertAttrsToReact(tagName, attrs) {
		if (!attrs) return null
		if(DBG)console.log('todo convertAttrsToReact typeof attrs ('+ typeof attrs +') toString('+ attrs.toString() +')');
		if (!attrs.toString()) return null
		if ('class' in attrs) {
			attrs['className'] = attrs['class']
			delete attrs['class']
		}
		if ('input' === tagName && 'value' in attrs) { // fix input to uncontrolled
			attrs['defaultValue'] = attrs['value']
			delete attrs['value']
		}
		if ('input' === tagName && 'maxlength' in attrs) { // fix input to uncontrolled
			attrs['maxLength'] = attrs['maxlength']
			delete attrs['maxlength']
		}
		return attrs
	}

	global.p5UI__buildDom = buildDom
})(window, window.p5VendorJs);
