<?php

Lib::loadClass('ViewAjax');

class TreeAjax extends ViewAjax {

	private $_cnf = '';// Column
	private $_htmlID = '';
	private $_dataSource = null;
	private $_rowFunctions = array();

	public function __construct($tblAcl) {
		$this->_tbl = $tblAcl->getName();
		$this->_acl = $tblAcl;
		$this->_zasobID = $tblAcl->getID();

		Lib::loadClass('Data_Tree_Source');
		$this->_dataSource = new Data_Tree_Source($tblAcl->getDB());
		$this->_dataSource->setTable($tblAcl->getName());
		$this->_dataSource->setCols($tblAcl->getRealFieldList());
		$this->_dataSource->setVirtualCols($tblAcl->getVirtualFieldList());

		// TODO: mv to config or Zasoby (ALIAS?)
		switch ($tblAcl->getName()) {
			case 'IN7_MK_BAZA_DYSTRYBUCJI':
				$this->_dataSource->setParentIdField('P_ID');
				$this->_dataSource->setNameField('ID');//M_DIST_DESC
				break;
			case 'CRM_LISTA_ZASOBOW':
				$this->_dataSource->setParentIdField('PARENT_ID');
				$this->_dataSource->setNameField('ID');// 'DESC_PL'
				$this->_dataSource->setSortField('SORT_PRIO');
				break;
			case 'CRM_PROCES':
				$this->_dataSource->setParentIdField('PARENT_ID');
				$this->_dataSource->setNameField('ID');
				$this->_dataSource->setSortField('SORT_PRIO');
				break;
			default:
				
		}

		$hash = V::get('_hash', '', $_GET);
		$this->_htmlID = ($hash)? $hash : $this->_tbl . '-Tree';//uniqid($this->_tbl);
		// TODO: load filters and state: table, edit, add, etc.
		// TODO: save this data in Workspace 1, 2, 3, etc.
	}

	public function renderRootNode() {
		?>
		<tr data-tt-id="0" data-tt-parent-id="" data-tt-branch="true">
			<td>
				<span class="directory">0</span>
			</td>
			<td coslpan="<?php echo count($fieldList); ?>"></td>
		</tr>
		<?php
	}

	public function renderNode($node) {
		$visibleCols = $this->_acl->getVisibleFieldList();
		$fieldList = array();
		foreach ($visibleCols as $fieldID => $col) {
			if ($col == 'ID') {// || $col == $this->_dataSource->getParentIdField()) {
				continue;
			}
			$fieldList[] = $col;
		}

		?>
		<tr data-tt-id="<?php echo $node->id; ?>" data-tt-parent-id="<?php echo ($node->parent_id > 0)? $node->parent_id : ''; ?>" data-tt-branch="<?php echo ($node->type == 'folder')? 'true' : 'false'; ?>" class="ui-state-default">
			<td class="TreeAjax-cell-indenter">
				<span class="<?php echo $node->type; ?>">
					<i class="icon-folder-open"></i>
					<?php echo $node->name; ?>
				</span>
			</td>
			<?php if ($this->_dataSource->hasSortField()) : ?>
				<td class="TreeAjax-cell-sorter">
					<i class="sort-handler icon-resize-vertical"></i>
				</td>
			<?php endif; ?>
			<?php if (true) : ?>
				<td class="TreeAjax-cell-edit">
					<a href="#EDIT/<?php echo $node->id; ?>" class="icon-pencil" title="Edytuj rekord"> </a>
				</td>
			<?php endif; ?>
			<?php foreach ($fieldList as $fieldName) : $val = V::get($fieldName, '', $node->data); ?>
				<td class="TreeAjax-cell-data tbl-short-txt"><span title="<?php echo $val; ?>"><?php echo $val ?></span></td>
			<?php endforeach; ?>
		</tr>
		<?php
	}

