text: COMPONENT('datagrid', 'checkbox:true;colwidth:150;rowheight:28;minheight:200;clusterize:true;limit:80;filterlabel:Filter;height:auto;margin:0;resize:true;reorder:true;sorting:true;boolean:true,on,yes;pluralizepages:# pages,# page,# pages,# pages;pluralizeitems:# items,# item,# items,# items;remember:true;highlight:false;unhighlight:true;autoselect:false;buttonapply:Apply;buttonreset:Reset;allowtitles:false;fullwidth_xs:true;clickid:id;dirplaceholder:Search;autoformat:1;controls:1', function(self, config) {
  
  	var opt = { filter: {}, filtercache: {}, filtercl: {}, filtervalues: {}, scroll: false, selected: {}, operation: '' };
  	var header, vbody, footer, container, ecolumns, isecolumns = false, ready = false;
  	var sheader, sbody, econtrols;
  	var Theadercol = Tangular.compile('<div class="dg-hcol dg-col-{{ index }}{{ if sorting }} dg-sorting{{ fi }}" data-index="{{ index }}">{{ if sorting }}<i class="dg-sort fa fa-sort"></i>{{ fi }}<div class="dg-label{{ alignheader }}"{{ if labeltitle }} title="{{ labeltitle }}"{{ fi }}{{ if reorder }} draggable="true"{{ fi }}>{{ label | raw }}</div>{{ if filter }}<div class="dg-filter{{ alignfilter }}{{ if filterval != null && filterval !== \'\' }} dg-filter-selected{{ fi }}"><i class="fa dg-filter-cancel fa-times"></i>{{ if options }}<label data-name="{{ name }}">{{ if filterval }}{{ filterval }}{{ else }}{{ filter }}{{ fi }}</label>{{ else }}<input autocomplete="new-password" type="text" placeholder="{{ filter }}" class="dg-filter-input" name="{{ name }}{{ ts }}" data-name="{{ name }}" value="{{ filterval }}" />{{ fi }}</div>{{ else }}<div class="dg-filter-empty">&nbsp;</div>{{ fi }}</div>');
  	var isIE = (/msie|trident/i).test(navigator.userAgent);
  	var isredraw = false;
  	var forcescroll = '';
  	var schemas = {};
  	var controls = { el: null, timeout: null, is: false, cache: {} };
  
  	self.meta = opt;
  
  	function Cluster(el) {
  
  		var self = this;
  		var dom = el[0];
  		var scrollel = el;
  
  		self.row = config.rowheight;
  		self.rows = [];
  		self.limit = config.limit;
  		self.pos = -1;
  		self.enabled = !!config.clusterize;
  		self.plus = 0;
  		self.scrolltop = 0;
  		self.prev = 0;
  
  		var seh = '<div style="height:0"></div>';
  		var set = $(seh);
  		var seb = $(seh);
  
  		var div = document.createElement('DIV');
  		dom.appendChild(set[0]);
  		dom.appendChild(div);
  		dom.appendChild(seb[0]);
  		self.el = $(div);
  
  		self.render = function() {
  
  			var t = self.pos * self.frame;
  			var b = (self.rows.length * self.row) - (self.frame * 2) - t;
  			var pos = self.pos * self.limit;
  			var posto = pos + (self.limit * 2);
  
  			set.css('height', t);
  			seb.css('height', b < 2 ? isMOBILE ? (config.exec ? (self.row + 1) : (self.row * 2.25)) >> 0 : 3 : b);
  
  			var tmp = self.scrollbar[0].scrollTop;
  			var node = self.el[0];
  			// node.innerHTML = '';
  
  			var child = node.firstChild;
  
  			while (child) {
  				node.removeChild(child);
  				child = node.firstChild;
  			}
  
  			for (var i = pos; i < posto; i++) {
  				if (typeof(self.rows[i]) === 'string')
  					self.rows[i] = $(self.rows[i])[0];
  
  				if (self.rows[i])
  					node.appendChild(self.rows[i]);
  				else
  					break;
  			}
  
  			if (self.prev < t)
  				self.scrollbar[0].scrollTop = t;
  			else
  				self.scrollbar[0].scrollTop = tmp;
  
  			self.prev = t;
  
  			if (self.grid.selected) {
  				var index = opt.rows.indexOf(self.grid.selected);
  				if (index !== -1 && (index >= pos || index <= (pos + self.limit)))
  					self.el.find('.dg-row[data-index="{0}"]'.format(index)).aclass('dg-selected');
  			}
  		};
  
  		self.scrolling = function() {
  
  			var y = self.scrollbar[0].scrollTop + 1;
  			self.scrolltop = y;
  
  			if (y < 0)
  				return;
  
  			var frame = Math.ceil(y / self.frame) - 1;
  			if (frame === -1)
  				return;
  
  			if (self.pos !== frame) {
  
  				// The content could be modified
  				var plus = (self.el[0].offsetHeight / 2) - self.frame;
  				if (plus > 0) {
  					frame = Math.ceil(y / (self.frame + plus)) - 1;
  					if (self.pos === frame)
  						return;
  				}
  
  				if (self.max && frame >= self.max)
  					frame = self.max;
  
  				self.pos = frame;
  
  				if (self.enabled)
  					self.render();
  				else {
  
  					var node = self.el[0];
  					var child = node.firstChild;
  
  					while (child) {
  						node.removeChild(child);
  						child = node.firstChild;
  					}
  
  					for (var i = 0; i < self.rows.length; i++) {
  						if (typeof(self.rows[i]) === 'string')
  							self.rows[i] = $(self.rows[i])[0];
  						self.el[0].appendChild(self.rows[i]);
  					}
  				}
  
  				self.scroll && self.scroll();
  				config.change && self.grid.SEEX(config.change, null, null, self.grid);
  			}
  		};
  
  		self.update = function(rows, noscroll) {
  
  			if (noscroll != true)
  				self.el[0].scrollTop = 0;
  
  			self.limit = config.limit;
  			self.pos = -1;
  			self.rows = rows;
  			self.max = Math.ceil(rows.length / self.limit) - 1;
  			self.frame = self.limit * self.row;
  
  			if (!self.enabled) {
  				self.frame = 1000000;
  			} else if (self.limit * 2 > rows.length) {
  				self.limit = rows.length;
  				self.frame = self.limit * self.row;
  				self.max = 1;
  			}
  
  			self.scrolling();
  		};
  
  		self.destroy = function() {
  			self.el.off('scroll');
  			self.rows = null;
  		};
  
  		self.scrollbar = scrollel.closest('.ui-scrollbar-area');
  		self.scrollbar.on('scroll', self.scrolling);
  	}
  
  	self.destroy = function() {
  		opt.cluster && opt.cluster.destroy();
  	};
  
  	// opt.cols    --> columns
  	// opt.rows    --> raw rendered data
  	// opt.render  --> for cluster
  
  	self.init = function() {
  
  		ON('resize + resize2', function() {
  			setTimeout2('datagridresize', ASETTER('datagrid/resize'), 500);
  		});
  
  		Thelpers.ui_datagrid_autoformat = function(val, type) {
  
  			switch (type) {
  				case 'email':
  					return val && val.length > 2 ? '<a href="mailto:{0}" class="dg-link"><i class="far fa-envelope"></i>{0}</a>'.format(val) : val;
  				case 'phone':
  					return val && val.length > 2 ? '<a href="tel:{0}" class="dg-link"><i class="fas fa-phone"></i>{0}</a>'.format(val) : val;
  				case 'url':
  					return val && val.length > 7 && (/http(s):\/\//i).test(val) ? '<a href="{0}" target="_blank" class="dg-link"><i class="fas fa-globe"></i>{0}</a>'.format(val) : val;
  			}
  
  			return val;
  		};
  
  		Thelpers.ui_datagrid_checkbox = function(val) {
  			return '<div class="dg-checkbox' + (val ? ' dg-checked' : '') + '" data-custom="1"><i class="fa fa-check"></i></div>';
  		};
  
  		Thelpers.ui_datagrid_colorize = function(val, encode) {
  			var hash = HASH(val + '');
  			var color = '#';
  			for (var i = 0; i < 3; i++) {
  				var value = (hash >> (i * 8)) & 0xFF;
  				color += ('00' + value.toString(16)).substr(-2);
  			}
  			return '<span style="background:{0}" class="dg-colorize">{1}</span>'.format(color, encode ? Thelpers.encode(val) : val);
  		};
  	};
  
  	self.readonly();
  	self.bindvisible();
  	self.nocompile();
  
  	var reconfig = function() {
  		self.tclass('dg-clickable', !!(config.click || config.dblclick));
  	};
  
  	self.configure = function(key, value, init) {
  		switch (key) {
  			case 'noborder':
  				self.tclass('dg-noborder', !!value);
  				break;
  			case 'checkbox':
  			case 'numbering':
  				!init && self.cols(NOOP);
  				break;
  			case 'pluralizepages':
  				config.pluralizepages = value.split(',').trim();
  				break;
  			case 'pluralizeitems':
  				config.pluralizeitems = value.split(',').trim();
  				break;
  			case 'checked':
  			case 'button':
  			case 'exec':
  				if (value && value.SCOPE)
  					config[key] = value.SCOPE(self, value);
  				break;
  			case 'dblclick':
  				if (value && value.SCOPE)
  					config.dblclick = value.SCOPE(self, value);
  				break;
  			case 'click':
  				if (value && value.SCOPE)
  					config.click = value.SCOPE(self, value);
  				break;
  			case 'columns':
  				self.datasource(value, function(path, value, type) {
  					if (value) {
  						opt.sort = null;
  						opt.filter = {};
  						opt.scroll = '';
  						opt.selected = {};
  						self.rebind(value, true);
  						type && self.setter(null);
  					}
  				});
  				break;
  		}
  
  		setTimeout2(self.ID + 'reconfigure', reconfig);
  	};
  
  	self.refresh = function() {
  		self.refreshfilter();
  	};
  
  	self.applycolumns = function(use) {
  		isecolumns = false;
  		ecolumns.aclass('hidden');
  		if (use) {
  			var nothidden = {};
  			ecolumns.find('.dg-columns-checkbox-checked').each(function() {
  				nothidden[this.getAttribute('data-id')] = true;
  			});
  			self.cols(function(cols) {
  				for (var i = 0; i < cols.length; i++) {
  					var col = cols[i];
  					col.hidden = nothidden[col.id] !== true;
  				}
  			});
  		}
  	};
  
  	self.fn_in_changed = function(arr) {
  		config.changed && self.SEEX(config.changed, arr || self.changed(), self);
  	};
  
  	self.fn_in_checked = function(arr) {
  		config.checked && self.SEEX(config.checked, arr || self.checked(), self);
  	};
  
  	self.fn_refresh = function() {
  		setTimeout2(self.ID + 'filter', function() {
  			if (config.exec)
  				self.operation(opt.operation);
  			else
  				self.refreshfilter(true);
  		}, 50);
  	};
  
  	self.make = function() {
  
  		self.IDCSS = GUID(5);
  		self.aclass('dg dg-noscroll dg-' + self.IDCSS + (config.height === 'fluid' ? ' dg-fluid' : ''));
  
  		self.find('script').each(function() {
  			var el = $(this);
  			var id = el.attrd('id');
  
  			if (id)
  				schemas[id] = el.html();
  
  			if (!schemas.default)
  				schemas.default = el.html();
  
  		});
  
  		controls.show = function(dom) {
  
  			if (controls.ishidding || !opt.controls || !config.controls)
  				return;
  
  			var el = $(dom);
  			var off = el.position();
  			var css = {};
  			var clshover = 'dg-row-hover';
  			var index = el.attrd('index');
  			controls.el && controls.el.rclass(clshover);
  			controls.el = $(dom).aclass(clshover);
  
  			var div = controls.cache[index];
  
  			if (div === null) {
  				controls.hide();
  				return;
  			}
  
  			if (!div) {
  				var html = opt.controls(opt.rows[+index]);
  				div = controls.cache[index] = html ? $('<div>' + html + '</div>')[0] : null;
  				controls.cache[index] = div;
  				if (div === null) {
  					controls.hide();
  					return;
  				}
  			}
  
  			while (true) {
  				var child = econtrols[0].firstChild;
  				if (child)
  					econtrols[0].removeChild(child);
  				else
  					break;
  			}
  
  			econtrols[0].appendChild(div);
  			css.top = Math.ceil(off.top - ((econtrols.height() / 2) - (config.rowheight / 2))) - 2;
  			econtrols.css(css).rclass('hidden').aclass('dg-controls-visible', 50).attrd('index', index);
  			controls.timeout = null;
  			controls.is = true;
  			controls.y = self.scrollbarY.scrollTop();
  		};
  
  		controls.hide = function(type) {
  			if (controls.is) {
  
  				// scrollbar
  				if (type === 1) {
  					var y = self.scrollbarY.scrollTop();
  					if (controls.y === y)
  						return;
  				} else if (type === 2) {
  					controls.ishidding = true;
  					setTimeout(function() {
  						controls.ishidding = false;
  					}, 1000);
  				}
  
  				controls.el.rclass('dg-row-hover');
  				controls.el = null;
  				controls.is = false;
  				econtrols.aclass('hidden').rclass('dg-controls-visible');
  			}
  		};
  
  		ON('scroll', function() {
  			controls.hide(1);
  		});
  
  		self.event('mouseenter', '.dg-row', function(e) {
  			controls.timeout && clearTimeout(controls.timeout);
  			controls.timeout = setTimeout(controls.show, controls.is ? 50 : 500, this, e);
  		});
  
  		var pagination = '';
  		if (config.exec)
  			pagination = '<div class="dg-footer hidden"><div class="dg-pagination-items hidden-xs"></div><div class="dg-pagination"><button name="page-first" disabled><i class="fa fa-angle-double-left"></i></button><button name="page-prev" disabled><i class="fa fa-angle-left"></i></button><div><input type="text" name="page" maxlength="5" class="dg-pagination-input" /></div><button name="page-next" disabled><i class="fa fa-angle-right"></i></button><button name="page-last" disabled><i class="fa fa-angle-double-right"></i></button></div><div class="dg-pagination-pages"></div></div>';
  
  		self.dom.innerHTML = '<div class="dg-btn-columns"><i class="fa fa-caret-left"></i><span class="fa fa-columns"></span></div><div class="dg-columns hidden"><div><div class="dg-columns-body"></div></div><button class="dg-columns-button" name="columns-apply"><i class="fa fa-check-circle"></i>{1}</button><span class="dt-columns-reset">{2}</span></div><div class="dg-container"><div class="dg-controls"></div><span class="dg-resize-line hidden"></span><div class="dg-header-scrollbar"><div class="dg-header"></div><div class="dg-body-scrollbar"><div class="dg-body"></div></div></div></div>{0}'.format(pagination, config.buttonapply, config.buttonreset);
  
  		header = self.find('.dg-header');
  		vbody = self.find('.dg-body');
  		footer = self.find('.dg-footer');
  		container = self.find('.dg-container');
  		ecolumns = self.find('.dg-columns');
  		sheader = self.find('.dg-header-scrollbar');
  		sbody = self.find('.dg-body-scrollbar');
  		econtrols = self.find('.dg-controls');
  
  		container.on('mouseleave', function() {
  			controls.hide(2);
  		});
  
  		self.scrollbarY = config.height !== 'fluid' ? SCROLLBAR(sbody, { visibleY: true, orientation: 'y', controls: container, marginY: isMOBILE ? 0 : 54 }) : null;
  		self.scrollbarX = SCROLLBAR(sheader, { visibleX: true, orientation: 'x', controls: container });
  
  		if (schemas.default) {
  			self.rebind(schemas.default);
  			schemas.$current = 'default';
  		}
  
  		var events = {};
  
  		events.mouseup = function(e) {
  			if (r.is) {
  				r.is = false;
  				r.line.aclass('hidden');
  				r.el.css('height', r.h);
  				var x = r.el.css('left').parseInt();
  				var index = +r.el.attrd('index');
  				var width = opt.cols[index].width + (x - r.x);
  				self.resizecolumn(index, width);
  				e.preventDefault();
  				e.stopPropagation();
  			}
  			events.unbind();
  		};
  
  		container.on('contextmenu', function(e) {
  			if (config.contextmenu) {
  				e.preventDefault();
  				self.EXEC(config.contextmenu, e, self);
  			}
  		});
  
  		events.unbind = function() {
  			$(W).off('mouseup', events.mouseup).off('mousemove', events.mousemove);
  		};
  
  		events.bind = function() {
  			$(W).on('mouseup', events.mouseup).on('mousemove', events.mousemove);
  		};
  
  		var hidedir = function() {
  			ishidedir = true;
  			SETTER('!directory', 'hide');
  			setTimeout(function() {
  				ishidedir = false;
  			}, 800);
  		};
  
  		var ishidedir = false;
  		var r = { is: false };
  
  		self.event('click', '.dg-btn-columns', function(e) {
  
  			e.preventDefault();
  			e.stopPropagation();
  
  			controls.hide();
  
  			var cls = 'hidden';
  			if (isecolumns) {
  				self.applycolumns();
  			} else {
  				var builder = [];
  
  				for (var i = 0; i < opt.cols.length; i++) {
  					var col = opt.cols[i];
  					(col.listcolumn && !col.$hidden) && builder.push('<div><label class="dg-columns-checkbox{1}" data-id="{0}"><span><i class="fa fa-check"></i></span>{2}</label></div>'.format(col.id, col.hidden ? '' : ' dg-columns-checkbox-checked', col.text));
  				}
  
  				ecolumns.find('.dg-columns-body')[0].innerHTML = builder.join('');
  				ecolumns.rclass(cls);
  				isecolumns = true;
  			}
  		});
  
  		header.on('click', 'label', function() {
  
  			var el = $(this);
  			var index = +el.closest('.dg-hcol').attrd('index');
  			var col = opt.cols[index];
  			var opts = col.options instanceof Array ? col.options : GET(self.makepath(col.options));
  			var dir = {};
  
  			controls.hide();
  			dir.element = el;
  			dir.items = opts;
  			dir.key = col.otext;
  			dir.offsetX = -6;
  			dir.offsetY = -2;
  			dir.placeholder = config.dirplaceholder;
  
  			dir.callback = function(item) {
  				self.applyfilterdirectory(el, col, item);
  			};
  
  			SETTER('directory', 'show', dir);
  		});
  
  		self.event('dblclick', '.dg-col', function(e) {
  			controls.hide();
  			e.preventDefault();
  			e.stopPropagation();
  			self.editcolumn($(this));
  		});
  
  		var dblclick = { ticks: 0, id: null, row: null };
  		r.line = container.find('.dg-resize-line');
  
  		var findclass = function(node) {
  			var count = 0;
  			while (true) {
  				for (var i = 1; i < arguments.length; i++) {
  					if (node.classList.contains(arguments[i]))
  						return true;
  				}
  				node = node.parentNode;
  				if ((count++) > 4)
  					break;
  			}
  		};
  
  		self.event('click', '.dg-row', function(e) {
  
  			var now = Date.now();
  			var el = $(this);
  			var type = e.target.tagName;
  			var target = $(e.target);
  
  			if ((type === 'DIV' || type === 'SPAN')) {
  
  				var cls = 'dg-selected';
  				var elrow = el.closest('.dg-row');
  				var index = +elrow.attrd('index');
  				var row = opt.rows[index];
  				if (!row)
  					return;
  
  				if (config.dblclick && dblclick.ticks && dblclick.ticks > now && dblclick.row === row && !findclass(e.target, 'dg-checkbox', 'dg-editable')) {
  					config.dblclick && self.SEEX(config.dblclick, row, self, elrow, target);
  					if (config.highlight && self.selected !== row) {
  						opt.cluster.el.find('.' + cls).rclass(cls);
  						self.selected = row;
  						elrow.aclass(cls);
  					}
  					e.preventDefault();
  					return;
  				}
  
  				dblclick.row = row;
  				dblclick.ticks = now + 300;
  
  				var rowarg = row;
  
  				if (config.highlight) {
  					opt.cluster.el.find('.' + cls).rclass(cls);
  					if (!config.unhighlight || self.selected !== row) {
  						self.selected = row;
  						elrow.aclass(cls);
  						controls.show(el[0]);
  					} else {
  						rowarg = self.selected = null;
  						controls.is && controls.hide();
  					}
  				} else {
  					if (controls.is)
  						controls.hide();
  					else if (rowarg)
  						controls.show(el[0]);
  				}
  
  				config.click && self.SEEX(config.click, rowarg, self, elrow, target);
  			}
  		});
  
  		self.reload = function() {
  			self.operation('refresh');
  		};
  
  		self.empty = function() {
  			self.set({ page: 1, pages: 0, count: 0, items: [], limit: 0 });
  		};
  
  		self.released = function(is) {
  			!is && setTimeout(self.resize, 500);
  		};
  
  		self.event('click', '.dg-filter-cancel,.dt-columns-reset', function() {
  			var el = $(this);
  
  			controls.hide();
  
  			if (el.hclass('dt-columns-reset'))
  				self.resetcolumns();
  			else {
  				var tmp = el.parent();
  				var input = tmp.find('input');
  				if (input.length) {
  					input.val('');
  					input.trigger('change');
  					return;
  				}
  
  				var label = tmp.find('label');
  				if (label.length) {
  					tmp.rclass('dg-filter-selected');
  					var index = +el.closest('.dg-hcol').attrd('index');
  					var col = opt.cols[index];
  					var k = label.attrd('name');
  					label.html(col.filter);
  					forcescroll = opt.scroll = 'y';
  					opt.operation = 'filter';
  					delete opt.filter[k];
  					delete opt.filtervalues[col.id];
  					delete opt.filtercl[k];
  					self.fn_refresh();
  				}
  			}
  		});
  
  		self.event('click', '.dg-label,.dg-sort', function() {
  
  			var el = $(this).closest('.dg-hcol');
  
  			controls.hide();
  
  			if (!el.find('.dg-sort').length)
  				return;
  
  			var index = +el.attrd('index');
  
  			for (var i = 0; i < opt.cols.length; i++) {
  				if (i !== index)
  					opt.cols[i].sort = 0;
  			}
  
  			var col = opt.cols[index];
  			switch (col.sort) {
  				case 0:
  					col.sort = 1;
  					break;
  				case 1:
  					col.sort = 2;
  					break;
  				case 2:
  					col.sort = 0;
  					break;
  			}
  
  			opt.sort = col;
  			opt.operation = 'sort';
  			forcescroll = '-';
  
  			if (config.exec)
  				self.operation(opt.operation);
  			else
  				self.refreshfilter(true);
  		});
  
  		isIE && self.event('keydown', 'input', function(e) {
  			if (e.keyCode === 13)
  				$(this).blur();
  			else if (e.keyCode === 27)
  				$(this).val('');
  		});
  
  		self.event('mousedown', function(e) {
  
  			var el = $(e.target);
  
  			if (!el.hclass('dg-resize'))
  				return;
  
  			controls.hide();
  			events.bind();
  
  			var offset = self.element.offset().left;
  			r.el = el;
  			r.offset = offset; //offset;
  
  			var prev = el.prev();
  			r.min = (prev.length ? prev.css('left').parseInt() : (config.checkbox ? 70 : 30)) + 50;
  			r.h = el.css('height');
  			r.x = el.css('left').parseInt();
  			r.line.css('height', opt.height);
  			r.is = true;
  			r.isline = false;
  			e.preventDefault();
  			e.stopPropagation();
  		});
  
  		header.on('mousemove', function(e) {
  			if (r.is) {
  				var x = (e.pageX - r.offset - 10);
  				var x2 = self.scrollbarX.scrollLeft() + x;
  				if (x2 < r.min)
  					x2 = r.min;
  
  				r.el.css('left', x2);
  				r.line.css('left', x + 9);
  
  				if (!r.isline) {
  					r.isline = true;
  					r.line.rclass('hidden');
  				}
  
  				e.preventDefault();
  				e.stopPropagation();
  			}
  		});
  
  		self.applyfilterdirectory = function(label, col, item) {
  
  			var val = item[col.ovalue];
  			var is = val != null && val !== '';
  			var name = label.attrd('name');
  
  			opt.filtervalues[col.id] = val;
  
  			if (is) {
  				if (opt.filter[name] == val)
  					return;
  				opt.filter[name] = val;
  			} else
  				delete opt.filter[name];
  
  			delete opt.filtercache[name];
  			opt.filtercl[name] = val;
  
  			forcescroll = opt.scroll = 'y';
  			opt.operation = 'filter';
  			label.parent().tclass('dg-filter-selected', is);
  			label.text(is ? (item[col.otext] || '') : col.filter);
  			self.fn_refresh();
  		};
  
  		var d = { is: false };
  
  		self.event('dragstart', function(e) {
  			!isIE && e.originalEvent.dataTransfer.setData('text/plain', GUID());
  		});
  
  		self.event('dragenter dragover dragexit drop dragleave', function (e) {
  
  			e.stopPropagation();
  			e.preventDefault();
  
  			switch (e.type) {
  				case 'drop':
  
  					if (d.is) {
  						var col = opt.cols[+$(e.target).closest('.dg-hcol').attrd('index')];
  						col && self.reordercolumn(d.index, col.index);
  					}
  
  					d.is = false;
  					break;
  
  				case 'dragenter':
  					if (!d.is) {
  						d.index = +$(e.target).closest('.dg-hcol').attrd('index');
  						d.is = true;
  					}
  					return;
  				case 'dragover':
  					return;
  				default:
  					return;
  			}
  		});
  
  		self.event('change', '.dg-pagination-input', function() {
  
  			var value = self.get();
  			var val = +this.value;
  
  			if (isNaN(val))
  				return;
  
  			if (val >= value.pages)
  				val = value.pages;
  			else if (val < 1)
  				val = 1;
  
  			value.page = val;
  			forcescroll = opt.scroll = 'y';
  			self.operation('page');
  			controls.hide();
  		});
  
  		self.event('change', '.dg-filter-input', function() {
  
  			var input = this;
  			var $el = $(this);
  			var el = $el.parent();
  			var val = $el.val();
  			var name = input.getAttribute('data-name');
  
  			var col = opt.cols[+el.closest('.dg-hcol').attrd('index')];
  			delete opt.filtercache[name];
  			delete opt.filtercl[name];
  
  			if (col.options) {
  				if (val)
  					val = (col.options instanceof Array ? col.options : GET(self.makepath(col.options)))[+val][col.ovalue];
  				else
  					val = null;
  			}
  
  			var is = val != null && val !== '';
  
  			if (col)
  				opt.filtervalues[col.id] = val;
  
  			if (is) {
  				if (opt.filter[name] == val)
  					return;
  				opt.filter[name] = val;
  			} else
  				delete opt.filter[name];
  
  			forcescroll = opt.scroll = 'y';
  			opt.operation = 'filter';
  			el.tclass('dg-filter-selected', is);
  			self.fn_refresh();
  			controls.hide();
  		});
  
  		self.select = function(row) {
  
  			var index;
  
  			if (typeof(row) === 'number') {
  				index = row;
  				row = opt.rows[index];
  			} else if (row)
  				index = opt.rows.indexOf(row);
  
  			var cls = 'dg-selected';
  			if (!row || index === -1) {
  				self.selected = null;
  				opt.cluster && opt.cluster.el.find('.' + cls).rclass(cls);
  				config.highlight && config.click && self.SEEX(config.click, null, self);
  				return;
  			}
  
  			self.selected = row;
  
  			var elrow = opt.cluster.el.find('.dg-row[data-index="{0}"]'.format(index));
  			if (elrow && config.highlight) {
  				opt.cluster.el.find('.' + cls).rclass(cls);
  				elrow.aclass(cls);
  			}
  
  			config.click && self.SEEX(config.click, row, self, elrow, null);
  			controls.hide();
  		};
  
  		self.event('click', '.dg-checkbox', function() {
  
  			var t = $(this);
  			var custom = t.attrd('custom');
  
  			if (custom === '1')
  				return;
  
  			t.tclass('dg-checked');
  
  			if (custom === '2')
  				return;
  
  			var val = t.attrd('value');
  			var checked = t.hclass('dg-checked');
  
  			if (val === '-1') {
  				if (checked) {
  					opt.checked = {};
  					for (var i = 0; i < opt.rows.length; i++)
  						opt.checked[opt.rows[i].ROW] = 1;
  				} else
  					opt.checked = {};
  				self.scrolling();
  			} else if (checked)
  				opt.checked[val] = 1;
  			else
  				delete opt.checked[val];
  
  			self.fn_in_checked();
  		});
  
  		self.event('click', '.dg-columns-checkbox', function() {
  			$(this).tclass('dg-columns-checkbox-checked');
  		});
  
  		self.event('click', 'button', function(e) {
  			switch (this.name) {
  				case 'columns-apply':
  					self.applycolumns(true);
  					break;
  				case 'page-first':
  					forcescroll = opt.scroll = 'y';
  					self.get().page = 1;
  					self.operation('page');
  					break;
  				case 'page-last':
  					forcescroll = opt.scroll = 'y';
  					var tmp = self.get();
  					tmp.page = tmp.pages;
  					self.operation('page');
  					break;
  				case 'page-prev':
  					forcescroll = opt.scroll = 'y';
  					self.get().page -= 1;
  					self.operation('page');
  					break;
  				case 'page-next':
  					forcescroll = opt.scroll = 'y';
  					self.get().page += 1;
  					self.operation('page');
  					break;
  				default:
  					var el = $(this);
  					var index = +el.closest('.dg-row,.dg-controls').attrd('index');
  					var row = opt.rows[index];
  					config.button && self.SEEX(config.button, this.name, row, el, e);
  					break;
  			}
  		});
  
  		self.scrollbarX.area.on('scroll', function() {
  			!ishidedir && hidedir();
  			isecolumns && self.applycolumns();
  		});
  
  		// config.exec && self.operation('init');
  	};
  
  	self.operation = function(type) {
  
  		var value = self.get();
  
  		if (value == null)
  			value = {};
  
  		if (type === 'filter' || type === 'init')
  			value.page = 1;
  
  		var keys = Object.keys(opt.filter);
  		self.SEEX(config.exec, type, keys.length ? opt.filter : null, opt.sort && opt.sort.sort ? [(opt.sort.name + '_' + (opt.sort.sort === 1 ? 'asc' : 'desc'))] : null, value.page, self);
  
  		switch (type) {
  			case 'sort':
  				self.redrawsorting();
  				break;
  		}
  	};
  
  	function align(type) {
  		return type === 1 ? 'center' : type === 2 ? 'right' : type;
  	}
  
  	self.clear = function() {
  		for (var i = 0; i < opt.rows.length; i++)
  			opt.rows[i].CHANGES = undefined;
  		self.renderrows(opt.rows, true);
  		opt.cluster && opt.cluster.update(opt.render);
  		self.fn_in_changed();
  	};
  
  	self.editcolumn = function(rindex, cindex) {
  
  		var col;
  		var row;
  
  		if (cindex == null) {
  			if (rindex instanceof jQuery) {
  				cindex = rindex.attr('class').match(/\d+/);
  				if (cindex)
  					cindex = +cindex[0];
  				else
  					return;
  				col = rindex;
  			}
  		} else
  			row = opt.cluster.el.find('.dg-row-' + (rindex + 1));
  
  		if (!col)
  			col = row.find('.dg-col-' + cindex);
  
  		var index = cindex;
  		if (index == null)
  			return;
  
  		if (!row)
  			row = col.closest('.dg-row');
  
  		var data = {};
  		data.col = opt.cols[index];
  		if (!data.col.editable)
  			return;
  
  		data.rowindex = +row.attrd('index');
  		data.row = opt.rows[data.rowindex];
  		data.colindex = index;
  		data.value = data.row[data.col.name];
  		data.elrow = row;
  		data.elcol = col;
  
  		var clone = col.clone();
  		var cb = function(data) {
  
  			if (data == null) {
  				col.replaceWith(clone);
  				return;
  			}
  
  			data.row[data.col.name] = data.value;
  
  			if (opt.rows[data.rowindex] != data.row)
  				opt.rows[data.rowindex] = data.row;
  
  			if (!data.row.CHANGES)
  				data.row.CHANGES = {};
  
  			data.row.CHANGES[data.col.name] = true;
  			opt.render[data.rowindex] = $(self.renderrow(data.rowindex, data.row))[0];
  			data.elrow.replaceWith(opt.render[data.rowindex]);
  			self.fn_in_changed();
  
  		};
  
  		if (config.change)
  			self.EXEC(config.change, data, cb, self);
  		else
  			self.datagrid_edit(data, cb);
  	};
  
  	self.applyfilter = function(obj, add) {
  
  
  		if (!ready) {
  			setTimeout(self.applyfilter, 100, obj, add);
  			return;
  		}
  
  		if (!add)
  			opt.filter = {};
  
  		var keys = Object.keys(obj);
  
  		for (var i = 0; i < keys.length; i++) {
  			var key = keys[i];
  			var col = opt.cols.findItem('name', key);
  			if (col.options) {
  				var items = col.options instanceof Array ? col.options : GET(self.makepath(col.options));
  				if (items instanceof Array) {
  					var item = items.findItem(col.ovalue, obj[key]);
  					if (item) {
  						var el = header.find('.dg-hcol[data-index="{0}"] label'.format(col.index));
  						if (el.length)
  							self.applyfilterdirectory(el, col, item);
  					}
  				}
  			}
  		}
  
  		header.find('input').each(function() {
  			var t = this;
  			var el = $(t);
  			var val = obj[el.attrd('name')];
  			if (val !== undefined)
  				el.val(val == null ? '' : val);
  		}).trigger('change');
  
  	};
  
  	self.rebind = function(code, prerender) {
  
  		if (code.length < 30 && code.indexOf(' ') === -1) {
  			schemas.$current = code;
  			schemas[code] && self.rebind(schemas[code]);
  			return;
  		}
  
  		opt.declaration = code;
  
  		var type = typeof(code);
  		if (type === 'string') {
  			code = code.trim();
  			self.gridid = 'dg' + HASH(code);
  		} else
  			self.gridid = 'dg' + HASH(JSON.stringify(code));
  
  		var cache = config.remember ? W.PREF ? W.PREF.get(self.gridid) : CACHE(self.gridid) : null;
  		var cols = type === 'string' ? new Function('return ' + code)() : CLONE(code);
  		var tmp;
  
  		opt.rowclasstemplate = null;
  		opt.search = false;
  
  		for (var i = 0; i < cols.length; i++) {
  			var col = cols[i];
  
  			if (typeof(col) === 'string') {
  				opt.rowclasstemplate = Tangular.compile(col);
  				cols.splice(i, 1);
  				i--;
  				continue;
  			}
  
  			if (col.type === 'controls' || col.type === 'buttons') {
  				opt.controls = col.template ? Tangular.compile(col.template) : null;
  				cols.splice(i, 1);
  				i--;
  				continue;
  			}
  
  			col.id = GUID(5);
  			col.realindex = i;
  
  			if (!col.name)
  				col.name = col.id;
  
  			if (col.listcolumn == null)
  				col.listcolumn = true;
  
  			if (col.hidden) {
  				col.$hidden = FN(col.hidden)(col) === true;
  				col.hidden = true;
  			}
  
  			if (col.hide) {
  				col.hidden = col.hide === true;
  				delete col.hide;
  			}
  
  			if (col.options) {
  				!col.otext && (col.otext = 'text');
  				!col.ovalue && (col.ovalue = 'value');
  			}
  
  			// SORT?
  			if (col.sort != null)
  				col.sorting = col.sort;
  
  			if (cache) {
  				var c = cache[i];
  				if (c) {
  					col.index = c.index;
  					col.width = c.width;
  					col.hidden = c.hidden;
  				}
  			}
  
  			if (col.index == null)
  				col.index = i;
  
  			if (col.sorting == null)
  				col.sorting = config.sorting;
  
  			if (col.alignfilter != null)
  				col.alignfilter = ' ' + align(col.alignfilter);
  
  			if (col.alignheader != null)
  				col.alignheader = ' ' + align(col.alignheader);
  
  			col.sort = 0;
  
  			if (col.search) {
  				opt.search = true;
  				col.search = col.search === true ? Tangular.compile(col.template) : Tangular.compile(col.search);
  			}
  
  			if (!col.align) {
  				switch (col.type) {
  					case 'date':
  						col.align = 1;
  						break;
  					case 'number':
  						col.align = 2;
  						break;
  					case 'boolean':
  						col.align = 1;
  						break;
  				}
  			}
  
  			if (col.align && col.align !== 'left') {
  				col.align = align(col.align);
  				col.align = ' ' + col.align;
  				if (!col.alignfilter)
  					col.alignfilter = ' center';
  				if (!col.alignheader)
  					col.alignheader = ' center';
  			}
  
  			var cls = col.class ? (' ' + col.class) : '';
  
  			if (col.editable) {
  				cls += ' dg-editable';
  				if (col.required)
  					cls += ' dg-required';
  			}
  
  			if (config.autoformat) {
  				switch (col.type) {
  					case 'number':
  						if (col.monospace == null)
  							col.monospace = true;
  						break;
  				}
  			}
  
  			var isbool = col.type && col.type.substring(0, 4) === 'bool';
  			var TC = Tangular.compile;
  
  			if (col.template) {
  				col.templatecustom = true;
  				col.template = TC((col.template.indexOf('<button') === -1 ? ('<div class="dg-value' + (col.monospace ? ' dg-monospace' : '') + cls + '">{0}</div>') : '{0}').format(col.template));
  			} else
  				col.template = TC(('<div class="' + (isbool ? 'dg-bool' : ('dg-value' + (col.monospace ? ' dg-monospace' : ''))) + cls + '"' + (config.allowtitles ? ' title="{{ {0} }}"' : '') + '>{{ {0} }}</div>').format(col.name + (col.currency ? ' | currency(\'{0}\')'.format(col.currency) : col.format != null ? ' | format({0})'.format(col.format && typeof(col.format) === 'string' ? ('\'' + col.format + '\'') : col.format) : '') + (col.type && config.autoformat ? ' | ui_datagrid_autoformat(\'{0}\')'.format(col.type) : '') + (col.empty ? ' | def({0})'.format(col.empty === true || col.empty == '1' ? '' : ('\'' + col.empty + '\'')) : '') + (isbool ? ' | ui_datagrid_checkbox' : '') + (col.colorize ? (' | ui_datagrid_colorize(' + (col.currency || col.format ? 0 : 1) + ')') : '')));
  
  			if (col.header)
  				col.header = TC(col.header);
  			else
  				col.header = TC('{{ text | raw }}');
  
  			if (!col.text)
  				col.text = col.name;
  
  			if (col.text.substring(0, 1) === '.')
  				col.text = '<i class="{0}"></i>'.format(col.text.substring(1));
  
  			if (col.filter !== false && !col.filter)
  				col.filter = config.filterlabel;
  
  			if (col.filtervalue != null) {
  				tmp = col.filtervalue;
  				if (typeof(tmp) === 'function')
  					tmp = tmp(col);
  				opt.filter[col.name] = opt.filtervalues[col.id] = tmp;
  			}
  		}
  
  		cols.quicksort('index');
  		opt.cols = cols;
  		self.rebindcss();
  		prerender && self.rendercols();
  		controls.hide();
  
  		// self.scrollbar.scroll(0, 0);
  	};
  
  	self.rebindcss = function() {
  
  		var cols = opt.cols;
  		var css = [];
  		var indexes = {};
  
  		opt.width = (config.numbering !== false ? 40 : 0) + (config.checkbox ? 40 : 0) + 30;
  
  		for (var i = 0; i < cols.length; i++) {
  			var col = cols[i];
  
  			if (!col.width)
  				col.width = config.colwidth;
  
  			css.push('.dg-{2} .dg-col-{0}{width:{1}px}'.format(i, col.width, self.IDCSS));
  
  			if (!col.hidden) {
  				opt.width += col.width;
  				indexes[i] = opt.width;
  			}
  		}
  
  		CSS(css, self.ID);
  
  		var w = self.width();
  		if (w > opt.width)
  			opt.width = w - 2;
  
  		if (sheader) {
  			css = { width: opt.width };
  			header.css(css);
  			// vbody.css(css);
  		}
  
  		header && header.find('.dg-resize').each(function() {
  			var el = $(this);
  			el.css('left', indexes[el.attrd('index')] - 39);
  		});
  	};
  
  	self.cols = function(callback) {
  		callback(opt.cols);
  		opt.cols.quicksort('index');
  		self.rebindcss();
  		self.rendercols();
  		opt.rows && self.renderrows(opt.rows);
  		self.save();
  		opt.cluster && opt.cluster.update(opt.render);
  		self.resize();
  	};
  
  	self.rendercols = function() {
  
  		var Trow = '<div class="dg-hrow dg-row-{0}">{1}</div>';
  		var column = config.numbering !== false ? Theadercol({ index: -1, label: config.numbering, filter: false, name: '$', sorting: false }) : '';
  		var resize = [];
  
  		opt.width = (config.numbering !== false ? 40 : 0) + (config.checkbox ? 40 : 0) + 30;
  
  		if (config.checkbox)
  			column += Theadercol({ index: -1, label: '<div class="dg-checkbox dg-checkbox-main" data-value="-1"><i class="fa fa-check"></i></div>', filter: false, name: '$', sorting: false });
  
  		for (var i = 0; i < opt.cols.length; i++) {
  			var col = opt.cols[i];
  			if (!col.hidden) {
  				var filteritems = col.options ? col.options instanceof Array ? col.options : GET(self.makepath(col.options)) : null;
  				var filtervalue = opt.filtervalues[col.id];
  				var obj = { index: i, ts: NOW.getTime(), label: col.header(col), filter: col.filter, reorder: config.reorder, sorting: col.sorting, name: col.name, alignfilter: col.alignfilter, alignheader: col.alignheader, filterval: filtervalue == null ? null : filteritems ? filteritems.findValue(col.ovalue, filtervalue, col.otext, '???') : filtervalue, labeltitle: col.title || col.text, options: filteritems };
  				opt.width += col.width;
  				config.resize && resize.push('<span class="dg-resize" style="left:{0}px" data-index="{1}"></span>'.format(opt.width - 39, i));
  				column += Theadercol(obj);
  			}
  		}
  
  		column += '<div class="dg-hcol"></div>';
  		header[0].innerHTML = resize.join('') + Trow.format(0, column);
  
  		var w = self.width();
  		if (w > opt.width)
  			opt.width = w;
  
  		self.redrawsorting();
  	};
  
  	self.redraw = function(update) {
  		var x = self.scrollbarX.scrollLeft();
  		var y = self.scrollbarY ? self.scrollbarY.scrollTop() : 0;
  		isredraw = update ? 2 : 1;
  		self.refreshfilter();
  		isredraw = 0;
  		self.scrollbarX.scrollLeft(x);
  		self.scrollbarY && self.scrollbarY.scrollTop(y);
  	};
  
  	self.redrawrow = function(oldrow, newrow) {
  		var index = opt.rows.indexOf(oldrow);
  		if (index !== -1) {
  
  			controls.cache = {};
  
  			// Replaces old row with a new
  			if (newrow) {
  				if (self.selected === oldrow)
  					self.selected = newrow;
  				oldrow = opt.rows[index] = newrow;
  			}
  
  			var el = vbody.find('.dg-row[data-index="{0}"]'.format(index));
  			if (el.length) {
  				opt.render[index] = $(self.renderrow(index, oldrow))[0];
  				el[0].parentNode.replaceChild(opt.render[index], el[0]);
  			}
  		}
  	};
  
  	self.appendrow = function(row, scroll, prepend) {
  
  		var index = prepend ? 0 : (opt.rows.push(row) - 1);
  		var model = self.get();
  
  		controls.cache = {};
  
  		if (model == null) {
  			// bad
  			return;
  		} else {
  			var arr = model.items ? model.items : model;
  			if (prepend) {
  				arr.unshift(row);
  			} else if (model.items)
  				arr.push(row);
  			else
  				arr.push(row);
  		}
  
  		if (prepend) {
  			var tmp;
  			// modifies all indexes
  			for (var i = 0; i < opt.render.length; i++) {
  				var node = opt.render[i];
  				if (typeof(node) === 'string')
  					node = opt.render[i] = $(node)[0];
  				var el = $(node);
  				var tmpindex = i + 1;
  				tmp = el.rclass2('dg-row-').aclass('dg-row-' + tmpindex).attrd('index', tmpindex);
  				tmp.find('.dg-number').html(tmpindex + 1);
  				tmp.find('.dg-checkbox-main').attrd('value', tmpindex);
  				if (opt.rows[i])
  					opt.rows[i].ROW = tmpindex;
  			}
  			row.ROW = index;
  			tmp = {};
  			var keys = Object.keys(opt.checked);
  			for (var i = 0; i < keys.length; i++)
  				tmp[(+keys[i]) + 1] = 1;
  			opt.checked = tmp;
  			opt.render.unshift(null);
  		}
  
  		opt.render[index] = $(self.renderrow(index, row))[0];
  		opt.cluster && opt.cluster.update(opt.render, !opt.scroll || opt.scroll === '-');
  		if (scroll) {
  			var el = opt.cluster.el[0];
  			el.scrollTop = el.scrollHeight;
  		}
  		self.scrolling();
  	};
  
  	self.renderrow = function(index, row, plus) {
  
  		if (plus === undefined && config.exec) {
  			// pagination
  			var val = self.get();
  			plus = (val.page - 1) * val.limit;
  		}
  
  		var Trow = '<div><div class="dg-row dg-row-{0}{3}{4}" data-index="{2}">{1}</div></div>';
  		var Tcol = '<div class="dg-col dg-col-{0}{2}{3}">{1}</div>';
  		var column = '';
  
  		if (config.numbering !== false)
  			column += Tcol.format(-1, '<div class="dg-number">{0}</div>'.format(index + 1 + (plus || 0)));
  
  		if (config.checkbox)
  			column += Tcol.format(-1, '<div class="dg-checkbox-main dg-checkbox{1}" data-value="{0}"><i class="fa fa-check"></i></div>'.format(row.ROW, opt.checked[row.ROW] ? ' dg-checked' : ''));
  
  		for (var j = 0; j < opt.cols.length; j++) {
  			var col = opt.cols[j];
  			if (!col.hidden)
  				column += Tcol.format(j, col.template(row), col.align, row.CHANGES && row.CHANGES[col.name] ? ' dg-col-changed' : '');
  		}
  
  		column += '<div class="dg-col">&nbsp;</div>';
  		var rowcustomclass = opt.rowclasstemplate ? opt.rowclasstemplate(row) : '';
  		return Trow.format(index + 1, column, index, self.selected === row ? ' dg-selected' : '', (row.CHANGES ? ' dg-row-changed' : '') + (rowcustomclass || ''));
  	};
  
  	self.renderrows = function(rows, noscroll) {
  
  		opt.rows = rows;
  		controls.cache = {};
  
  		var output = [];
  		var plus = 0;
  
  		if (config.exec) {
  			// pagination
  			var val = self.get();
  			plus = (val.page - 1) * val.limit;
  		}
  
  		var is = false;
  
  		for (var i = 0, length = rows.length; i < length; i++) {
  			var row = rows[i];
  			if (!is && self.selected) {
  				if (self.selected === row) {
  					is = true;
  				} else if (config.clickid && self.selected[config.clickid] === row[config.clickid]) {
  					self.selected = row;
  					is = true;
  				}
  			}
  
  			output.push(self.renderrow(i, rows[i], plus));
  		}
  
  		var min = ((((opt.height || config.minheight) - 120) / config.rowheight) >> 0) + 1;
  		var is = output.length < min;
  		if (is) {
  			for (var i = output.length; i < min + 1; i++)
  				output.push('<div class="dg-row-empty">&nbsp;</div>');
  		}
  
  		self.tclass('dg-noscroll', is);
  
  		if (noscroll) {
  			self.scrollbarX.scrollLeft(0);
  			self.scrollbarY && self.scrollbarY.scrollTop(0);
  		}
  
  		opt.render = output;
  		self.onrenderrows && self.onrenderrows(opt);
  	};
  
  	self.exportrows = function(page_from, pages_count, callback, reset_page_to, sleep) {
  
  		var arr = [];
  		var source = self.get();
  
  		if (reset_page_to === true)
  			reset_page_to = source.page;
  
  		if (page_from === true)
  			reset_page_to = source.page;
  
  		pages_count = page_from + pages_count;
  
  		if (pages_count > source.pages)
  			pages_count = source.pages;
  
  		for (var i = page_from; i < pages_count; i++)
  			arr.push(i);
  
  		!arr.length && arr.push(page_from);
  
  		var index = 0;
  		var rows = [];
  
  		arr.wait(function(page, next) {
  			opt.scroll = (index++) === 0 ? 'xy' : '';
  			self.get().page = page;
  			self.operation('page');
  			self.onrenderrows = function(opt) {
  				rows.push.apply(rows, opt.rows);
  				setTimeout(next, sleep || 100);
  			};
  		}, function() {
  			self.onrenderrows = null;
  			callback(rows, opt);
  			if (reset_page_to > 0) {
  				self.get().page = reset_page_to;
  				self.operation('page');
  			}
  		});
  	};
  
  	self.reordercolumn = function(index, position) {
  
  		var col = opt.cols[index];
  		if (!col)
  			return;
  
  		var old = col.index;
  
  		opt.cols[index].index = position + (old < position ? 0.2 : -0.2);
  		opt.cols.quicksort('index');
  
  		for (var i = 0; i < opt.cols.length; i++) {
  			col = opt.cols[i];
  			col.index = i;
  		}
  
  		opt.cols.quicksort('index');
  
  		self.rebindcss();
  		self.rendercols();
  		self.renderrows(opt.rows);
  		opt.sort && opt.sort.sort && self.redrawsorting();
  		opt.cluster && opt.cluster.update(opt.render, true);
  		self.scrolling();
  
  		controls.hide();
  		config.remember && self.save();
  	};
  
  	self.resizecolumn = function(index, size) {
  		opt.cols[index].width = size;
  		self.rebindcss();
  		config.remember && self.save();
  		self.resize();
  	};
  
  	self.save = function() {
  
  		var cache = {};
  
  		for (var i = 0; i < opt.cols.length; i++) {
  			var col = opt.cols[i];
  			col.index = i;
  			cache[col.realindex] = { index: col.index, width: col.width, hidden: col.hidden };
  		}
  
  		if (W.PREF)
  			W.PREF.set(self.gridid, cache, '1 month');
  		else
  			CACHE(self.gridid, cache, '1 month');
  	};
  
  	self.rows = function() {
  		return opt.rows.slice(0);
  	};
  
  	var resizecache = {};
  
  	self.resize = function() {
  		setTimeout2(self.ID + 'resize', self.resizeforce, 100);
  	};
  
  	self.resizeforce = function() {
  
  		if (!opt.cols || HIDDEN(self.dom))
  			return;
  
  		var el;
  		var footerh = opt.footer = footer.length ? footer.height() : 0;
  
  		if (typeof(config.height) === 'string' && config.height.substring(0, 6) === 'parent') {
  
  			el = self.element.parent();
  
  			var count = +config.height.substring(6);
  			if (count) {
  				for (var i = 0; i < count; i++)
  					el = el.parent();
  			}
  
  			opt.height = (el.height() - config.margin);
  
  		} else {
  			switch (config.height) {
  				case 'auto':
  					var wh = config.parent ? self.parent(config.parent).height() : WH;
  					el = self.element;
  					opt.height = (wh - (el.offset().top + config.margin));
  					break;
  				case 'window':
  					opt.height = WH - config.margin;
  					break;
  				case 'fluid':
  					opt.height = (opt.rows ? opt.rows.length : 0) * config.rowheight;
  					break;
  				default:
  
  					if (config.height > 0) {
  						opt.height = config.height;
  					} else {
  						el = self.element.closest(config.height);
  						opt.height = ((el.length ? el.height() : 200) - config.margin);
  					}
  					break;
  			}
  		}
  
  		var mr = (vbody.parent().css('margin-right') || '').parseInt();
  		var h = opt.height - footerh;
  		var sh = SCROLLBARWIDTH();
  		controls.hide();
  
  		if (config.height === 'fluid') {
  			var mh = config.minheight;
  			if (h < mh)
  				h = mh;
  		} else if (config.height === 'auto') {
  			var mh = config.minheight;
  			if (h < mh)
  				h = mh;
  		}
  
  		var ismobile = isMOBILE && isTOUCH;
  
  		if (resizecache.mobile !== ismobile && !config.noborder) {
  			resizecache.mobile = ismobile;
  			self.tclass('dg-mobile', ismobile);
  		}
  
  		if (resizecache.h !== h) {
  			resizecache.h = h;
  			sheader.css('height', h);
  		}
  
  		var tmpsh = h - (sh ? (sh + self.scrollbarX.thinknessX - 2) : (footerh - 2));
  		resizecache.tmpsh = h;
  		sbody.css('height', tmpsh + self.scrollbarX.marginY + (config.exec && self.scrollbarX.size.empty ? footerh : 0));
  
  		var w;
  
  		if (config.fullwidth_xs && WIDTH() === 'xs' && isMOBILE) {
  			var isfrm = false;
  			try {
  				isfrm = W.self !== W.top;
  			} catch (e) {
  				isfrm = true;
  			}
  			if (isfrm) {
  				w = screen.width - (self.element.offset().left * 2);
  				if (resizecache.wmd !== w) {
  					resizecache.wmd = w;
  					self.css('width', w);
  				}
  			}
  		}
  
  		if (w == null)
  			w = self.width();
  
  		var emptyspace = 50 - mr;
  		if (emptyspace < 50)
  			emptyspace = 50;
  
  		var width = (config.numbering !== false ? 40 : 0) + (config.checkbox ? 40 : 0) + emptyspace;
  
  		for (var i = 0; i < opt.cols.length; i++) {
  			var col = opt.cols[i];
  			if (!col.hidden)
  				width += col.width + 1;
  		}
  
  		if (w > width)
  			width = w - 2;
  
  		if (resizecache.hc !== h) {
  			resizecache.hc = h;
  			container.css('height', h);
  		}
  
  		if (resizecache.width !== width) {
  			resizecache.width = width;
  			header.css('width', width);
  			vbody.css('width', width);
  			sbody.css('width', width);
  			opt.width2 = w;
  		}
  
  		opt.height = h + footerh;
  		self.scrollbarX.resize();
  		self.scrollbarY && self.scrollbarY.resize();
  
  		ready = true;
  		// header.parent().css('width', self.scrollbar.area.width());
  	};
  
  	self.refreshfilter = function(useraction) {
  
  		// Get data
  		var obj = self.get() || EMPTYARRAY;
  		var items = (obj instanceof Array ? obj : obj.items) || EMPTYARRAY;
  		var output = [];
  
  		if (isredraw) {
  			if (isredraw === 2) {
  				self.fn_in_checked();
  				self.fn_in_changed();
  			}
  		} else {
  			opt.checked = {};
  			config.checkbox && header.find('.dg-checkbox-main').rclass('dg-checked');
  			self.fn_in_checked(EMPTYARRAY);
  		}
  
  		for (var i = 0, length = items.length; i < length; i++) {
  			var item = items[i];
  
  			item.ROW = i;
  
  			if (!config.exec) {
  				if (opt.filter && !self.filter(item))
  					continue;
  				if (opt.search) {
  					for (var j = 0; j < opt.cols.length; j++) {
  						var col = opt.cols[j];
  						if (col.search)
  							item['$' + col.name] = col.search(item);
  					}
  				}
  			}
  
  			output.push(item);
  		}
  
  		if (!isredraw) {
  
  			if (opt.scroll) {
  
  				if (self.scrollbarY && (/y/).test(opt.scroll))
  					self.scrollbarY.scrollTop(0);
  
  				if ((/x/).test(opt.scroll)) {
  					if (useraction)	{
  						var sl = self.scrollbarX.scrollLeft();
  						self.scrollbarX.scrollLeft(sl ? sl - 1 : 0);
  					} else
  						self.scrollbarX.scrollLeft(0);
  				}
  
  				opt.scroll = '';
  			}
  
  			if (opt.sort != null) {
  				if (!config.exec)
  					opt.sort.sort && output.quicksort(opt.sort.name, opt.sort.sort === 1);
  				self.redrawsorting();
  			}
  		}
  
  		self.resize();
  		self.renderrows(output, isredraw);
  
  		setTimeout(self.resize, 100);
  		opt.cluster && opt.cluster.update(opt.render, !opt.scroll || opt.scroll === '-');
  		self.scrolling();
  
  		if (isredraw) {
  			if (isredraw === 2) {
  				// re-update all items
  				self.select(self.selected || null);
  			}
  		} else {
  			var sel = self.selected;
  			if (config.autoselect && output && output.length) {
  				setTimeout(function() {
  					var index = sel ? output.indexOf(sel) : 0;
  					if (index === -1)
  						index = 0;
  					self.select(output[index]);
  				}, 1);
  			} else {
  				var index = sel ? output.indexOf(sel) : -1;
  				self.select(index === -1 ? null : output[index]);
  			}
  		}
  	};
  
  	self.redrawsorting = function() {
  		var arr = self.find('.dg-sorting');
  		for(var i = 0; i < arr.length; i++) {
  			var el = $(arr[i]);
  			var col = opt.cols[+el.attrd('index')];
  			if (col) {
  				var fa = el.find('.dg-sort').rclass2('fa-');
  				switch (col.sort) {
  					case 1:
  						fa.aclass('fa-arrow-up');
  						break;
  					case 2:
  						fa.aclass('fa-arrow-down');
  						break;
  					default:
  						fa.aclass('fa-sort');
  						break;
  				}
  			}
  		}
  		controls.hide();
  	};
  
  	self.resetcolumns = function() {
  
  		if (W.PREF)
  			W.PREF.set(self.gridid);
  		else
  			CACHE(self.gridid, null, '-1 day');
  
  		self.rebind(opt.declaration);
  		self.cols(NOOP);
  		ecolumns.aclass('hidden');
  		isecolumns = false;
  		controls.hide();
  	};
  
  	self.resetfilter = function() {
  		opt.filter = {};
  		opt.filtercache = {};
  		opt.filtercl = {};
  		opt.filtervalues = {};
  		opt.cols && self.rendercols();
  		if (config.exec)
  			self.operation('refresh');
  		else
  			self.refresh();
  		controls.hide();
  	};
  
  	var pagecache = { pages: -1, count: -1 };
  
  	self.redrawpagination = function() {
  
  		if (!config.exec)
  			return;
  
  		var value = self.get();
  
  		if (!value.page)
  			value.page = 1;
  
  		if (value.pages == null)
  			value.pages = 0;
  
  		if (value.count == null)
  			value.count = 0;
  
  		var is = false;
  
  		if (value.page === 1 || (value.pages != null && value.count != null)) {
  			pagecache.pages = value.pages;
  			pagecache.count = value.count;
  			is = true;
  		}
  
  		footer.find('button').each(function() {
  
  			var el = $(this);
  			var dis = true;
  
  			switch (this.name) {
  				case 'page-next':
  					dis = value.page >= pagecache.pages;
  					break;
  				case 'page-prev':
  					dis = value.page === 1;
  					break;
  				case 'page-last':
  					dis = !value.page || value.page >= pagecache.pages;
  					break;
  				case 'page-first':
  					dis = value.page === 1;
  					break;
  			}
  
  			el.prop('disabled', dis);
  		});
  
  		footer.find('input')[0].value = value.page;
  
  		if (is) {
  			var num = pagecache.pages || 0;
  			footer.find('.dg-pagination-pages')[0].innerHTML = num.pluralize.apply(num, config.pluralizepages);
  			num = pagecache.count || 0;
  			footer.find('.dg-pagination-items')[0].innerHTML = num.pluralize.apply(num, config.pluralizeitems);
  		}
  
  		footer.rclass('hidden');
  	};
  
  	self.setter = function(value, path, type) {
  
  		if (!ready) {
  			setTimeout(self.setter, 100, value, path, type);
  			return;
  		}
  
  		controls.hide();
  
  		if (config.exec && (value == null || value.items == null)) {
  			self.operation('refresh');
  			if (value && value.items == null)
  				value.items = [];
  			else
  				return;
  		}
  
  		if (value && value.schema && schemas.$current !== value.schema) {
  			schemas.$current = value.schema;
  			self.selected = null;
  			self.rebind(schemas[value.schema], true);
  			setTimeout(function() {
  				self.setter(value, path, type);
  			}, 100);
  			return;
  		}
  
  		if (!opt.cols)
  			return;
  
  		opt.checked = {};
  
  		if (forcescroll) {
  			opt.scroll = forcescroll;
  			forcescroll = '';
  		} else
  			opt.scroll = type !== 'noscroll' ? 'xy' : '';
  
  		self.applycolumns();
  		self.refreshfilter();
  		self.redrawsorting();
  		self.redrawpagination();
  		self.fn_in_changed();
  		!config.exec && self.rendercols();
  		setTimeout2(self.ID + 'resize', self.resize, 100);
  
  		if (opt.cluster)
  			return;
  
  		config.exec && self.rendercols();
  		opt.cluster = new Cluster(vbody);
  		opt.cluster.grid = self;
  		opt.cluster.scroll = self.scrolling;
  		opt.render && opt.cluster.update(opt.render);
  		self.aclass('dg-visible');
  	};
  
  	self.scrolling = function() {
  		config.checkbox && setTimeout2(self.ID, function() {
  			vbody.find('.dg-checkbox-main').each(function() {
  				$(this).tclass('dg-checked', opt.checked[this.getAttribute('data-value')] == 1);
  			});
  		}, 80, 10);
  	};
  
  	var REG_STRING = /\/\|\\|,/;
  	var REG_DATE1 = /\s-\s/;
  	var REG_DATE2 = /\/|\||\\|,/;
  	var REG_SPACE = /\s/g;
  
  	self.filter = function(row) {
  		var keys = Object.keys(opt.filter);
  		for (var i = 0; i < keys.length; i++) {
  
  			var column = keys[i];
  			var filter = opt.filter[column];
  			var val2 = opt.filtercache[column];
  			var val = row['$' + column] || row[column];
  			var type = typeof(val);
  
  			if (val instanceof Array) {
  				val = val.join(' ');
  				type = 'string';
  			} else if (val && type === 'object' && !(val instanceof Date)) {
  				val = JSON.stringify(val);
  				type = 'string';
  			}
  
  			if (type === 'number') {
  
  				if (val2 == null)
  					val2 = opt.filtercache[column] = self.parseNumber(filter);
  
  				if (val2.length === 1 && val !== val2[0])
  					return false;
  
  				if (val < val2[0] || val > val2[1])
  					return false;
  
  			} else if (type === 'string') {
  
  				var is = false;
  
  				if (opt.filtercl[column] != null) {
  					is = opt.filtercl[column] == val;
  					if (!is)
  						return false;
  				}
  
  				if (val2 == null) {
  					val2 = opt.filtercache[column] = filter.split(REG_STRING).trim();
  					for (var j = 0; j < val2.length; j++)
  						val2[j] = val2[j].toSearch();
  				}
  
  				var s = val.toSearch();
  
  				for (var j = 0; j < val2.length; j++) {
  					if (s.indexOf(val2[j]) !== -1) {
  						is = true;
  						break;
  					}
  				}
  
  				if (!is)
  					return false;
  
  			} else if (type === 'boolean') {
  				if (val2 == null)
  					val2 = opt.filtercache[column] = typeof(filter) === 'string' ? config.boolean.indexOf(filter.replace(REG_SPACE, '')) !== -1 : filter;
  				if (val2 !== val)
  					return false;
  			} else if (val instanceof Date) {
  
  				val.setHours(0);
  				val.setMinutes(0);
  
  				if (val2 == null) {
  
  					val2 = filter.trim().replace(REG_DATE1, '/').split(REG_DATE2).trim();
  					var arr = opt.filtercache[column] = [];
  
  					for (var j = 0; j < val2.length; j++) {
  						var dt = val2[j].trim();
  						var a = self.parseDate(dt, j === 1);
  						if (a instanceof Array) {
  							if (val2.length === 2) {
  								arr.push(j ? a[1] : a[0]);
  							} else {
  								arr.push(a[0]);
  								if (j === val2.length - 1) {
  									arr.push(a[1]);
  									break;
  								}
  							}
  						} else
  							arr.push(a);
  					}
  
  					if (val2.length === 2 && arr.length === 2) {
  						arr[1].setHours(23);
  						arr[1].setMinutes(59);
  						arr[1].setSeconds(59);
  					}
  
  					val2 = arr;
  				}
  
  				if (val2.length === 1) {
  					if (val2[0].YYYYMM)
  						return val.format('yyyyMM') === val2[0].format('yyyyMM');
  					if (val.format('yyyyMMdd') !== val2[0].format('yyyyMMdd'))
  						return false;
  				}
  
  				if (val < val2[0] || val > val2[1])
  					return false;
  
  			} else
  				return false;
  		}
  
  		return true;
  	};
  
  	self.checked = function() {
  		var arr = Object.keys(opt.checked);
  		var output = [];
  		var model = self.get() || EMPTYARRAY;
  		var rows = model instanceof Array ? model : model.items;
  		for (var i = 0; i < arr.length; i++) {
  			var index = +arr[i];
  			output.push(rows[index]);
  		}
  		return output;
  	};
  
  	self.readfilter = function() {
  		return opt.filter;
  	};
  
  	self.changed = function() {
  		var output = [];
  		var model = self.get() || EMPTYARRAY;
  		var rows = model instanceof Array ? model : model.items;
  		for (var i = 0; i < rows.length; i++)
  			rows[i].CHANGES && output.push(rows[i]);
  		return output;
  	};
  
  	self.parseDate = function(val, second) {
  
  		var index = val.indexOf('.');
  		var m, y, d, a, special, tmp;
  
  		if (index === -1) {
  			if ((/[a-z]+/).test(val)) {
  				var dt;
  				try {
  					dt = NOW.add(val);
  				} catch (e) {
  					return [0, 0];
  				}
  				return dt > NOW ? [NOW, dt] : [dt, NOW];
  			}
  			if (val.length === 4)
  				return [new Date(+val, 0, 1), new Date(+val + 1, 0, 1)];
  		} else if (val.indexOf('.', index + 1) === -1) {
  			a = val.split('.');
  			if (a[1].length === 4) {
  				y = +a[1];
  				m = +a[0] - 1;
  				d = second ? new Date(y, m, 0).getDate() : 1;
  				special = true;
  			} else {
  				y = NOW.getFullYear();
  				m = +a[1] - 1;
  				d = +a[0];
  			}
  
  			tmp = new Date(y, m, d);
  			if (special)
  				tmp.YYYYMM = true;
  			return tmp;
  		}
  		index = val.indexOf('-');
  		if (index !== -1 && val.indexOf('-', index + 1) === -1) {
  			a = val.split('-');
  			if (a[0].length === 4) {
  				y = +a[0];
  				m = +a[1] - 1;
  				d = second ? new Date(y, m, 0).getDate() : 1;
  				special = true;
  			} else {
  				y = NOW.getFullYear();
  				m = +a[0] - 1;
  				d = +a[1];
  			}
  
  			tmp = new Date(y, m, d);
  
  			if (special)
  				tmp.YYYYMM = true;
  
  			return tmp;
  		}
  
  		return val.parseDate();
  	};
  
  	var REG_NUM1 = /\s-\s/;
  	var REG_COMMA = /,/g;
  	var REG_NUM2 = /\/|\|\s-\s|\\/;
  
  	self.parseNumber = function(val) {
  		var arr = [];
  		var num = val.replace(REG_NUM1, '/').replace(REG_SPACE, '').replace(REG_COMMA, '.').split(REG_NUM2).trim();
  		for (var i = 0, length = num.length; i < length; i++) {
  			var n = num[i];
  			arr.push(+n);
  		}
  		return arr;
  	};
  
  	self.datagrid_cancel = function(meta, force) {
  		var current = self.editable;
  		if (current && current.is) {
  			current.is = false;
  			force && current.el.replaceWith(current.backup);
  			current.input && current.input.off();
  			$(W).off('keydown', current.fn).off('click', current.fn);
  		}
  	};
  
  	self.datagrid_edit = function(meta, next) {
  
  		if (!meta || !meta.col.editable)
  			return;
  
  		if (!self.editable)
  			self.editable = {};
  
  		var el = meta.elcol;
  		var current = self.editable;
  		current.is && self.datagrid_cancel(meta, true);
  		current.is = true;
  
  		current.backup = el.find('.dg-editable').aclass('dg-editable').clone();
  		el = el.find('.dg-editable');
  
  		if (!meta.col.type) {
  			if (meta.value instanceof Date)
  				meta.col.type = 'date';
  			else
  				meta.col.type = typeof(meta.value);
  		}
  
  		if (meta.col.options) {
  			current.el = el;
  			var opt = {};
  			opt.element = el;
  			opt.items = meta.col.options;
  
  			if (typeof(opt.items) === 'string')
  				opt.items = self.makepath(opt.items);
  
  			opt.key = meta.col.otext;
  			opt.placeholder = meta.col.dirsearch ? meta.col.dirsearch : '';
  			if (meta.col.dirsearch === false)
  				opt.search = false;
  			opt.callback = function(item) {
  				current.is = false;
  				meta.value = item[meta.col.ovalue];
  				next(meta);
  				self.datagrid_cancel(meta);
  			};
  			SETTER('directory', 'show', opt);
  			return;
  		}
  
  		var align = meta.col.align;
  		el.rclass('dg-value').html(meta.col.type.substring(0, 4) === 'bool' ? '<div{1}><div class="dg-checkbox{0}" data-custom="2"><i class="fa fa-check"></i></div></div>'.format(meta.value ? ' dg-checked' : '', align ? (' class="' + align.trim() + '"') : '') : '<input type="{0}" maxlength="{1}"{2} />'.format(meta.col.ispassword ? 'password' : 'text', meta.col.maxlength || 100, align ? (' class="' + align.trim() + '"') : ''));
  		current.el = el;
  
  		var input = meta.elcol.find('input');
  		input.val(meta.value instanceof Date ? meta.value.format(meta.col.format) : meta.value);
  		input.focus();
  		current.input = input;
  
  		if (meta.col.type === 'date') {
  			// DATE
  			var opt = {};
  			opt.element = el;
  			opt.value = meta.value;
  			opt.callback = function(date) {
  				current.is = false;
  				meta.value = date;
  				next(meta);
  				self.datagrid_cancel(meta);
  			};
  			SETTER('datepicker', 'show', opt);
  		}
  
  		current.fn = function(e) {
  
  			if (!current.is)
  				return;
  
  			if (e.type === 'click') {
  				if (e.target.tagName === 'INPUT')
  					return;
  				e.preventDefault();
  				e.keyCode = 13;
  				if (meta.col.type === 'date') {
  					e.type = 'keydown';
  					setTimeout(current.fn, 800, e);
  					return;
  				} else if (meta.col.type.substring(0, 4) === 'bool') {
  					var tmp = $(e.target);
  					var is = tmp.hclass('dg-checkbox');
  					if (!is) {
  						tmp = tmp.closest('.dg-checkbox');
  						is = tmp.length;
  					}
  					if (is) {
  						meta.value = tmp.hclass('dg-checked');
  						next(meta);
  						self.datagrid_cancel(meta);
  						return;
  					}
  				}
  			}
  
  			switch (e.keyCode) {
  				case 13: // ENTER
  				case 9: // TAB
  
  					var val = input.val();
  					if (val == meta.value) {
  						next = null;
  						self.datagrid_cancel(meta, true);
  					} else {
  
  						if (meta.col.type === 'number') {
  							val = val.parseFloat();
  							if (val == meta.value || (meta.min != null && meta.min > val) || (meta.max != null && meta.max < val)) {
  								next = null;
  								self.datagrid_cancel(meta, true);
  								return;
  							}
  						} else if (meta.col.type === 'date') {
  
  							val = val.parseDate(meta.format ? meta.format.env() : undefined);
  
  							if (!val || isNaN(val.getTime()))
  								val = null;
  
  							if (val && meta.value && val.getTime() === meta.value.getTime()) {
  								next = null;
  								self.datagrid_cancel(meta, true);
  								return;
  							}
  						}
  
  						if (meta.col.required && (val == null || val === '')) {
  							// WRONG VALUE
  							self.datagrid_cancel(meta, true);
  							return;
  						}
  
  						meta.value = val;
  						next(meta);
  						self.datagrid_cancel(meta);
  					}
  
  					if (e.which === 9) {
  
  						// tries to edit another field
  						var elcol = meta.elcol;
  
  						while (true) {
  							elcol = elcol.next();
  							if (!elcol.length)
  								break;
  
  							var eledit = elcol.find('.dg-editable');
  							if (eledit.length) {
  								setTimeout(function(meta, elcol) {
  									self.editcolumn(meta.rowindex, +elcol.attr('class').match(/\d+/)[0]);
  								}, 200, meta, elcol);
  								break;
  							}
  						}
  					}
  
  					break;
  
  				case 27: // ESC
  					next = null;
  					self.datagrid_cancel(meta, true);
  					break;
  			}
  		};
  
  		$(W).on('keydown', current.fn).on('click', current.fn);
  	};
  });