API Docs for: 0.0.2
Show:

File: api/src/tree/old-tree.js

/**
 * Displays a tree
 * This class will soon be replaced by a new Tree widget.
 * @namespace tree
 * @class Tree
 * @extends View
 * @deprecated
 */
ludo.tree.Tree = new Class({
    Extends:ludo.View,
    type:'tree.Tree',
    /**
     * Keys to use as primary key for records. If you show different types of nodes, example continent, country, city
     * you may have records with the same "id". primaryKey let you specify keys to use as a combined primary key
     * example: ["id","type"]. If no keys are set, "id" will be used.
     * @attribute {Array} primaryKey
     * @default []
     */
    primaryKey:[],

    treeConfig:{
        defaultValues:{}
    },

    rootRecord:{
        id:'ludo-tree-root-node-' + String.uniqueID(),
        title:'Root',
        type:'root',
        hidden:true
    },

    recordConfig:{
        'root':{
            dd:{
                drag:false,
                drop:true,
                siblings:false,
                children:true,
                newChild:'top'
            }
        }
    },
    selectedRecord:undefined,

    modificationStorage:[],

    dd:{},
    searchConfig:null,
    filterObj:null,
    /**
     * Initial records, example
     * [ {id:1, title: 'Document', type: 'document' , children: [{ id:2, title: 'Chapter 1', type: 'chapter'},{ id:3, title: 'Chapter 2', type: 'chapter'}] }
     * In most cases, you will want to pull this from the server. Response from server should be { success: true, data : [ { tree nodes structure above } ]}
     * @attribute {Array} data
     */
    data:[],

    recordMap:{},

    nodeTpl:'<span>{title}</span>',


    search:false,


    tplFields:{},
    /**
     * Show lines between nodes
     * @attribute {Boolean} showLines
     * @default true
     *
     */
    showLines:true,


    /**
     * Initially expand tree to this depth
     * @attribute {Number} expandDepth
     */
    expandDepth:0,


    /**
     * Auto scroll node into view when calling the selectRecord method
     * @attribute {Boolean} autoScrollNode
     * @default true
     */
    autoScrollNode:false,
    /**
     * Auto expand tree to selected node when calling selectRecord method
     * @attribute {Boolean} autoExpand
     * @default true
     */
    autoExpand:true,
    selectedNode:null,
    hiddenNodes:{},
    /**
     * Object of class ludo.tree.Modifications. Store modified, updated and removed records
     * @property object modificationManager
     */
    modificationManager:undefined,

    ludoConfig:function (config) {
        this.parent(config);
        this.setConfigParams(config, ['data','nodeTpl','recordConfig','showLines','autoScrollNode','expandDepth',
            'search','dd','primaryKey']);

        config.treeConfig = config.treeConfig || {};

        if (config.rootRecord !== undefined) {
            this.rootRecord = config.rootRecord;
            this.rootRecord.id = this.rootRecord.id || 'ludo-tree-root-node-' + String.uniqueID();
            this.rootRecord.type = this.rootRecord.type || 'root';
        }
        if (config.treeConfig.defaultValues !== undefined)this.treeConfig.defaultValues = config.treeConfig.defaultValues;

        this.dd = config.dd || this.dd;

        if (this.search) {
            this.searchConfig = config.searchConfig;
            this.filterObj = new ludo.tree.Filter({
                data:this.data,
                component:this
            });
        }
    },

    ludoRendered:function () {
        if (this.data.length) {
            this.insertJSON(this.data);
        }
        if (!this.showLines) {
            this.getEl().addClass('ludo-tree-no-lines');
        }
    },

    ludoDOM:function () {
        this.parent();
        this.els.nodes = [];
        this.els.expand = [];
        this.els.childContainers = [];
    },

    ludoEvents:function () {
        this.parent();

        if ((this.dd.drag || this.dd.drop) && !this.dd.obj) {
            this.dd.obj = new ludo.tree.DragDrop();
        }

        this.addEvent('remove', this.removeRecord.bind(this));
        this.addEvent('add', this.addRecord.bind(this));

        var b = this.getBody();
        b.addEvent('click', this.recordClick.bind(this));
        b.addEvent('dblclick', this.recordDblClick.bind(this));
        b.addEvent('click', this.expandByDom.bind(this));
        b.addEvent('click', this.toggleExpandCollapse.bind(this));
        if (Browser['ie']) {
            b.addEvent('selectstart', this.cancelSelection.bind(this));
        }
    },

    load:function () {
        this.clearView();
        this.parent();
    },

    clearView:function () {
        this.els.nodes = [];
        this.getBody().set('html', '');
        this.getModificationManager().clearDirtyStorage();
    },

    filter:function (searchString) {
        this.filterObj.filter(searchString);
    },

    removeRecord:function (record) {
        if (this.dd['removeDragged'] === false) {
            return;
        }
        this.removeDomForRecord(record);
        var parentRecord = this.getParentRecord(record);
        this.getModificationManager().storeRemovedRecord(record, parentRecord);
        parentRecord.children.erase(record);
        this.showHideExpandElement(parentRecord);
        this.updateSearchIndex();

        this.setNewCssForDomNodes(record);
    },

    removeDomForRecord:function (record) {
        this.setChildDomNodesToUndefined(record);
        var id = this.getUniqueRecordId(record);
        var node = this.getDomNode(record);
        if (node) {
            this.getDomNode(record).dispose();
            this.els.childContainers[id].dispose();
            this.els.nodes[id] = undefined;
            this.els.childContainers[id] = undefined;
        }
    },

    setChildDomNodesToUndefined:function (record) {
        if (record.children && record.children.length) {
            for (var i = 0; i < record.children.length; i++) {
                this.els.nodes[this.getUniqueRecordId(record.children[i])] = undefined;
                this.setChildDomNodesToUndefined(record.children[i]);
            }
        }
    },
    /**
     * Method called by ludo.tree.
	 * @method addRecord
     * @param {Object} obj
     */
    addRecord:function (obj) {
        obj.record.unique = undefined;
        if (obj.pos == 'sibling') {
            this.addSibling(obj.record, obj.targetRecord);
        } else {
            this.addChild(obj.record, obj.targetRecord);
        }
    },

    updateSearchIndex:function () {
        if (this.search) {
            this.filterObj.setData(this.data.children ? this.data.children : this.data);
        }
    },

    isRecordChildOf:function (record, parentRecord) {
        var primaryKey = this.getPrimaryKey(record);
        for (var i = 0; i < parentRecord.children.length; i++) {
            var child = parentRecord.children[i];
            if (this.getPrimaryKey(child) == primaryKey) {
                return true;
            }
        }
        return false;
    },

    addSibling:function (record, sibling) {
        var id = this.getUniqueRecordId(sibling);
        var parentRecord = this.recordMap[id].parent;

        if (this.isRecordChildOf(record, parentRecord)) {
            return;
        }

        var index = parentRecord.children.indexOf(sibling) + 1;

        var count = parentRecord.children.length;
        for (var i = count; i > index; i--) {
            parentRecord.children[i] = parentRecord.children[i - 1];
        }
        parentRecord.children[index] = record;

        var node = this.createNode(record, parentRecord);
        node.setStyle('display', '');

        node.inject(this.els.nodes[id], 'after');

        this.getModificationManager().storeAddedRecord(record, sibling, 'sibling');

        this.setNewCssForDomNodes(record);
        this.updateSearchIndex();
    },

    addChild:function (record, parentRecord) {
        if (this.isRecordChildOf(record, parentRecord)) {
            return;
        }
        this.renderChildNodes(parentRecord);
        parentRecord.children = parentRecord.children || [];

        if (!this.shouldDropChildOnTop(parentRecord)) {
            this.addSibling(record, parentRecord.children[parentRecord.children.length - 1]);
            return;
        }

        record.children = record.children || [];
        var count = parentRecord.children.length;
        for (var i = count; i > 0; i--) {
            parentRecord.children[i] = parentRecord.children[i - 1];
        }

        var sibling;
        if (parentRecord.children.length > 0) {
            sibling = parentRecord.children[0];
        }

        parentRecord.children[0] = record;

        var node = this.createNode(record, parentRecord);
        node.setStyle('display', '');

        if (sibling) {
            var id = this.getUniqueRecordId(sibling);
            node.inject(this.els.nodes[id], 'before');
        } else {
            this.renderChildNodes(parentRecord);
        }

        this.setNewCssForDomNodes(record);
        this.showHideExpandElement(parentRecord);
        this.getModificationManager().storeAddedRecord(record, parentRecord, 'child');
        this.expandNode(parentRecord);
        this.updateSearchIndex();
    },

    getModifiedRecords:function () {
        return this.getModificationManager().getModifications();
    },

    getModificationManager:function () {
        if (!this.modificationManager) {
            this.modificationManager = new ludo.tree.Modifications({ tree:this });
        }
        return this.modificationManager;
    },

    insertJSON:function (remoteData) {
        var data = remoteData;
        this.clearView();
        if (data.length > 0 && data[0]['rootNode']) {
            this.rootRecord = data[0];
            data = data[0].children;
        }
        this.data = this.rootRecord;
        this.data.children = data;
        this.updateSearchIndex();
        this.render();

        if (this.expandDepth) {
            this.expandSome(null, this.expandDepth);
        }
    },

    render:function () {
        this.createNode(this.rootRecord);
        if (this.rootRecord.hidden || !this.rootRecord.title) {
            var id = this.getUniqueRecordId(this.rootRecord);
            this.els.nodes[id].getElements('.ludo-tree-node-plain').setStyle('display', 'none');

        }
        this.renderChildNodes(this.data);
        this.fireEvent('render', this);
    },

    renderChildNodes:function (record) {
        var records = record.children;
        var lastChildIndex = records.length - 1;
        for (var i = 0; i < records.length; i++) {
            this.createNode(records[i], record, (i == lastChildIndex));
        }
    },

    createNode:function (record, parentRecord, isLastChild) {
        var id = this.getUniqueRecordId(record);
        this.recordMap[id] = {
            record:record,
            parent:parentRecord || undefined
        };

        if (!this.els.nodes[id]) {
            var el = this.els.nodes[id] = new Element('div');
            el.className = 'ludo-tree-node';

            var nodeText = [];
            nodeText.push('<div class="ludo-tree-node-plain" id="' + id + '">');
            nodeText.push('<span');
            if (this.isSelectable(record)) {
                nodeText.push(' class="ludo-tree-node-selectable"');
            }
            nodeText.push(' id="' + id + '"');
            nodeText.push('>');
            nodeText.push(this.getNodeText(record));
            nodeText.push('</span>');

            nodeText.push('<div style="position:absolute" class="ludo-tree-node-expand" id="' + id + '"></div>');

            el.innerHTML = nodeText.join('');
			if(!this.els.expand)this.els.expand = {};
			if(!this.els.childContainers)this.els.childContainers = {};
            this.els.expand[id] = el.getElement('.ludo-tree-node-expand');
            this.els.childContainers[id] = el.getElement('.ludo-tree-node-container');

            if (parentRecord) {
                this.els.childContainers[this.getUniqueRecordId(parentRecord)].appendChild(el);
            } else {
                this.getBody().appendChild(el);
                ludo.dom.addClass(el, 'ludo-tree-node-root');
            }

            if (isLastChild) {
                ludo.dom.addClass(el, 'ludo-tree-node-last-sibling');
            }

            if (record['leaf'] || !record.children || record.children.length == 0) {
                this.els.expand[id].style.display = 'none';
            }

            var container = this.els.childContainers[id] = new Element('div');
            container.className = 'ludo-tree-node-container';
            el.appendChild(container);

            if (this.isRecordDragable(record)) {
                this.dd.obj.addSource({ el:el.getElement('.ludo-tree-node-plain'), record:record, component:this });
            }

            if (this.isRecordDropTarget(record)) {
                this.dd.obj.addTarget({ el:el.getElement('span'), record:record, component:this, config:this.getDragDropConfigFor(record) });
            }
        }
        return this.els.nodes[id];
    },

    cancelSelection:function () {
        return false;
    },

    isSelectable:function (record) {
        var ret = this.getRecordConfig(record, 'selectable');
        if (ret === undefined) {
            ret = true;
        }
        return ret;
    },

    selectRecord:function (record) {
        record = this.findRecord(record);
        if (record) {
            this.selectedRecord = record;
            if (!this.isRendered(record)) {
                var path = this.getPathToRecord(record);
                for (var i = 0; i < path.length; i++) {
                    this.renderChildNodes(path[i]);
                    this.expandNode(path[i]);
                }
            }
            var domNode = this.getDomNode(record);
            this.setSelectedNode(domNode);
            if (this.autoScrollNode) {
                ludo.dom.scrollIntoView(domNode, this);
            }
            if (this.autoExpand) {
                this.expandToRecord(record);
            }
            this.fireEvent('selectrecord', record);
            return record;
        }
        return undefined;
    },
    getSelectedRecord:function () {
        return this.selectedRecord;
    },
    getPathToRecord:function (record) {
        var id = this.getUniqueRecordId(record);
        if (!this.recordMap[id]) {
            this.buildRecordMap();
        }
        var ret = [];
        while (record = this.getParentRecord(record)) {
            ret.push(record);
        }
        return ret.reverse();
    },

    buildRecordMap:function (parentRecord, children) {
        children = children || this.data.children;
        for (var i = 0; i < children.length; i++) {
            var record = children[i];
            var id = this.getUniqueRecordId(record);
            this.recordMap[id] = {
                record:record,
                parent:parentRecord || undefined
            };
            if (record.children && record.children.length) {
                this.buildRecordMap(record, record.children);
            }
        }
    },

    findRecord:function (search, children) {
        if (!children && this.isRootNode(search)) {
            return this.rootRecord;
        }
        var rec = null;
        children = children || this.data.children;
        if (children) {
            for (var i = 0; i < children.length; i++) {
                var record = children[i];
                if (this.recordMatchesSearch(record, search)) {
                    return record;
                }
                if (record.children && record.children.length > 0) {
                    rec = this.findRecord(search, record.children);
                    if (rec) {
                        return rec;
                    }
                }
            }
        }
        return rec;
    },

    recordMatchesSearch:function (record, search) {
        if (record.unique && search.unique) {
            return record.unique == search.unique;
        }
        for (var type in search) {
            if (search.hasOwnProperty(type)) {
                if (!record[type] || record[type] != search[type]) {
                    return false;
                }
            }
        }
        return true;
    },
    /**
     * Update CSS of branch after moving or removing a record from the tree(Drag and Drop)
     * @method setNewCssForDomNodes
     * @param {Object} record
     * @private
     */
    setNewCssForDomNodes:function (record) {
        var parentRecord = this.getParentRecord(record);
        var domNode;
        if (parentRecord && parentRecord.children && parentRecord.children.length > 0) {
            for (var i = 0; i < parentRecord.children.length - 1; i++) {
                domNode = this.getDomNode(parentRecord.children[i]);
                if (domNode) {
                    domNode.removeClass('ludo-tree-node-last-sibling')
                }
            }
            domNode = this.getDomNode(this.getLastChildRecord(parentRecord));
            if (domNode) {
                ludo.dom.addClass(domNode, 'ludo-tree-node-last-sibling')
            }
        }
    },

    getDomNode:function (record) {
        var domId = this.getUniqueRecordId(record);
        return this.els.nodes[domId] ? this.els.nodes[domId] : undefined;
    },

    getLastChildRecord:function (record) {
        if (record.children && record.children.length) {
            return record.children[record.children.length - 1];
        }
        return undefined;
    },

    setRecordProperty:function (record, property, newValue) {
        record = this.findRecord(record);
        if (record) {
            record[property] = newValue;
        }
        this.updateDOMOfRecord(record);
        this.getModificationManager().storeUpdatedRecord(record);
    },

    updateDOMOfRecord:function (record) {
        var id = this.getUniqueRecordId(record);
        if (this.els.nodes[id]) {
            var textEl = this.els.nodes[id].getElements('span')[0];
            textEl.set('html', this.getNodeText(record));
        }
    },

    recordDblClick:function (e) {
        var el = this.getSelectableDomNode(e.target);
        if (!el)return;
        this.fireEvent('dblclick', [this.recordMap[el.getProperty('id')].record, e]);
    },
    recordClick:function (e) {
        var el = this.getSelectableDomNode(e.target);
        if (!el)return undefined;
        this.setSelectedNode(el);
        this.selectedRecord = this.recordMap[el.getProperty('id')].record;
        this.fireEvent('click', [this.recordMap[el.getProperty('id')].record, e]);
        this.fireEvent('selectrecord', [this.recordMap[el.getProperty('id')].record, e]);

        return e.target && e.target.tagName.toLowerCase() === 'input' ? undefined : false;
    },

    getSelectableDomNode:function (el) {
        if (el.hasClass('ludo-tree-node-expand')) {
            return null;
        }
        if (!el.hasClass('ludo-tree-node-plain')) {
            el = el.getParent('.ludo-tree-node-plain');
        }
        if (!el || !el.getElement('.ludo-tree-node-selectable'))return null;
        return el;
    },

    setSelectedNode:function (node) {
        if (!node.hasClass('ludo-tree-node-plain')) {
            node = node.getFirst('.ludo-tree-node-plain');
        }
        if (this.selectedNode && this.selectedNode.tagName) {
            this.selectedNode.removeClass('ludo-tree-selected-node');
        }
        ludo.dom.addClass(node, 'ludo-tree-selected-node');
        this.selectedNode = node;
    },

    getNodeText:function (record) {
        var tplFields = this.getTplFields(record);
        var ret = this.getNodeTemplate(record);
        for (var i = 0, count = tplFields.length; i < count; i++) {
            var field = tplFields[i];
            ret = ret.replace('{' + field + '}', record[field] ? record[field] : this.getDefaultValue(record, field));
        }
        if (!this.isRootNode(record)) {
            ret = '<span class="ludo-tree-node-spacer"></span>' + ret;
        }
        return ret;
    },
	/**
	Return record fields/columns for the node template, example ['title','description'] from {title} {description}
	@method getTplFields
	@param {Object} record
	@return array
	@private
	*/
    getTplFields:function (record) {
        if (!this.tplFields[record.type]) {
            var tpl = this.getNodeTemplate(record);
            var matches = tpl.match(/{([^}]+)}/g);
            for (var i = 0; i < matches.length; i++) {
                matches[i] = matches[i].replace(/[{}]/g, '');
            }
            this.tplFields[record.type] = matches;
        }
        return this.tplFields[record.type];
    },

    getNodeTemplate:function (record) {
        if (this.recordConfig[record.type] && this.recordConfig[record.type].nodeTpl) {
            return this.recordConfig[record.type].nodeTpl;
        }
        return this.nodeTpl;
    },

    getDefaultValue:function (record, field) {
        var values = this.getRecordConfig(record, 'defaults');
        if (!values || !values[field]) {
            return this.treeConfig.defaultValues[field] ? this.treeConfig.defaultValues[field] : '';
        }
        return values[field];
    },

    isRecordDragable:function (record) {
        return this.isDragDropFeatureEnabled(record, 'drag');
    },

    isRecordDropTarget:function (record) {
        return this.isDragDropFeatureEnabled(record, 'drop');
    },

    shouldDropChildOnTop:function (record) {
        var dd = this.getDragDropConfigFor(record);
        if (!dd || !dd['newChild'])return true;
        return dd['newChild'] == 'top';
    },

    isDragDropFeatureEnabled:function (record, feature) {
        if (!this.dd[feature]) {
            return false;
        }
        if (!record.type) {
            return true;
        }
        var dd = this.getDragDropConfigFor(record);
        if (!dd)return false;
        if (dd[feature] !== undefined) {
            return dd[feature];
        }
        return true;
    },

    getDragDropConfigFor:function (record) {
        return this.getRecordConfig(record, 'dd');
    },

    getRemoteConfigFor:function (record) {
        var ret = this.getRecordConfig(record, 'remote');
        if (ret && !ret.url) {
            ret.url = this.getUrl();
        }
        return ret;
    },

    getRecordConfig:function (record, key) {
        if (!record.type || !this.recordConfig[record.type] || this.recordConfig[record.type][key] === undefined) {
            return undefined;
        }
        return this.recordConfig[record.type][key];
    },

    getNewExpandDOMElement:function (record, id) {
        id = id || this.getUniqueRecordId(record);
        var el = this.els.expand[id] = new Element('div');
        el.style.position = 'absolute';
        el.className = 'ludo-tree-node-expand';
        el.setProperty('record', id);
        return el;
    },

    toggleExpandCollapse:function (e) {
        if (!e.target.hasClass('ludo-tree-node-expand')) {
            return;
        }
        var dom = this.getExpandElAndChildByDomNode(e.target);
        var el = dom.expand, child = dom.child;
        if (el.hasClass('ludo-tree-node-collapse')) {
            el.removeClass('ludo-tree-node-collapse');
            child.setStyle('display', 'none');
        } else {
            var record = this.recordMap[el.getProperty('id')].record;
            this.expandNode(record);
        }
    },

    expandByDom:function (e) {
        if (e.target.tagName.toLowerCase() !== 'span')return;
        var dom = this.getExpandElAndChildByDomNode(e.target);
        if (!dom.expand.hasClass('ludo-tree-node-collapse')) {
            var record = this.recordMap[dom.expand.getProperty('id')].record;
            this.expandNode(record);
        }
    },

    getExpandElAndChildByDomNode:function (domNode) {
        if (!domNode.hasClass('ludo-tree-node-expand')) {
            domNode = domNode.getParent('.ludo-tree-node').getElement('.ludo-tree-node-expand');
        }
        var child = domNode.getParent('.ludo-tree-node').getFirst('.ludo-tree-node-container');
        return {
            expand:domNode,
            child:child
        };
    },

    expandButNotLoad:function (record) {
        this.expandNode(record, true);
    },

    expandToRecord:function (record) {
        var parent = this.getParentRecord(record);
        while (parent) {
            this.expandNode(parent, true);
            parent = this.getParentRecord(parent);
        }
    },
    /**
     * @method expandAll
     * @description expand entire tree or branch from parentRecord
     * @param {Object} parentRecord (optional)
     */
    expandAll:function (parentRecord) {
        this.expandSome(parentRecord, 100);
    },

    /**
     * Expand a record
     * @method expandSome
     * @param {Object} parentRecord (optional - if not set, tree will be expanded from root)
     * @param {Number} depth How deep to expand, 1 will only expand direct children
     */
    expandSome:function (parentRecord, depth, currentDepth) {
        parentRecord = parentRecord || this.data;
        if (!parentRecord || !parentRecord.children) {
            return;
        }

        currentDepth = currentDepth || 0;
        currentDepth++;

        for (var i = 0; i < parentRecord.children.length; i++) {
            var record = parentRecord.children[i];
            this.expandNode(record);
            if (record.children && record.children.length > 0 && currentDepth < depth) {
                this.expandSome(record, depth, currentDepth + 1);
            }
        }
    },

    expandNode:function (record, skipRemote) {
        var id = this.getUniqueRecordId(record);
        if(!this.els.expand[id])return;
        this.els.expand[id].addClass('ludo-tree-node-collapse');
        this.els.childContainers[id].style.display = '';

        if (!skipRemote && this.shouldLoadChildrenRemotely(record)) {
            this.loadChildNodes(record);
        } else if (record.children && record.children.length && !this.isRendered(record.children[0])) {
            this.renderChildNodes(record);
        }
    },

    loadChildNodes:function (record) {
        var remoteConfig = this.getRemoteConfigFor(record);

		var req = ludo.remote.JSON({
			url: remoteConfig.url,
			data : Object.merge(remoteConfig.data, { record:record }),
			listeners:{
				"success": function(request){
					record.children = request.getResponseData();
					this.renderChildNodes(record);
					this.remoteLoadedNodes[this.getUniqueRecordId(record)] = true;
					this.showHideExpandElement(record);
				}.bind(this)
			}
		});
        req.send();
    },

    isRendered:function (record) {
        var id = record.unique || this.getUniqueRecordId(record);
        return this.els.nodes[id] && this.els.nodes[id].tagName ? true : false;
    },

    showHideExpandElement:function (record, id) {
        id = id || this.getUniqueRecordId(record);
        if (this.isRootNode(record)) {
            this.els.expand[id].style.display = 'none';
            return;
        }
        if (this.shouldLoadChildrenRemotely(record)) {
            this.els.expand[id].style.display = '';
        }
        else if (record['leaf'] || !record.children || record.children.length == 0) {
            this.els.expand[id].style.display = 'none';
        } else {
            this.els.expand[id].style.display = '';
        }
    },

    isRootNode:function (record) {
        if (this.rootRecord) {
            return record.id == this.rootRecord.id;
        }
        return false;
    },

    remoteLoadedNodes:{},
    shouldLoadChildrenRemotely:function (record) {
        if (record['leaf']) {
            return false;
        }
        var id = this.getUniqueRecordId(record);

        if (this.remoteLoadedNodes[id] || (record.children && record.children.length > 0)) {
            return false;
        }

        return this.getRemoteConfigFor(record) ? true : false;
    },

    uniqueCache:{},
    uniqueCounter:0,
    getUniqueRecordId:function (record) {
        if (!record.unique) {
            this.uniqueCounter++;
            record.unique = ['U', this.id, '-', this.uniqueCounter].join('');
        }
        return record.unique;
    },

    getPrimaryKey:function (record) {
        if (!this.primaryKey.length) {
            return record.id;
        }
        var ret = '';
        for (var i = 0; i < this.primaryKey.length; i++) {
            var field = this.primaryKey[i];
            ret += record[field] ? record[field] : '';
        }
        return ret;
    },

    getParentRecord:function (record) {
        var id = this.getUniqueRecordId(record);
        if (!this.recordMap[id]) {
            this.buildRecordMap();
        }
        return this.recordMap[id].parent;
    },

    showNode:function (record) {
        var id = record.unique || this.getUniqueRecordId(record);
        if (!this.hiddenNodes[id]) {
            return;
        }
        var node = this.els.nodes[id];
        if (node) {
            node.style.display = '';
            this.hiddenNodes[id] = false;
        }
    },

    hideNode:function (record) {
        var id = record.unique || this.getUniqueRecordId(record);
        if (this.hiddenNodes[id])return;
        this.els.nodes[id].style.display = 'none';
        this.hiddenNodes[id] = true;
    },

    showChildren:function (record) {
        if (record.children && record.children.length) {
            for (var i = 0; i < record.children.length; i++) {
                this.showNode(record.children[i]);
            }
        }
    },

    showBranch:function (record) {
        if (record.children && record.children.length) {
            for (var i = 0; i < record.children.length; i++) {
                this.showNode(record.children[i]);
                this.showBranch(record.children[i]);
            }
        }
    }
});