	public function render() {
		if (!$this->_dataSource->isValidTree()) {
			$msg = "Błąd nie można wyświetlić drzewa dla tej tabeli";
			$msg = '<div class="alert alert-error">' . $msg . '</div>';
			$msg = '<div class="container">' . $msg . '</div>';
			return $msg;
		}

		$visibleCols = $this->_acl->getVisibleFieldList();
		$fieldList = array();
		foreach ($visibleCols as $fieldID => $col) {
			if ($col == 'ID') {// || $col == $this->_dataSource->getParentIdField()) {
				continue;
			}
			$fieldList[] = $col;
		}

		$nodes = $this->_dataSource->getTreeNodes(0, array('order_by'=>'SORT_PRIO'));

		$pInitList = array();
		$forceFilterInit = array();

		ob_start();
		// class="TreeAjax table table-striped table-hover table-bordered table-condensed"
		?>
		<link rel="stylesheet" href="stuff/jquery.treetable/jquery.treetable.css" type="text/css" />
		<link rel="stylesheet" href="stuff/jquery.treetable/jquery.treetable.theme.default.css" type="text/css" />
		<style type="text/css">
.treetable .icon-folder-open { margin-top:0; }
.treetable tr.selected  .icon-folder-open { background-image: url(stuff/twitter-bootstrap/img/glyphicons-halflings-white.png); }
.treetable {white-space:nowrap; width:auto;}
.treetable .TreeAjax-cell-sorter { cursor:pointer; }
.treetable td,
.treetable th { border-color:#999; }
.AjaxTreeCont .breadcrumb em { color:#bbb; }
.AjaxTreeCont .treetable tr.selected td { background-color:#3875D7; color:#FFF; }

/* loading */
.AjaxTreeCont .loading-info {display:none;}
.AjaxTreeCont .inlineEditBox .loading-info {display:block;}
.AjaxTree-loading .loading-info,
.AjaxTreeCont .inlineEditBox .loading-info {display:block; padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;}

/* form btn Cofnij: .appendBack */
.AjaxTreeCont .AjaxTableEdit .show-last-value .btn-appendBack,
.AjaxTreeCont .AjaxFrmHorizontalEdit .show-last-value .btn-appendBack {display:none;}

		</style>

		<div class="AjaxTreeCont">

		<ul class="breadcrumb">
			<li><a href="#" onclick="return treeAjaxBackToTree();"><?php echo $this->getLabelHtml(); ?></a></li>
			<?php if (!empty($pInitList)) : ?>
				<div class="btn-group pull-right">
					<a class="btn btn-mini btn-info dropdown-toggle" data-toggle="dropdown" href="#" title="Uruchom filtr procesu powiązanego z tabelą <?php echo $this->getLabel(); ?>">
						Filtr procesu
						<span class="caret"></span>
					</a>
					<ul class="dropdown-menu pull-left">
						<?php foreach ($pInitList as $kId => $vLabel) : ?>
							<?php if ($pInitListSelected && $pInitListSelected == $kId) : ?>
							<li class="disabled">
							<?php else : ?>
							<li>
							<?php endif; ?>
								<a href="index.php?FUNCTION_INIT=MENU_SELECT_PROCES&_action=setPermsByProces&id_proces=<?php echo $kId; ?>&MENU_INIT=VIEWTABLE_AJAX&ZASOB_ID=<?php echo $this->_zasobID; ?>" title="<?php echo htmlspecialchars("{{$kId}} {$vLabel}"); ?>"><?php echo "{{$kId}} " . V::strShortUtf8($vLabel, 50); ?></a>
							</li>
						<?php endforeach; ?>
					</ul>
				</div>
			<?php endif; ?>

			<a class="pull-right" style="padding:0 20px 0 0;" href="<?php echo "index.php?MENU_INIT=VIEWTABLE_AJAX&ZASOB_ID={$this->_zasobID}"; ?>"><i class="icon-list"></i> Tabela</a>
		</ul>
		<table id="<?php echo $this->_htmlID; ?>" class="TreeAjax table table-bordered">
			<thead>
				<tr>
					<th></th>
					<?php if ($this->_dataSource->hasSortField()) : ?>
						<th class="TreeAjax-cell-sorter">
							<i title="Sortuj" class="icon-question-sign"></i>
						</th>
					<?php endif; ?>
					<?php if (true) : ?>
						<th class="TreeAjax-cell-edit">
							<i title="Edytuj rekord" class="icon-question-sign"></i>
						</th>
					<?php endif; ?>
					<?php foreach ($fieldList as $label) : ?>
						<th><?php echo $label; ?></th>
					<?php endforeach; ?>
				</tr>
			</thead>
			<tbody>
				<?php //$this->renderRootNode(); ?>

				<?php foreach ($nodes as $node) : ?>
					<?php //$this->renderNode($node); ?>
				<?php endforeach; ?>
			</tbody>
		</table>

		</div>

		<script>
/*
 * jQuery treeTable Plugin 3.0.2
 * http://ludo.cubicphuse.nl/jquery-treetable
 *
 * Copyright 2013, Ludo van den Boom
 * Dual licensed under the MIT or GPL Version 2 licenses.
 */
(function() {
  var $, Node, Tree, methods;

  $ = jQuery;

  Node = (function() {
    function Node(row, tree, settings) {
      var parentId;

      this.row = row;
      this.tree = tree;
      this.settings = settings;

      // TODO Ensure id/parentId is always a string (not int)
      this.id = this.row.data(this.settings.nodeIdAttr);

      // TODO Move this to a setParentId function?
      parentId = this.row.data(this.settings.parentIdAttr);
      if (parentId != null && parentId !== "") {
        this.parentId = parentId;
      }

      this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]);
      this.expander = $(this.settings.expanderTemplate);
      this.indenter = $(this.settings.indenterTemplate);
      this.children = [];
      this.initialized = false;
      this.treeCell.prepend(this.indenter);
    }

    Node.prototype.addChild = function(child) {
      return this.children.push(child);
    };

    Node.prototype.ancestors = function() {
      var ancestors, node;
      node = this;
      ancestors = [];
      while (node = node.parentNode()) {
        ancestors.push(node);
      }
      return ancestors;
    };

    Node.prototype.collapse = function() {
      this._hideChildren();
      this.row.removeClass("expanded").addClass("collapsed");
      this.expander.attr("title", this.settings.stringExpand);

      if (this.initialized && this.settings.onNodeCollapse != null) {
        this.settings.onNodeCollapse.apply(this);
      }

      return this;
    };

    // TODO destroy: remove event handlers, expander, indenter, etc.

    Node.prototype.expand = function() {
      if (this.initialized && this.settings.onNodeExpand != null) {
        this.settings.onNodeExpand.apply(this);
      }

      this.row.removeClass("collapsed").addClass("expanded");
      if ($(this.row).is(":visible")) {
        this._showChildren();
      }
      this.expander.attr("title", this.settings.stringCollapse);

      return this;
    };

    Node.prototype.expanded = function() {
      return this.row.hasClass("expanded");
    };

    Node.prototype.hide = function() {
      this._hideChildren();
      this.row.hide();
      return this;
    };

    Node.prototype.isBranchNode = function() {
      if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) {
        return true;
      } else {
        return false;
      }
    };

    Node.prototype.updateBranchLeafClass = function(){
      this.row.removeClass('branch');
      this.row.removeClass('leaf');
      this.row.addClass(this.isBranchNode()?'branch':'leaf');
    };

    Node.prototype.level = function() {
      return this.ancestors().length;
    };

    Node.prototype.parentNode = function() {
      if (this.parentId != null) {
        return this.tree[this.parentId];
      } else {
        return null;
      }
    };

    Node.prototype.removeChild = function(child) {
      var i = $.inArray(child, this.children);
      return this.children.splice(i, 1)
    };

    Node.prototype.render = function() {
      var handler,
          settings = this.settings,
          target;

      if (settings.expandable === true && this.isBranchNode()) {
        handler = function(e) {
          $(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle();
          return e.preventDefault();
        };

        this.indenter.html(this.expander);
        target = settings.clickableNodeNames === true ? this.treeCell : this.expander;

        target.off("click.treetable").on("click.treetable", handler);
        target.off("keydown.treetable").on("keydown.treetable", function(e) {
          if (e.keyCode == 13) {
            handler.apply(this, [e]);
          }
        });
      }

      if (settings.expandable === true && settings.initialState === "collapsed") {
        this.collapse();
      } else {
        this.expand();
      }

      this.indenter[0].style.paddingLeft = "" + (this.level() * settings.indent) + "px";

      return this;
    };

    Node.prototype.reveal = function() {
      if (this.parentId != null) {
        this.parentNode().reveal();
      }
      return this.expand();
    };

    Node.prototype.setParent = function(node) {
      if (this.parentId != null) {
        this.tree[this.parentId].removeChild(this);
      }
      this.parentId = node.id;
      this.row.data(this.settings.parentIdAttr, node.id);
      return node.addChild(this);
    };

    Node.prototype.show = function() {
      if (!this.initialized) {
        this._initialize();
      }
      this.row.show();
      if (this.expanded()) {
        this._showChildren();
      }
      return this;
    };

    Node.prototype.toggle = function() {
      if (this.expanded()) {
        this.collapse();
      } else {
        this.expand();
      }
      return this;
    };

    Node.prototype._hideChildren = function() {
      var child, _i, _len, _ref, _results;
      _ref = this.children;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        child = _ref[_i];
        _results.push(child.hide());
      }
      return _results;
    };

    Node.prototype._initialize = function() {
      this.render();
      if (this.settings.onNodeInitialized != null) {
        this.settings.onNodeInitialized.apply(this);
      }
      return this.initialized = true;
    };

    Node.prototype._showChildren = function() {
      var child, _i, _len, _ref, _results;
      _ref = this.children;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        child = _ref[_i];
        _results.push(child.show());
      }
      return _results;
    };

    return Node;
  })();

  Tree = (function() {
    function Tree(table, settings) {
      this.table = table;
      this.settings = settings;
      this.tree = {};

      // Cache the nodes and roots in simple arrays for quick access/iteration
      this.nodes = [];
      this.roots = [];

			if (typeof this.settings.router == "function") {
				var that = this;
				$(window).bind('hashchange', function() {
					//console.log('hashchange: location.hash: ' + location.hash);
					that.settings.router.apply(this);
				});

				that.settings.router.apply(this);
			}
    }

    Tree.prototype.collapseAll = function() {
      var node, _i, _len, _ref, _results;
      _ref = this.nodes;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        _results.push(node.collapse());
      }
      return _results;
    };

    Tree.prototype.expandAll = function() {
      var node, _i, _len, _ref, _results;
      _ref = this.nodes;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        _results.push(node.expand());
      }
      return _results;
    };

    Tree.prototype.findLastNode = function (node) {
      if (node.children.length > 0) {
        return this.findLastNode(node.children[node.children.length - 1]);
      } else {
        return node;
      }
    };

    Tree.prototype.loadRows = function(rows) {
      var node, row, i;

      if (rows != null) {
        for (i = 0; i < rows.length; i++) {
          row = $(rows[i]);

          if (row.data(this.settings.nodeIdAttr) != null) {
            node = new Node(row, this.tree, this.settings);
            this.nodes.push(node);
            this.tree[node.id] = node;

            if (node.parentId != null) {
              if (this.tree[node.parentId]) {
								this.tree[node.parentId].addChild(node);
							} else {
								//console.log('loadRows this.tree[' + node.parentId + ']', this.tree[node.parentId])
							}
            } else {
              this.roots.push(node);
            }
          }
        }
      }

      for (i = 0; i < this.nodes.length; i++) {
        node = this.nodes[i].updateBranchLeafClass();
      }

      return this;
    };

    Tree.prototype.move = function(node, destination) {
      // Conditions:
      // 1: +node+ should not be inserted as a child of +node+ itself.
      // 2: +destination+ should not be the same as +node+'s current parent (this
      //    prevents +node+ from being moved to the same location where it already
      //    is).
      // 3: +node+ should not be inserted in a location in a branch if this would
      //    result in +node+ being an ancestor of itself.
      var nodeParent = node.parentNode();
      if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) {
        node.setParent(destination);
        this._moveRows(node, destination);

        // Re-render parentNode if this is its first child node, and therefore
        // doesn't have the expander yet.
        if (node.parentNode().children.length === 1) {
          node.parentNode().render();
        }
      }

      if(nodeParent){
        nodeParent.updateBranchLeafClass();
      }
      if(node.parentNode()){
        node.parentNode().updateBranchLeafClass();
      }
      node.updateBranchLeafClass();
      return this;
    };

    Tree.prototype.render = function() {
      var root, _i, _len, _ref;
      _ref = this.roots;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        root = _ref[_i];

        // Naming is confusing (show/render). I do not call render on node from
        // here.
        root.show();
      }
      return this;
    };

    Tree.prototype._moveRows = function(node, destination) {
      var child, _i, _len, _ref, _results;
      node.row.insertAfter(destination.row);
      node.render();
      _ref = node.children;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        child = _ref[_i];
        _results.push(this._moveRows(child, node));
      }
      return _results;
    };

    Tree.prototype.unloadBranch = function(node) {
      var child, children, i;

      for (i = 0; i < node.children.length; i++) {
        child = node.children[i];

        // Recursively remove all descendants of +node+
        this.unloadBranch(child);

        // Remove child from DOM (<tr>)
        child.row.remove();

        // Clean up Tree object (so Node objects are GC-ed)
        delete this.tree[child.id];
        this.nodes.splice($.inArray(child, this.nodes), 1);
      }

      // Reset node's collection of children
      node.children = [];

      node.updateBranchLeafClass();

      return this;
    };

		Tree.prototype.updateNodeHtmlData = function(id, newDataHtml) {
			var draggedNode = this.tree[id]
				// draggedNode = this.data("treetable").tree[id];// $("#<?php echo $this->_htmlID; ?>").treetable("node", node.id);
				, cellsOld = draggedNode.row.find('td')
				, cellsNew = $(newDataHtml).find('td')
				, cellsOldCnt = cellsOld.length
				, cellsNewCnt = cellsNew.length;
			if (cellsOldCnt > 1 && cellsOldCnt == cellsNewCnt) {
				for(var i=1; i<cellsOldCnt; i++){
					$(cellsOld[i]).replaceWith(cellsNew[i]);
				}
			}
      return this;
		};

    return Tree;
  })();

  // jQuery Plugin
  methods = {
    init: function(options, force) {
      var settings;

      settings = $.extend({
        branchAttr: "ttBranch",
        clickableNodeNames: false,
        column: 0,
        columnElType: "td", // i.e. 'td', 'th' or 'td,th'
        expandable: false,
        expanderTemplate: "<a href='#'>&nbsp;</a>",
        indent: 19,
        indenterTemplate: "<span class='indenter'></span>",
        initialState: "collapsed",
        nodeIdAttr: "ttId", // maps to data-tt-id
        parentIdAttr: "ttParentId", // maps to data-tt-parent-id
        stringExpand: "Expand",
        stringCollapse: "Collapse",
        router: null, // router

        // Events
        onInitialized: null,
        onNodeCollapse: null,
        onNodeExpand: null,
        onNodeInitialized: null
      }, options);

      return this.each(function() {
        var el = $(this), tree;

        if (force || el.data("treetable") === undefined) {
          tree = new Tree(this, settings);
          tree.loadRows(this.rows).render();

          el.addClass("treetable").data("treetable", tree);

          if (settings.onInitialized != null) {
            settings.onInitialized.apply(tree);
          }
        }

        return el;
      });
    },

    destroy: function() {
      return this.each(function() {
        return $(this).removeData("treetable").removeClass("treetable");
      });
    },

    collapseAll: function() {
      this.data("treetable").collapseAll();
      return this;
    },

    collapseNode: function(id) {
      var node = this.data("treetable").tree[id];

      if (node) {
        node.collapse();
      } else {
        throw new Error("Unknown node '" + id + "'");
      }

      return this;
    },

    expandAll: function() {
      this.data("treetable").expandAll();
      return this;
    },

    expandNode: function(id) {
      var node = this.data("treetable").tree[id];

      if (node) {
        if (!node.initialized) {
          node._initialize();
        }

        node.expand();
      } else {
        throw new Error("Unknown node '" + id + "'");
      }

      return this;
    },

    loadBranch: function(node, rows) {
      var settings = this.data("treetable").settings,
          tree = this.data("treetable").tree;

      // TODO Switch to $.parseHTML
      rows = $(rows);

      if (node == null) { // Inserting new root nodes
        this.append(rows);
      } else {
        var lastNode = this.data("treetable").findLastNode(node);
        rows.insertAfter(lastNode.row);
      }

      this.data("treetable").loadRows(rows);

      // Make sure nodes are properly initialized
      rows.filter("tr").each(function() {
        tree[$(this).data(settings.nodeIdAttr)].show();
      });

      return this;
    },

    move: function(nodeId, destinationId) {
      var destination, node;

      node = this.data("treetable").tree[nodeId];
      destination = this.data("treetable").tree[destinationId];
      this.data("treetable").move(node, destination);

      return this;
    },

    node: function(id) {
      return this.data("treetable").tree[id];
    },

    reveal: function(id) {
      var node = this.data("treetable").tree[id];

      if (node) {
        node.reveal();
      } else {
        throw new Error("Unknown node '" + id + "'");
      }

      return this;
    },

    unloadBranch: function(node) {
      this.data("treetable").unloadBranch(node);
      return this;
    },

		updateNode: function(id, params) {
			var params = params || {};
			if ('html' in params) {
				this.data("treetable").updateNodeHtmlData(id, params.html);
			}
			else {
				var that = this;
				$.ajax({
					data: {},
					type: "GET",
					url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=NODE&ID=' + id
				})
				.done(function(data, textStatus, jqXHR){
					//console.log('move-info: ', data);
					//$("#<?php echo $this->_htmlID; ?>").treetable("updateNode", id, {html: jqXHR.responseText});
					that.data("treetable").updateNodeHtmlData(id, jqXHR.responseText);
					jQuery.notify('Aktualizacja danych ' + id, 'success');
				})
				.fail(function(jqXHR){// jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});
					var txt = jqXHR.responseText || 'Error';
					if (jqXHR.status == 404) {
						jQuery.notify(jqXHR.responseText, 'error');
					} else {
						jQuery.notify(jqXHR.responseText, 'warn');
					}
				});
			}
		}

  };

  $.fn.treetable = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      return $.error("Method " + method + " does not exist on jQuery.treetable");
    }
  };

  // Expose classes to world
  this.TreeTable || (this.TreeTable = {});
  this.TreeTable.Node = Node;
  this.TreeTable.Tree = Tree;
}).call(this);
		</script>
		<script>

function treeAjaxBackToTree() {
	var cont = jQuery('#<?php echo $this->_htmlID; ?>').parent();
	cont.show();
	var taskCont = jQuery('#<?php echo $this->_htmlID . '_task'; ?>').parent();
	taskCont.remove();

	// TODO: reload data?
	//var con = jQuery('#<?php echo $this->_htmlID; ?>');
	//con.TableAjaxLoadPage(0);// TODO: load current page
}

jQuery(document).ready(function(){

/*
	jQuery('#<?php echo $this->_htmlID; ?>').TableAjax({
		url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>',
		debug: true, // TODO: DBG
		height: 400 // TODO: ?
	});
*/

	var table = $("#<?php echo $this->_htmlID; ?>");

	function initializeTableRow() {
		var n = $(this);

		// Highlight on press
		n.on("mousedown", function() {
			$("tr.selected").removeClass("selected");
			$(this).addClass("selected");
		});

		// Drag & Drop Example Code
		n.find(".folder").on("mouseenter", function() {
			var el = $(this);

			if(!el.data("dndInit")) {
				el.data("dndInit", true);

				el.draggable({
					helper: "clone",
					opacity: .75,
					refreshPositions: true, // Performance?
					revert: "invalid",
					revertDuration: 300,
					scroll: true
				});
			}
		});

		// add droppable
		n.droppable({
			accept: ".file, .folder",
			drop: function(e, ui) {
				var droppedEl = ui.draggable.parents("tr")
					, node = $("#<?php echo $this->_htmlID; ?>").treetable("node", droppedEl.data("ttId"))
					, this_ttId = $(this).data("ttId");
				$(this).find(".placeholder").remove();
				// Update server-side tree
				$.ajax({
					data: { MOVE_TO_ID: this_ttId },
					type: "POST",
					url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=MOVE&ID=' + node.id,
				})
				.done(function(data, textStatus, jqXHR){
					//console.log('move-info: ', data);
					$("#<?php echo $this->_htmlID; ?>").treetable("move", node.id, this_ttId);

					$("#<?php echo $this->_htmlID; ?>").treetable("updateNode", node.id, {html: jqXHR.responseText});
					jQuery.notify('Przeniesienie udane', 'success');
				})
				.fail(function(jqXHR){// jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});
					var txt = jqXHR.responseText || 'Error';
					if (jqXHR.status == 404) {
						jQuery.notify(jqXHR.responseText, 'error');
					} else {
						jQuery.notify(jqXHR.responseText, 'warn');
					}
				});
			},
			hoverClass: "accept",
			over: function(e, ui) {
				var droppedEl = ui.draggable.parents("tr");
				if(this != droppedEl[0] && !$(this).is(".expanded")) {
					$("#<?php echo $this->_htmlID; ?>").treetable("expandNode", $(this).data("ttId"));
				}
			}
		});
/*
		.sortable({
			items: "tr:not(.placeholder)",
			sort: function() {
				// gets added unintentionally by droppable interacting with sortable
				// using connectWithSortable fixes this, but doesn't allow you to customize active/hoverClass options
				$(this).removeClass("ui-state-default");
			}
		});
*/

		// add tooltips
		if (false) n.find('.TreeAjax-cell-data').each(function(ind, n){
			var title;
			//console.log('TreeAjax-cell-data.each(n:', n, n.firstChild);
			if(n.firstChild) {
				title = jQuery(n.firstChild).data('original-title');
			}
			if (title) {
				jQuery(n).tooltip({
					title: title,
					html: true,
					container: 'body',
					trigger: 'hover',
					placement: 'top',
					delay: {
						show: 500,
						hide: 100
					}
				});
			}
		});
	}

	table.treetable({
		expandable: true,
		onInitialized: function() {
			//console.log('onInitialized...');

			var node;

			// Render loader/spinner while loading
			$.ajax({
				async: false, // Must be false, otherwise loadBranch happens after showChildren?
				url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=ROOT_NODES',
			})
			.done(function(html) {
				if ($.trim(html) == '') {
					if (node.indenter) {
						$(node.indenter).empty();// no children -> remove indenter
					}
					return false;
				}

				var rows = $(html).filter("tr");

				rows.find(".folder").parents("tr").each(function() {
					initializeTableRow.apply(this);
				})

				table.treetable("loadBranch", node, rows);
			});
		},
		router: function () {
			var routes = {
				EDIT: function (args) {
					var recordID = args;
					if (typeof args == 'object') {
						recordID = args.shift();
						recordID = parseInt(recordID);
					}
					if (typeof recordID !== 'number' || recordID <= 0) {
						// TODO: msg
						return false;
					}
					var cont = jQuery('#<?php echo $this->_htmlID; ?>').parent();
					cont.hide();

					// remove previous task content
					var taskCnt = jQuery('#<?php echo $this->_htmlID; ?>_task');
					taskCnt.parent().remove();
					taskCnt.remove();

					var taskCont = jQuery('<div class="AjaxTreeCont"></div>').insertBefore(cont);
					jQuery('<ul class="breadcrumb">' +
						'<li><a href="#" onclick="return treeAjaxBackToTree();"><?php echo $this->getLabelHtml(); ?></a> <span class="divider">/</span></li>' +
						'<li class="active">Edytuj rekord</li>' +
					'</ul>').appendTo(taskCont);
					taskCnt = jQuery('<div id="<?php echo $this->_htmlID . '_task'; ?>" class="AjaxTableTaskCnt AjaxTable-loading"></div>').appendTo(taskCont);
					jQuery('<span class="loading-info"> loading ...</span>').appendTo(taskCnt);

					jQuery.ajax({
						url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=EDIT&ID=' + recordID,
						type: 'GET',
						dataType: 'text',
						data: '',
						async: true,
						success: function (data) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request finished L.<?php echo __LINE__; ?>');
							jQuery(data).appendTo(taskCnt);

							taskCnt.find('.se_type-date').datepicker({
								format: "yyyy-mm-dd"
								, language: 'pl'
								, todayBtn: "linked"
							});
							taskCnt.find('.se_type-datetime').parent().datetimepicker({
								language: 'pl'
								, format: 'yyyy-MM-dd hh:mm'
								, weekStart: 1
							});

						},
						error: function (err) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request error: {0}'.f(err));
						}
					});

					//return false;
				},
				HIST: function (args) {
					var recordID = args;
					if (typeof args == 'object') {
						recordID = args.shift();
						recordID = parseInt(recordID);
					}
					if (typeof recordID !== 'number' || recordID <= 0) {
						// TODO: msg
						return false;
					}
					var cont = jQuery('#<?php echo $this->_htmlID; ?>').parent();
					cont.hide();

					// remove previous task content
					var taskCnt = jQuery('#<?php echo $this->_htmlID; ?>_task');
					taskCnt.parent().remove();
					taskCnt.remove();

					var taskCont = jQuery('<div class="AjaxTableCont"></div>').insertBefore(cont);
					jQuery('<ul class="breadcrumb">' +
						'<li><a href="#" onclick="return treeAjaxBackToTree();"><?php echo $this->getLabelHtml(); ?></a> <span class="divider">/</span></li>' +
						'<li class="active">Historia rekordu</li>' +
					'</ul>').appendTo(taskCont);
					var taskCnt = jQuery('<div id="<?php echo $this->_htmlID . '_task'; ?>" class="AjaxTableTaskCnt AjaxTable-loading"></div>').appendTo(taskCont);
					jQuery('<span class="loading-info"> loading ...</span>').appendTo(taskCnt);

					jQuery.ajax({
						url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=HIST&ID=' + recordID,
						type: 'GET',
						dataType: 'text',
						data: '',
						async: true,
						success: function (data) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request finished L.<?php echo __LINE__; ?>');
							jQuery(data).appendTo(taskCnt);
						},
						error: function (err) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request error: {0}'.f(err));
						}
					});
					//return false;
				},
				FILES: function tableAjaxFiles(args) {
					var recordID = args;
					if (typeof args == 'object') {
						recordID = args.shift();
						recordID = parseInt(recordID);
					}
					if (typeof recordID !== 'number' || recordID <= 0) {
						// TODO: msg
						return false;
					}
					var cont = jQuery('#<?php echo $this->_htmlID; ?>').parent();
					cont.hide();

					// remove previous task content
					var taskCnt = jQuery('#<?php echo $this->_htmlID; ?>_task');
					taskCnt.parent().remove();
					taskCnt.remove();

					var taskCont = jQuery('<div class="AjaxTableCont"></div>').insertBefore(cont);
					jQuery('<ul class="breadcrumb">' +
						'<li><a href="#" onclick="return treeAjaxBackToTree();"><?php echo $this->getLabelHtml(); ?></a> <span class="divider">/</span></li>' +
						'<li class="active">Pliki</li>' +
					'</ul>').appendTo(taskCont);
					var taskCnt = jQuery('<div id="<?php echo $this->_htmlID . '_task'; ?>" class="AjaxTable-loading"></div>').appendTo(taskCont);
					jQuery('<span class="loading-info"> loading ...</span>').appendTo(taskCnt);

					jQuery.ajax({
						url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=FILES&ID=' + recordID,
						type: 'GET',
						dataType: 'text',
						data: '',
						async: true,
						success: function (data) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request finished L.<?php echo __LINE__; ?>', data);
							jQuery(data).appendTo(taskCnt);
						},
						error: function (jqXHR, textStatus, errorThrown) {
							//console.log('request error:', jqXHR.status, 'txt:', jqXHR.responseText, 'err:', jqXHR);
							taskCnt.removeClass('AjaxTable-loading');
							var txt = jqXHR.responseText || 'Error';
							if (jqXHR.status == 404) {
								jQuery('<div class="container"><div class="alert alert-error">' + txt + '</div></div>').appendTo(taskCnt);
							} else {
								jQuery('<div class="container"><div class="alert alert-error">' + txt + '</div></div>').appendTo(taskCnt);
							}
						}
					});
					//return false;
				},
				CREATE: function tableAjaxCreate() {
					var cont = jQuery('#<?php echo $this->_htmlID; ?>').parent();
					cont.hide();

					// remove previous task content
					var taskCnt = jQuery('#<?php echo $this->_htmlID; ?>_task');
					taskCnt.parent().remove();
					taskCnt.remove();

					var taskCont = jQuery('<div class="AjaxTableCont"></div>').insertBefore(cont);
					jQuery('<ul class="breadcrumb">' +
						'<li><a href="#" onclick="return treeAjaxBackToTree();"><?php echo $this->getLabelHtml(); ?></a> <span class="divider">/</span></li>' +
						'<li class="active">Dodaj nowy rekord</li>' +
					'</ul>').appendTo(taskCont);
					taskCnt = jQuery('<div id="<?php echo $this->_htmlID . '_task'; ?>" class="AjaxTableTaskCnt AjaxTable-loading"></div>').appendTo(taskCont);
					jQuery('<span class="loading-info"> loading ...</span>').appendTo(taskCnt);

					var reqData = {};
					var forceFilterInit = <?php echo json_encode($forceFilterInit);// TODO: read from TableAjax ?>;
					if (forceFilterInit) {
						$.map(forceFilterInit, function (fltrProps, fltr) {
							reqData['ff_' + fltr] = fltrProps;
						});
					}

					jQuery.ajax({
						url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=CREATE',
						type: 'GET',
						dataType: 'text',
						data: reqData,
						async: true,
						success: function (data) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request finished L.<?php echo __LINE__; ?>');
							jQuery(data).appendTo(taskCnt);

							taskCnt.find('.se_type-date').datepicker({
								format: "yyyy-mm-dd"
								, language: 'pl'
								, todayBtn: "linked"
							});
							taskCnt.find('.se_type-datetime').parent().datetimepicker({
								language: 'pl'
								, format: 'yyyy-MM-dd hh:mm'
								, weekStart: 1
							});
						},
						error: function (err) {
							taskCnt.removeClass('AjaxTable-loading');
							//console.log('request error: {0}'.f(err));
						}
					});
					//return false;
				}
			};
			var routePath = location.hash;
			//console.log('location.hash: ' + routePath);
			if (location.hash == '' || location.hash == '#') {
				return false;
			}
			if (routePath.charAt(0) == '#') {
				routePath = routePath.substr(1);
			}
			//console.log('routePath: ' + routePath);
			var parts = routePath.split('/');
			var task = parts.shift();
			//console.log('task(' + task + ') parts:');
			//console.log(parts);
			if (task in routes && typeof routes[task] == 'function') {
				routes[task](parts);
			} else {
				return false;
			}
		},
		onNodeCollapse: function() {
			var node = this;
			//table.treetable("unloadBranch", node);// when unloadBranch childrens are only hidden and unnecessary ajax call on expand 
		},
		onNodeExpand: function() {
			var node = this;

			// prevent ajax call if childrens are only hidden
			if (node.children && node.children.length > 0) {
				return;
			}

			// Render loader/spinner while loading
			$.ajax({
				async: false, // Must be false, otherwise loadBranch happens after showChildren?
				url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=CHILDREN&ID=' + node.id,
			})
			.done(function(html) {
				if ($.trim(html) == '') {
					$(node.indenter).empty();// no children -> remove indenter
					return false;
				}

				var rows = $(html).filter("tr");

				rows.find(".folder").parents("tr").each(function() {
					initializeTableRow.apply(this);
				})

				table.treetable("loadBranch", node, rows);
			});
		}
	});

	jQuery.fx.interval = 100;// performance tip
	var tblBody = $("#<?php echo $this->_htmlID; ?>").find("tbody");
	// treetable("sortBranch", parentId, columnOrFunction)
	tblBody.sortable({
		//connectWith: "#<?php echo $this->_htmlID; ?> tbody",
		handle: ".sort-handler",
		items: "tr:not(.ui-state-disabled)",
		activate: function(e, ui) {// == start
			//tblBody.find('.ui-state-default').addClass('ui-state-disabled');
		},
		start: function(e, ui) {
			//console.log('start: ttId(',ui.item.data('ttId'),') ttParentId(',ui.item.data('ttParentId'),')  e:', e, 'ui:', ui);
			// ui.data = { ttId: "118" ttParentId: "499" }
			// TODO: set class ui-state-disabled to all tr which is not under ui.data('ttParentId')
		},
		stop: function(e, ui) {// sort finish
		},
		update: function(e, ui) {// when position change - dom update
			//console.log('update: ttId(',ui.item.data('ttId'),') ttParentId(',ui.item.data('ttParentId'),')  e:', e, 'ui:', ui);
			var prev = ui.item.next('tr'),
				next = ui.item.prev('tr'),
				allowMode = false,
				reqData = {};
			//console.log('ui.item next', prev.data('ttId'), 'pId:', prev.data('ttParentId'));
			//console.log('ui.item prev', next.data('ttId'), 'pId:', next.data('ttParentId'));

			if (prev && prev.data('ttParentId') == ui.item.data('ttParentId')) {
				allowMode = true;
				reqData = {after: prev.data('ttId')}
			}
			if (next && next.data('ttParentId') == ui.item.data('ttParentId')) {
				allowMode = true;
				reqData = {before: next.data('ttId')}
			}
			if (allowMode) {// && confirm("Przenieść rekord " + ui.item.data('ttId') + "?")) {
				$.ajax({
					data: reqData,
					type: "POST",
					url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=MOVE_SORT&ID=' + ui.item.data('ttId'),
				})
				.done(function(data, textStatus, jqXHR){
					//console.log('move-sort-info: ', data);
					jQuery.notify('Przeniesienie udane', 'success');
				})
				.fail(function(jqXHR){// jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});
					var txt = jqXHR.responseText || 'Error';
					if (jqXHR.status == 404) {
						jQuery.notify(jqXHR.responseText, 'error');
					} else {
						jQuery.notify(jqXHR.responseText, 'warn');
					}
				});
			} else {
				if (!allowMode) {
					var notAllowMsg = 'Błąd: nie można przenieść rekordu';
					jQuery.notify(notAllowMsg, 'error');
				}
				$(this).sortable("cancel");
			}
		}
//		, helper: function(e, ui) {// Return a helper with preserved width of cells
//			ui.children().each(function() {
//				$(this).width($(this).width());
//			});
//			return ui;
//		}
	}).disableSelection();

});
		</script>
		<?php
		$out = ob_get_contents();
		ob_end_clean();
		return $out;
	}

	private function sendAjaxChildren($id, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		header("Content-type: text/html");

		$out = '';
		$params = array();
		$params['order_by'] = 'SORT_PRIO';
		$nodes = $this->_dataSource->getTreeNodes($id, $params);
		if (!empty($nodes)) {
			ob_start();
			?>
				<?php foreach ($nodes as $node) : ?>
					<?php $this->renderNode($node); ?>
				<?php endforeach; ?>
			<?php
			$out = ob_get_contents();
			ob_end_clean();
		}
		echo $out;
		exit;
	}

	private function sendAjaxMove($id, $p_id, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		header("Content-type: text/html");

		$moved = $this->_dataSource->moveTreeNode($id, $p_id);
		if ($moved) {
			$node = $this->_dataSource->getTreeNode($id);
			$this->renderNode($node);
		} else {
			header("HTTP/1.0 404 Not Found");
			echo "Nie udało się przenieść {$id} pod {$p_id}";
		}
		exit;
	}

	private function sendAjaxNode($id, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		header("Content-type: text/html");

		$node = $this->_dataSource->getTreeNode($id);
		if ($node) {
			$this->renderNode($node);
		} else {
			header("HTTP/1.0 404 Not Found");
			echo "Rekord {$id} nie istnieje";
		}
		exit;
	}

	private function sendAjaxMoveSort($id, $beforeId, $afterId, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		header("Content-type: text/html");

		$moved = false;
		if ($beforeId > 0) {
			$moved = $this->_dataSource->moveTreeNodeSortBefore($id, $beforeId);
		} else if ($afterId > 0) {
			$moved = $this->_dataSource->moveTreeNodeSortAfter($id, $afterId);
		}
		if ($moved) {
			$node = $this->_dataSource->getTreeNode($id);
			$this->renderNode($node);
		} else {
			header("HTTP/1.0 404 Not Found");
			echo "Nie udało się przenieść {$id} pod {$p_id}";
		}
		exit;
	}

	/**
	 * ajax url: &_task=EDIT_INLINE
	 * @param $rowID - $_GET['ID']
	 * @param $fieldName - $_GET['col']
	 */
	private function sendAjaxEditInline($rowID, $fieldName, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		header("Content-type: text/plain");

		die('TODO: L.' . __LINE__);

		$fieldID = $this->_acl->getFieldIdByName($fieldName);
		if (!$fieldID) {
			echo "404: No field by name ({$fieldName})";
			exit;
		}

		if ($DBG) echo "fieldID: {$fieldID}\n";

		$row = $this->_dataSource->getItem($rowID);
		if (!$row) {
			echo "404: No item ID({$rowID})";
			exit;
		}

		if (!$this->_acl->isAllowed($fieldID, 'R', $row)) {
			if ($DBG) echo " R not allowed\n";
		} else {
			if ($DBG) echo " R allowed\n";
		}

		if (!$this->_acl->isAllowed($fieldID, 'W', $row)) {
			if ($DBG) echo " W not allowed\n";
		} else {
			if ($DBG) echo " W allowed\n";
		}

		$fieldVal = '';
		if ($this->_acl->isAllowed($fieldID, 'R', $row)) {
			$fieldVal = V::get($fieldName, $fieldVal, $row);
		}
		$fieldVal = V::get("f{$fieldID}", $fieldVal, $_POST);

		$vCol = $this->_acl->getField($fieldID);
		$vCol['label'] = (!empty($vCol['label']))? $vCol['label'] : $vCol['name'];

		$tsValues = array();
		Lib::loadClass('Typespecial');
		$typeSpecial = Typespecial::getInstance($fieldID, $vCol['name']);
		if ($typeSpecial) {
			if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Typespecial('.$fieldID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($typeSpecial);echo'</pre>';}
			$specialValues = $typeSpecial->getEditSelectedValuesByIds($this->_zasobID, $row->ID, $fieldName, V::get($fieldName, $fieldVal, $row));
			if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Typespecial('.$fieldID.') specialValues (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($specialValues);echo'</pre>';}
			if (!empty($specialValues)) {
				$tsValues[$row->ID] = implode('<br>', $specialValues);
			}
		}

		?>
		<label for="<?php echo "f{$fieldID}"; ?>" class="AjaxTableEdit-label">
			<strong title="<?php echo "[{$fieldID}] {$fieldName}"; ?>"><?php echo "{$vCol['label']}"; ?></strong>
			<?php if (!empty($vCol['opis'])) : ?>
				<em><?php echo $vCol['opis']; ?></em>
			<?php endif; ?>
		</label>
		<?php
			$fieldParams = array('widthClass'=>'inside-modal', 'maxGrid'=>6);
			if (!empty($tsValues[$row->ID])) {
				$fieldParams['typespecialValue'] = $tsValues[$row->ID];
			}
			$vDefault = $this->_dataSource->getColDefault($fieldName);
			if (!empty($vDefault)) {
				$fieldParams['default'] = $vDefault;
			}
			echo $this->_acl->showFormItem('W', $fieldID, "f{$fieldID}", $fieldVal, $fieldParams, $row);
			if ($typeSpecial) {
				echo '<p style="padding:100px 0;"></p>';
			}
		exit;
	}

	private function sendAjaxEditInlineSave($rowID, $fieldName, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		sleep(1);// TODO: RMME DBG loading

		die('TODO: L.' . __LINE__);

		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: save ID(' . $id . ') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($args);echo'</pre>';}
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">acl (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($this->_acl);echo'</pre>';}
		$dbID = $this->_acl->getDB();
		$db = DB::getDB($dbID);

		if (!$db) {
			header('HTTP/1.0 406 Not Acceptable');
			exit;
		}

		$tblName = $this->_acl->getName();

		$fieldID = $this->_acl->getFieldIdByName($fieldName);
		if (!$fieldID) {
			header('HTTP/1.0 404 Not Found');
			echo "404: No field by name ({$fieldName})";
			exit;
		}

		$row = $this->_dataSource->getItem($rowID);
		if (!$row) {
			header('HTTP/1.0 404 Not Found');
			echo "404: No item ID({$rowID})";
			exit;
		}

		if (!$this->_acl->isAllowed($fieldID, 'W', $row)) {
			header('HTTP/1.0 403 Forbidden');
			echo "403: field not allowed to Write ({$fieldName})";
			exit;
		} else {
			if ($DBG) echo " Write allowed\n";
		}

		$sqlObj = new stdClass();
		if (array_key_exists("f{$fieldID}", $args)) {
			$sqlObj->{$fieldName} = $args["f{$fieldID}"];

			if (empty($args["f{$fieldID}"]) && strlen($args["f{$fieldID}"]) == 0) {// fix bug in input type date and value="0000-00-00"
				$sqlObj->{$fieldName} = $this->_acl->fixEmptyValueFromUser($fieldID);
			}
		}
		else {
			if ($DBG) echo " TODO: field value not set\n";
		}
		$sqlObj->ID = $rowID;
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">E('.$tblName.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sqlObj);echo'</pre>';}

		$ret = $db->UPDATE_OBJ($tblName, $sqlObj);
		if ($ret > 0) {
			echo '<div class="alert alert-success">';
				echo "Rekord zapisany pomyślnie";//"Record saved successfully";
			echo '</div>';
		} else if ($ret == 0) {
			echo '<div class="alert alert-info">';
				echo "Nie wprowadzono żadnych zmian";
				if ($db->has_errors()) {
					//echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">db errors: (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($db->get_errors());echo'</pre>';
				}
			echo '</div>';
		} else {
			echo '<div class="alert alert-error">';
				echo '<h4>' . "Wystąpiły błędy!" . '</h4>';
				if ($db->has_errors()) {
					$errors = $db->get_errors();
					echo implode('<br>', $errors);
				}
			echo '</div>';
		}

		exit;
	}

	private function sendAjaxEdit($id, $args) {
		header("Content-type: text/plain");
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));

		$cols = array();

		$record = $this->_dataSource->getItem($id);
		if (!$this->_acl->canWriteRecord($record) && !$this->_acl->hasPermSuperWrite()) {
			echo '<div class="alert alert-error">';
				echo "Brak dostępu do rekordu";// TODO: more info - reason
			echo '</div>';
			return;
		}

		$fieldsList = $this->_acl->getFields();
		foreach ($fieldsList as $kID => $vCol) {
			if ($vCol['name'] == 'ID') {
				unset($fieldsList[$kID]);
				continue;
			}
			$cols[$kID] = '';
			if ($this->_acl->isAllowed($kID, 'R', $record)) {
				$cols[$kID] = V::get($vCol['name'], '', $record);
			}
			$cols[$kID] = V::get("f{$kID}", $cols[$kID], $_POST);

			$fieldsList[$kID]['label'] = (!empty($vCol['label']))? $vCol['label'] : str_replace('_', ' ', $vCol['name']);
		}

		$tsValues = array();
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fieldsList (F.' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($fieldsList);echo'</pre>';}
		if (!empty($fieldsList)) {
			Lib::loadClass('Typespecial');
			foreach ($fieldsList as $vColID => $vCol) {
				$typeSpecial = Typespecial::getInstance($vColID, $vCol['name']);
				if ($typeSpecial) {
					if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Typespecial('.$vColID.') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($typeSpecial);echo'</pre>';}
					$colValue = V::get($vCol['name'], '', $record);
					if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">V::get('.$vCol['name'].', "", $record) (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($colValue);echo'</pre>';}
					$specialValues = $typeSpecial->getEditSelectedValuesByIds($this->_zasobID, $record->ID, $vCol['name'], $colValue);
					if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">Typespecial('.$vColID.') specialValues (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($specialValues);echo'</pre>';}
					if (!empty($specialValues)) {
						$tsValues[$vColID] = implode('<br>', $specialValues);
					}
				}
			}
		}
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">tsValues (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($tsValues);echo'</pre>';}

		$rowFunctionsOut = '';// TODO: $this->_showRowFunctions($record->ID, array('edit', 'cp'), true);

		?>
		<div class="container AjaxFrmHorizontalEdit">
		<form class="form-horizontal" action="" method="post" id="EDIT_FRM_<?php echo $this->_htmlID; ?>">
			<fieldset>
				<legend>Edycja rekordu Nr <?php echo $record->ID; ?><span class="pull-right valign-btns-bottom"><?php echo $rowFunctionsOut; ?></span></legend>

				<?php $tabindex = 0; foreach ($fieldsList as $kID => $vCol) : ?>
					<?php if ($this->_acl->isAllowed($kID, 'W', $record)) : ?>
						<div class="control-group">
							<label class="control-label" for="<?php echo "f{$kID}"; ?>"><?php echo $vCol['label']; ?>
								<i class="icon-info-sign frm-help" data-toggle="popover" data-trigger="hover" title="" data-content="<?php echo htmlspecialchars($vCol['opis']); ?>" data-original-title="<?php echo "[{$kID}] {$vCol['name']}"; ?>"></i>
							</label>
							<div class="controls">
								<?php
									$fieldParams = array('appendBack'=>true, 'tabindex'=>(++$tabindex), 'maxGrid'=>8);
									if (!empty($tsValues[$kID])) {
										$fieldParams['typespecialValue'] = $tsValues[$kID];
									}
									echo $this->_acl->showFormItem('W', $kID, "f{$kID}", $cols[$kID], $fieldParams, $record);
								?>
							</div>
						</div>
					<?php elseif ($this->_acl->isAllowed($kID, 'R', $record)) : ?>
						<div class="control-group">
							<label class="control-label" for="<?php echo "f{$kID}"; ?>"><?php echo $vCol['label']; ?>
								<i class="icon-info-sign frm-help" data-toggle="popover" data-trigger="hover" title="" data-content="<?php echo htmlspecialchars($vCol['opis']); ?>" data-original-title="<?php echo "[{$kID}] {$vCol['name']}"; ?>"></i>
							</label>
							<div class="controls">
								<p style="margin-top:5px;">
								<?php
									//echo $this->_acl->showFormItem('R', $kID, "f{$kID}", $cols[$kID], array('appendBack'=>true), $record);
									if (!empty($tsValues[$kID])) {
										echo $tsValues[$kID];
									} else if (!empty($record->{$vCol['name']})) {
										echo $record->{$vCol['name']};
									}
								?>
								</p>
							</div>
						</div>
					<?php endif; ?>
				<?php endforeach; ?>

				<div class="control-group">
					<div class="controls">
						<button type="submit" class="btn btn-primary" tabindex="<?php echo (++$tabindex); ?>">Zapisz</button>
					</div>
				</div>

			</fieldset>
		</form>
		<p style="padding:100px 0;"></p>
		</div>
<script>
jQuery(document).ready(function(){
	jQuery('textarea').autosize();

	jQuery('.frm-help').popover({trigger:'hover'});

	jQuery('#EDIT_FRM_<?php echo $this->_htmlID; ?>').on('submit', function(){
		var data = jQuery(this).serialize();

		// TODO: change Edit btn to return to edit record with fields -> show form

		var taskCont = jQuery('#<?php echo $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-error"><div style="padding:0 0 0 20px; background:url(./icon/loading.gif) no-repeat left top;"> zapisywanie ... </div></div>').appendTo(alertCnt);

		jQuery.ajax({
			url: 'index-ajax.php?_zasobID=<?php echo $this->_zasobID; ?>&_cls=<?php echo __CLASS__; ?>&_hash=<?php echo $this->_htmlID; ?>&_task=EDIT_SAVE&ID=<?php echo $record->ID; ?>',
			type: 'POST',
			dataType: 'text',
			data: data,
			async: true,
			success: function (data) {
				alertCntWrap.removeClass('AjaxTable-loading');
				//console.log('request finished L.<?php echo __LINE__; ?>');
				alertCnt.empty();
				var out = '';
				out += data;
				out += '<div class="breadcrumb">' +
						' <a href="#" onclick="return treeAjaxBackToTree();" class="btn btn-link btn-small"> <i class="icon-arrow-left"></i> Wróć do tabeli <?php echo $this->getLabelHtml(); ?></a>' +
						'<span class="divider">/</span>' +
						' <a href="#EDIT/<?php echo $id; ?>/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-small"> <i class="icon-pencil"></i> Edytuj rekord <?php echo $id; ?></a>' +
					'</div>';
				jQuery(out).appendTo(alertCnt);

				// update node data in tree
				$("#<?php echo $this->_htmlID; ?>").treetable("updateNode", <?php echo $id; ?>);

			},
			error: function (jhr, textStatus, errorThrown) {
				var errorTxt = jhr.responseText || 'Error';
				alertCntWrap.removeClass('AjaxTable-loading');
				//console.log('Request Error: {0}: {1}'.f(textStatus, errorThrown));
				alertCnt.empty();
				jQuery(errorTxt).appendTo(alertCnt);
				var errLinks = jQuery('<div class="breadcrumb"></div>').appendTo(alertCnt);
				jQuery('<a href="#" onclick="return treeAjaxBackToTree();"> <i class="icon-arrow-left"></i> Wróć do tabeli <?php echo $this->getLabelHtml(); ?></a>').appendTo(errLinks);
				var backToEditBtn = jQuery('<a href="#EDIT/<?php echo $id; ?>/' + Math.random(1).toString().substr(2) + '" class="btn btn-link btn-small"> <i class="icon-pencil"></i> Popraw dane <?php echo $id; ?></a>').appendTo(errLinks);
				backToEditBtn.on('click', function(){
					alertCnt.remove();
					taskCont.children().fadeIn('slow');
					return false;
				});
			}
		});

		return false;
	});

	jQuery('#EDIT_FRM_<?php echo $this->_htmlID; ?>').find('.show-last-value input').on('input', function(e) {
		var input, btn;
		input = jQuery(e.target);
		btn = input.next();
		if (btn.is('button')) {
			if (btn.attr('title') != input.val()) {
				btn.show();
			} else {
				btn.hide();
			}
		}
	});
	jQuery('#EDIT_FRM_<?php echo $this->_htmlID; ?>').find('.show-last-value button.btn-appendBack').on('click', function(e) {
		var input, btn;
		btn = jQuery(e.target);
		input = btn.prev();
		if (input.is('input')) {
			if (btn.attr('title') != input.val()) {
				input.val(btn.attr('title'));
				btn.hide();
			}
		}
	});
});
</script>
		<?php
		exit;
	}

	private function sendAjaxEditSave($id, $args) {
		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
		sleep(1);// TODO: RMME DBG loading

		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">TODO: save ID(' . $id . ') (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($args);echo'</pre>';}
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">acl (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($this->_acl);echo'</pre>';}
		$dbID = $this->_acl->getDB();
		$db = DB::getDB($dbID);

		if (!$db) {
			header('HTTP/1.0 406 Not Acceptable');
			exit;
		}

		$tblName = $this->_acl->getName();

		$record = $db->get_by_id($tblName, $id);
		if (!$this->_acl->canWriteRecord($record) && !$this->_acl->hasPermSuperWrite()) {
			echo '<div class="alert alert-error">';
				echo "Brak dostępu do rekordu";// TODO: more info - reason
			echo '</div>';
		}

		$sqlObj = new stdClass();
		$fields = $this->_acl->getFields();
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">fields (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($fields);echo'</pre>';}
		foreach ($fields as $kID => $vField) {
			if (!$this->_acl->isAllowed($kID, 'W', $record)) {
				continue;
			}
			if (array_key_exists("f{$kID}", $args)) {
				$sqlObj->{$vField['name']} = $args["f{$kID}"];

				if (empty($args["f{$kID}"]) && strlen($args["f{$kID}"]) == 0) {// fix bug in input type date and value="0000-00-00"
					$sqlObj->{$vField['name']} = $this->_acl->fixEmptyValueFromUser($kID);
				}
			}
		}
		$sqlObj->ID = $id;
		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;"> (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($sqlObj);echo'</pre>';}

		$ret = $db->UPDATE_OBJ($tblName, $sqlObj);

		if ($ret > 0) {
			echo '<div class="alert alert-success">';
				echo "Rekord zapisany pomyślnie";//"Record saved successfully";
			echo '</div>';
		} else if ($ret == 0) {
			echo '<div class="alert alert-info">';
				echo "Nie wprowadzono żadnych zmian";
			echo '</div>';
		} else {
			header('HTTP/1.0 404 Not Found');
			echo '<div class="alert alert-error">';
				echo '<h4>' . "Wystąpiły błędy!" . '</h4>';
				if ($db->has_errors()) {
					$errors = $db->get_errors();
					echo implode('<br>', $errors);
				}
			echo '</div>';
		}

		exit;
	}

	public function ajaxTask($task) {
		switch ($task) {
			case 'CHILDREN': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				if ($id > 0) {
					$this->sendAjaxChildren($id, $_REQUEST);
				} else {
					echo '404';
				}
				break;
			}
			case 'ROOT_NODES': {
				$this->sendAjaxChildren(0, $_REQUEST);
				break;
			}
			case 'ADD': {
				echo 'TODO: L.' . __LINE__;
				break;
			}
			case 'MOVE': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				$p_id = V::get('MOVE_TO_ID', 0, $_REQUEST, 'int');
				if ($id > 0 && $p_id > 0) {
					$this->sendAjaxMove($id, $p_id, $_REQUEST);
				} else {
					header("HTTP/1.0 404 Not Found");
					echo 'Błędne parametry';
					exit;
				}
				break;
			}
			case 'MOVE_SORT': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				$beforeId = V::get('before', 0, $_REQUEST, 'int');
				$afterId = V::get('after', 0, $_REQUEST, 'int');
				if ($id > 0 && ($beforeId > 0 || $afterId > 0)) {
					$this->sendAjaxMoveSort($id, $beforeId, $afterId, $_REQUEST);
				} else {
					header("HTTP/1.0 404 Not Found");
					echo 'Błędne parametry';
					exit;
				}
				break;
			}
			case 'NODE': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				if ($id > 0) {
					$this->sendAjaxNode($id, $_REQUEST);
				} else {
					echo '404';
				}
				break;
			}
			case 'EDIT': { // &_task=EDIT&ID=510
				$id = V::get('ID', 0, $_REQUEST, 'int');
				if ($id > 0) {
					$this->sendAjaxEdit($id, $_REQUEST);
				} else {
					echo '404';
				}
				break;
			}
			case 'EDIT_SAVE': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				if ($id > 0) {
					$this->sendAjaxEditSave($id, $_REQUEST);
				} else {
					echo '404';
				}
				break;
			}
			case 'EDIT_INLINE': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				$col = V::get('col', '', $_REQUEST);
				if ($id > 0 && !empty($col)) {
					$this->sendAjaxEditInline($id, $col, $_REQUEST);
				} else {
					echo '404';
				}
				break;
			}
			case 'EDIT_INLINE_SAVE': {
				$id = V::get('ID', 0, $_REQUEST, 'int');
				$col = V::get('col', '', $_REQUEST);
				if ($id > 0 && !empty($col)) {
					$this->sendAjaxEditInlineSave($id, $col, $_REQUEST);
				} else {
					echo '404';
				}
				break;
			}
			default:
				//$this->sendAjaxData($_REQUEST);
		}
	}

}
