Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
jsarnowski/oxygen / angular / controllers / controller.tree.js
Size: Mime:
/**
 * All tree manipulations here
 * 
 */

CTFrontendBuilder.controller("ComponentsTree", function($scope, $parentScope, $timeout, $interval) {

    /**
     * Components Tree object. Contain all data needed to generate DOM.
     * Passed to WordPress as JSON on save.
     * 
     */
    
    // Example sctructure
    $scope.componentsTreeExample = {

        'name' : 'root',
        'children': [ 
                {
                    'id' : 1,
                    'name' : 'section',
                    'options' : {
                            'ct_parent': 0,
                            'ct_id': 1,
                            'original' : {
                                'background' : '#515151',
                            },
                            'hover' : {
                                'background' : '#cccccc',
                            }
                        },
                    'children': [
                        {
                            'id' : 2,
                            'name' : 'button',
                            'options' : {
                                'ct_parent': 1,
                                'ct_id': 2,
                                'original' : {
                                        'value' : 'Click Me!',
                                    },
                            }
                        }
                    ],
                },
                {
                    'id' : 3,
                    'name' : 'section',
                    'options' : {
                        'ct_parent': 0,
                        'ct_id': 1,
                        'original' : {
                                'background' : '#515151',
                            },
                        'hover' : {
                                'background' : '#333333',
                            },
                        'media' : [
                            {   
                                'size' : "748px",
                                'original' : {
                                    'background' : '#929292',
                                },
                            },
                        ]
                        },
                    'children': [
                        { 
                            'id' : 4,
                            'name' : 'headline',
                            'options' : {
                                'ct_parent': 3,
                                'ct_id': 4,
                                'original' : {
                                    'tag' : 'h3',
                                    'text' : 'I am a headline!',
                                    
                                },
                            }
                        },
                    ]
                },
            ]
        }

    // Tree
    $scope.componentsTree = [
        {
            'id' : 0,
            'name' : 'root',
            'depth' : 0
        }
    ];


    $scope.findComponentByName = function(node, name, callback, variable) {

        var returnVal;

        isBreak = false;
        
        angular.forEach(node, function(item) {

            if ( !isBreak ) {
                if ( item.name == name ) {
                    // do something if find
                    returnVal = callback(item.id, item, variable);
                    // stop the loop
                    isBreak = true;
                } 
                else {
                    // go deeper in Components Tree
                    if ( item.children ) {
                        returnVal = $scope.findComponentByName(item.children, name, callback, variable);
                    }
                }
            }

        });

        if(typeof(returnVal) !== 'undefined')
            return returnVal;
    }

    /**
     * Recursively find component in Components Tree by ID
     * and pass it to callback function
     * 
     * @since 0.1
     */

    $scope.findComponentItem = function(node, id, callback, variable) {

        var returnVal;

        if ($scope.log) {
            //console.log("findComponentItem()", node, id);
        }

        isBreak = false;

        // if root
        if ( id == 0 ) {
            returnVal = callback(id, $scope.componentsTree, variable);
            if(typeof(returnVal) === 'undefined')
                return false;
            else
                return returnVal;
        }
        
        angular.forEach(node, function(item) {

            if ( !isBreak ) {
                if ( item.id == id ) {
                    // do something if find
                    returnVal = callback(id, item, variable);
                    // stop the loop
                    isBreak = true;
                } 
                else {
                    // go deeper in Components Tree
                    if ( item.children ) {
                        returnVal = $scope.findComponentItem(item.children, id, callback, variable);
                    }
                }
            }

        });

        if(typeof(returnVal) !== 'undefined')
            return returnVal;
    };


    /**
     * Finds if a component has a parent, other than the body
     * 
     * @since 0.3.4
     * @author Gagan Goraya
     */

    $scope.hasParent = function(id) {
        return $scope.findParentComponentItem($scope.componentsTree, id,  function( val ) {return val;} ) > 0;
    };


    /**
     * Recursively find a parent of component in Components Tree
     * and pass it to callback function
     * 
     * @since 0.1
     * @author Ilya K.
     */

    $scope.findParentComponentItem = function(node, id, callback, variable) {

        var returnVal;

        if ($scope.log) {
            //console.log("findParentComponentItem", id);
        }

        isBreak = false;

        angular.forEach(node.children, function(item) {

            if ( !isBreak ) {
                if ( item.id == id ) {
                    // do something if find
                    returnVal = callback(item.options['ct_parent'], node, id, variable);        
                    // stop the loop
                    isBreak = true;
                } 
                else {
                    // go deeper in Components Tree
                    if ( item.children ) {
                        returnVal = $scope.findParentComponentItem(item, id, callback, variable);
                    }
                }
            }

        });

        if(typeof(returnVal) !== 'undefined')
            return returnVal;
    };


    /**
     * Remove component from Components Tree
     * 
     * @since 0.1
     * @author Ilya K.
     */

    $scope.removeComponentFromTree = function(key, item, idToRemove, component) {

        if ($scope.log) {
            console.log("removeComponentFromTree()", idToRemove);
        }

        // fix for removing link from span
        if (component && component.removeName == "ct_span" && component.parentName == "ct_link") {
            
            item.options.ct_content = 
            item.options.ct_content.replace("<span id=\"ct-placeholder-"+component.parentId+"\"></span>",
                                            "<span id=\"ct-placeholder-"+component.removeId+"\"></span>");
        }

        angular.forEach(item.children, function(child, id){
            if ( child.id == idToRemove ) {
                item.children.splice(id, 1);
                $scope.newComponentKey = id;
            }
        })

        // remove children property if there is no more children left
        if ( item.children.length == 0 ) {
            delete item.children;
        }
        
        if ( component && component.removeName == "ct_span" && component.parentName == "ct_link" ) {
            $scope.rebuildDOM(item.id);
        }
    }


    /**
     * Insert new component to given Components Tree item
     * 
     * @since 0.1
     * @author Ilya K.
     */

    $scope.insertComponentToTree = function(key, parent, component, componentFromParent) {
        
        if ($scope.log) {
            console.log("insertComponentToTree()", key, parent, component );
        }

        if ( typeof component !== 'object' ) {
            component = componentFromParent;
        }
        
        var element = $scope.getComponentById(key),
            nestable = element.attr("is-nestable");

        if (component.isBuiltIn&&parent.name=='oxy_superbox') {
            nestable = true;
        }
        
        // look for parent if component not nestable and this is not a column, span or link over the span
        if ( !nestable && component.name != "ct_column" && component.name != "ct_span" &&  
             !(component.name == "ct_link" && component.currentName == "ct_span") &&
             // allow div block to be a child of new columns
             !(component.name == "ct_div_block" && parent.name == "ct_new_columns") &&
             // allow header row to be a child of header
             !(component.name == "oxy_header_row" && parent.name == "oxy_header") &&
             // allow header left/center/right to be a child of header row
             !((component.name == "oxy_header_left"||component.name == "oxy_header_center"||component.name == "oxy_header_right") && parent.name == "oxy_header_row")
             ) {
            
            $scope.findParentComponentItem($scope.componentsTree, key, $scope.insertComponentToTree, component);
            return;
        }

        // create empty children object if not exist
        if ( !parent.children ) {
            parent.children = [];
        }

        var child = {
            'id': component.id,
            'name': component.name,
            'options': {
                'ct_id': component.id,
                'ct_parent': key,
                'selector': component.name.slice(3) + "-" + component.id + "-" + CtBuilderAjax.postId,
                'original': {}
            },
        }
        
        // TODO: change to "type" parameter or something        
        if ( component.isShortcode ) {
            child.options['ct_shortcode'] = "true";
        }

        if ( component.isWidget ) {
            child.options['ct_widget'] = "true";
        }

		if ( component.isData ) {
			child.options['ct_data'] = "true";
		}

        if ( component.isSidebar ) {
            child.options['ct_sidebar'] = "true";
        }

        if ( component.isNavMenu ) {
            child.options['ct_nav_menu'] = "true";
        }

        if ( component.isBuiltIn ) {
            child.options['oxy_builtin'] = "true";
        }

        // link and span fix
        if ( component.name == "ct_link" && component.currentName == "ct_span" ) {
            
            parent.options.ct_content = 
            parent.options.ct_content.replace(  "<span id=\"ct-placeholder-"+component.currentId+"\"></span>",
                                                "<span id=\"ct-placeholder-"+component.id+"\"></span>")
        }

        // add columns number for columns component
        if ( component.name == "ct_columns" ) {
            $scope.columns[component.id] = 1;
        }
        
        var depth = $scope.calculateDepth(component, parent);

        // apply component depth if defined
        if ( depth ) {
            child.depth = depth;
        }

        // check if index specified
        if ( component.index ) {
            $scope.idToInsert = component.index;
            // deduct builtin components to get correct index
            for(key in parent.children) {
                var possibleBuiltInComponent = parent.children[key];
                if (possibleBuiltInComponent.options['oxy_builtin']=='true') {
                    $scope.idToInsert++;
                }
            } 
        }

        // paste new child
        if ( $scope.idToInsert >= 0 && $scope.idToInsert !== false) {
            parent.children.splice($scope.idToInsert, 0, child);
            $scope.idToInsert = -1;
        } else {
            parent.children.push(child);
        }

        // update columns widths
        if ( component.name == "ct_div_block" && parent.name == "ct_new_columns" && $scope.updateColumnsOnAdd ) {

            var newColumnWidth = 0,
                columnsNumber = (parent.children.length > 1) ? parent.children.length - 1 : 100,
                newColumnWidth = 100 / columnsNumber;

            $scope.setOptionModel("width-unit", "%", component.id, component.name);

            var hasActvieClass = false;

            // check if any column has active class selector
            angular.forEach(parent.children, function(column) {
                var columnObj = {};
                $scope.findComponentItem($scope.componentsTree.children, column.id, $scope.getComponentActiveSelector, columnObj);
                if (columnObj.activeselector) {
                    hasActvieClass = true;
                }
            });

            if (hasActvieClass) {
                return false;
            }

            // change all columns
            angular.forEach(parent.children, function(column) {
                // get width value
                var columnWidth = 100 / columnsNumber;
                // calculate new value
                columnWidth = (columnWidth / ((100 + newColumnWidth) / 100)).toFixed(2);
                // update width
                $scope.setOptionModel("width", columnWidth, column.id, "ct_div_block");
                //column.options.original.width = columnWidth + "%";
            });

            // make sure sum is always 100
            $scope.checkColumnsWidthSum(parent.children, "ct_div_block");
        }

        $scope.updateDOMTreeNavigator(component.id);
    }


    /**
     * Insert a component to a parent of given item
     * 
     * @since 0.1
     */

    $scope.insertComponentToParent = function(key, item, component) {

        $scope.findComponentItem($scope.componentsTree.children, item.options.ct_parent, $scope.insertComponentToTree, component);
    }

    $scope.insertComponentToChild = function(key, item, component) { // on first nestable child

        if(item.children && item.children.length > 0 && item.children[0]['name'] == "ct_div_block") {
            $scope.findComponentItem($scope.componentsTree.children, item.children[0]['id'], $scope.insertComponentToTree, component);
        }
    }

    /**
     * Insert a component to a grand parent of given item
     * 
     * @since 0.1.6
     */

    $scope.insertComponentToGrandParent = function(key, item, component) {

        $scope.findParentComponentItem($scope.componentsTree, item.options.ct_parent, $scope.insertComponentToTree, component);
    }


    /**
     * helper function to return a component item from the tree
     * 
     * @since 1.2.0
     */

    $scope.getComponentItem = function(key, item, variable) {
        return item;
    }

    /**
     * Paste existing component to Tree (callback in componentsReorder() and wrapWithComponent())
     * 
     * @since 0.1.3
     */

    $scope.pasteComponentToTree = function(key, parent, id) {

        if ($scope.log) {
            console.log("pasteComponentToTree()", key, parent, id, $scope.componentBuffer);
        }

        try {

            var componentElement = $scope.getComponentById($scope.componentBuffer.id);

            if (componentElement instanceof jQuery && componentElement.hasClass('oxy-tabs-contents')) {
                tabsContents = componentElement;
            }
            else {
                tabsContents = jQuery(componentElement).find('.oxy-tabs-contents');
            }

            if (tabsContents.length) {
                var timeout = $timeout(function() {
                    var tabsWrapperID = tabsContents.attr("data-oxy-tabs-wrapper"),
                        tabsWrapper = jQuery("#"+tabsWrapperID),
                        activeClass = tabsWrapper.attr("data-oxy-tabs-active-tab-class");

                    tabsWrapper.children("."+activeClass).trigger('click');

                    $timeout.cancel(timeout);
                }, 10, false);
            }

            if(jQuery('body').hasClass('ct_inner') && (parseInt(key) === 0 || parseInt(key) > 100000)) {
                if(jQuery('.ct-inner-content.ct-component').length > 0) {
                    key = parseInt(jQuery('.ct-inner-content.ct-component').attr('ng-attr-component-id'));
                    parent = $scope.findComponentItem($scope.componentsTree.children, key, $scope.getComponentItem);
                    $scope.componentBuffer.options.ct_parent = parseInt(key);
                }
            } 

            if ( parent.name == 'ct_inner_content' && (CtBuilderAjax['query'] && CtBuilderAjax['query']['post_type'] && CtBuilderAjax['query']['post_type'] === 'ct_template') && !jQuery('body').hasClass('ct_inner') ) {
                // paste it next to the ct_inner_content

                var lastparent = parent;

                // avoid nesting inside ct_inner_content, rather go for its parent
                key = parseInt(parent.options.ct_parent);
                parent = $scope.findComponentItem($scope.componentsTree.children, key, $scope.getComponentItem);
                $scope.componentBuffer.options.ct_parent = parseInt(key);

                // paste it next to the ct_inner_content

                $scope.newComponentKey = _.indexOf(parent.children, lastparent) + 1;

            }

            if ( !$scope.componentBuffer ) {
                return false;
            }

            if ( parent.name == "ct_columns" ) {

                var columnsNumber = parent.children.length;

                // check columns number
                if ( columnsNumber == 12 ) {
                    alert("Max number of columns is 12");
                    $scope.componentBuffer = false;
                    return false;
                }
            }
            
            $scope.updateComponentDepth($scope.componentBuffer, parent);
            
            // check if parent already have children
            if ( !parent.children ) {
                
                parent.children = [];
                parent.children.push($scope.componentBuffer);
            } 
            else {

                if ( $scope.newComponentKey >= 0 ) {
                    parent.children.splice($scope.newComponentKey, 0, $scope.componentBuffer);
                    $scope.newComponentKey = -1;
                } else {
                    parent.children.push($scope.componentBuffer);
                }
            }

            var componentName = "ct_column";

            if ( $scope.componentBuffer.name == "ct_div_block" ) {
                var componentName = "ct_div_block";
            }

            // update columns widths
            if ( ( $scope.componentBuffer.name == "ct_column" ||
                   ( $scope.componentBuffer.name == "ct_div_block" && parent.name == "ct_new_columns" ) 
                ) && !$scope.reorderSameParent ) {

                var hasActvieClass = false;

                // check if any column has active class selector
                angular.forEach(parent.children, function(column) {
                    var columnObj = {};
                    $scope.findComponentItem($scope.componentsTree.children, column.id, $scope.getComponentActiveSelector, columnObj);
                    if (columnObj.activeselector) {
                        hasActvieClass = true;
                    }
                });

                if (hasActvieClass) {
                    return false;
                }

                var newColumnWidth = 0,
                    columnsNumber = (parent.children.length > 1) ? parent.children.length - 1 : 100;

                if ($scope.componentBuffer.name == "ct_div_block" && ( !$scope.componentBuffer.options.original || !$scope.componentBuffer.options.original.width )) {
                    newColumnWidth = 100 / columnsNumber;
                    $scope.setOptionModel("width-unit", "%", $scope.componentBuffer.id, $scope.componentBuffer.name);
                }
                else {
                    newColumnWidth = parseFloat($scope.componentBuffer.options.original.width);
                }

                // change all columns
                angular.forEach(parent.children, function(column) {
                    // get width value
                    if ($scope.componentBuffer.name == "ct_div_block" && ( !column.options.original || !column.options.original.width )) {
                        var columnWidth = 100 / columnsNumber;
                    }
                    else {
                        var columnWidth = parseFloat(column.options.original.width);
                    }
                    // calculate new value
                    columnWidth = (columnWidth / ((100 + newColumnWidth) / 100)).toFixed(2);
                    // update width
                    $scope.setOptionModel("width", columnWidth, column.id, componentName);
                    //column.options.original.width = columnWidth + "%";
                });

                // make sure sum is always 100
                $scope.checkColumnsWidthSum(parent.children, componentName);
            }

            $scope.unsavedChanges();

        }
        catch(error) {
            console.error(error);
            var message = error.toString();
            var timeout = $timeout(function() {
                $scope.showErrorModal(0, 'There was an error. DO NOT SAVE THE PAGE. Please copy below error message and send to our support team.', message);
                $scope.$apply();
                $timeout.cancel(timeout);
            }, 10, false);
        }
    }
    

    /**
     * Calculate new element depth based on its parent depth
     * 
     * @since 0.1.7
     */

    $scope.calculateDepth = function(component, parent) {

        var depth = false;
        
        // Columns
        if ( component.name == 'ct_column' && parent.name == 'ct_columns' ) {
            depth = parseInt(parent.depth);
        }
        if ( component.name == 'ct_columns' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Header Builder
        if ( component.name == 'oxy_header' ) {
            depth = parseInt(parent.depth) + 1;
        }
        if ( component.name == 'oxy_header_row' || component.name == 'oxy_header_left' || component.name == 'oxy_header_center' || component.name == 'oxy_header_right' ) {
            depth = parseInt(parent.depth);
        }

        // New Columns
        if ( component.name == 'ct_new_columns' ) {
            depth = parseInt(parent.depth) + 1;
        }

        if ( component.name == 'oxy_dynamic_list' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Div Blocks
        if ( component.name == 'ct_div_block' || component.name == 'ct_nestable_shortcode' ) {
            depth = parseInt(parent.depth) + 1;
        }
        if ( (component.name == 'ct_div_block' || component.name == 'ct_nestable_shortcode') && (parent.name == 'ct_column' || parent.name == 'ct_new_columns') ) {
            depth = parseInt(parent.depth);
        }

        // Link Wrappers
        if ( component.name == 'ct_link' ) {
            depth = parseInt(parent.depth) + 1;
        }
        if ( component.name == 'ct_link' && parent.name == 'ct_column' ) {
            depth = parseInt(parent.depth);
        }

        // Sections
        if ( component.name == 'ct_section' ) {
            depth = parseInt(parent.depth) + 1;
        }
        if ( component.name == 'ct_section' && parent.name == 'ct_column' ) {
            depth = parseInt(parent.depth);
        }

        // Inner Content
        if ( component.name == 'ct_inner_content' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Slider
        if ( component.name == 'ct_slider' ) {
            depth = parseInt(parent.depth) + 1;
        }
        if ( component.name == 'ct_slide' ) {
            depth = parseInt(parent.depth);
        }


        // Icon Box
        if ( component.name == 'oxy_icon_box' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Super Box
        if ( component.name == 'oxy_superbox' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Toggle
        if ( component.name == 'oxy_toggle' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Tabs
        if ( component.name == 'oxy_tab' && parent.name == 'oxy_tabs' ) {
            depth = parseInt(parent.depth);
        }
        if ( component.name == 'oxy_tabs' ) {
            depth = parseInt(parent.depth) + 1;
        }

        if ( component.name == 'oxy_tab_content' && parent.name == 'oxy_tabs_contents' ) {
            depth = parseInt(parent.depth);
        }
        if ( component.name == 'oxy_tabs_contents' ) {
            depth = parseInt(parent.depth) + 1;
        }

        // Modal
        if ( component.name == 'ct_modal' ) {
            depth = parseInt(parent.depth) + 1;
        }

        if ( component.name == 'oxy-product-builder' ) {
            depth = parseInt(parent.depth) + 1;
        }

        return depth;
    }


    /**
     * Update component depth values
     * 
     * @since 0.1.7
     * @author Ilya K.
     */

    $scope.updateComponentDepth = function(component, parent) {

        var depth = $scope.calculateDepth(component, parent);

        // apply component depth if defined
        if ( depth ) {
            component.depth = depth;
        }

        // update children
        if (component.children) {
            
            angular.forEach( component.children, function(child) {
                $scope.updateComponentDepth(child,component);
            });
        }
    }


    /**
     * Update copied components IDs and selectors
     * 
     * @since 0.1.6
     * @author Ilya K.
     */

    $scope.updateNewComponentIds = function(item, parent) {

        if ($scope.log) {
            console.log("updateNewComponentIds()", item, parent);
        }

        // TODO: update only selectors that was not changed by user
        item.options.selector = item.name.slice(3) + "-" + $scope.component.id + "-" + CtBuilderAjax.postId;

        // update placeholders
        if ( parent.options && parent.options.ct_content ) {
            parent.options.ct_content = 
            parent.options.ct_content.replace(  "<span id=\"ct-placeholder-"+item.id+"\"></span>",
                                                "<span id=\"ct-placeholder-temporary-"+$scope.component.id+"\"></span>")
        }

        // update ids
        item.id                 = $scope.component.id;
        item.options.ct_id      = $scope.component.id;
        item.options.ct_parent  = parseInt(parent.id);

        // update children
        if (item.children) {
            angular.forEach( item.children, function(child) {
                
                $scope.component.id++;
                
                iframeScope.activeSelectors[$scope.component.id] = iframeScope.activeSelectors[child.id];
                
                $scope.updateNewComponentIds(child,item);
                
            });
        }

        if ( item.options && item.options.ct_content ) {
            // remove placeholders for not existing children
            item.options.ct_content = item.options.ct_content.replace(new RegExp('<span id="ct-placeholder-[0-9]+"></span>',"g"),"")
            // turn temporary placeholders back to normal
            item.options.ct_content = item.options.ct_content.replace(new RegExp('ct-placeholder-temporary-',"g"),"ct-placeholder-")
        }

        // Update Tabs
        if ( item.name == 'oxy_tabs' && !$scope.tabsContentsItemToUpdate ) {
            $scope.tabsItemToUpdate = item;
        }
        if ( item.name == 'oxy_tabs_contents' && $scope.tabsItemToUpdate ) {
            var selector    = item.options.selector,
                idToUpdate  = $scope.tabsItemToUpdate.options['ct_id'];
            $scope.tabsItemToUpdate.options.original['tabs_contents_wrapper'] = selector;
            var timeout = $timeout(function() {
                $scope.rebuildDOM(idToUpdate);
                $interval.cancel(timeout);
            }, 0, false);
            $scope.tabsItemToUpdate = false;
        }
        else if ( item.name == 'oxy_tabs_contents' && !$scope.tabsItemToUpdate ) {
            $scope.tabsContentsItemToUpdate = item;
        }
        if ( item.name == 'oxy_tabs' && $scope.tabsContentsItemToUpdate) {
            var selector = $scope.tabsContentsItemToUpdate.options.selector;
            
            item.options.original['tabs_contents_wrapper'] = selector;
            $scope.tabsContentsItemToUpdate = false;
        }
    }


    /**
     * Copy component tree node
     * 
     * @since 0.1.6
     * @author Ilya K.
     */

    $scope.copyComponentTreeNode = function(key, item, idToCopy) {

        angular.forEach( item.children, function(child, key) {

            if ( child.id == idToCopy ) {

                // process columns
                if ( child.name == "ct_column" && !$scope.reorderSameParent ) {

                    columnsNumber = item.children.length;

                    // check columns number
                    if ( columnsNumber == 12 ) {
                        alert("Max number of columns is 12");
                        $scope.componentBuffer = false;
                        return false;
                    }
                }
                
                // save in scope 
                $scope.componentBuffer = angular.copy(child);

                   // update new element ids and selector
                $scope.updateNewComponentIds($scope.componentBuffer, item);

                // save id
                $scope.newComponentKey = key+1;
            }
        })
    }


    /**
     * Cut components from Tree (callback in componentsReorder())
     * 
     * @since 0.1.3
     * @author Ilya K.
     */

    $scope.cutComponentFromTree = function(key, item, idToRemove) {

        if ($scope.log) {
            console.log("cutComponentFromTree()", item, idToRemove);
        }

        $scope.idToInsert = -1;

        angular.forEach( item.children, function(child, index){
            
            if ( child.id == idToRemove ) {

                var hasActvieClass = false;

                // process columns
                if ( ( child.name == "ct_column" ||
                     ( child.name == "ct_div_block" && item.name == "ct_new_columns") )
                    && !$scope.reorderSameParent ) {

                    newColumnsNumber    = item.children.length - 1;
                    freeSpace           = parseFloat(child.options.original.width);

                    // check if any column has active class selector
                    angular.forEach(item.children, function(column) {
                        var columnObj = {};
                        $scope.findComponentItem($scope.componentsTree.children, column.id, $scope.getComponentActiveSelector, columnObj);
                        if (columnObj.activeselector) {
                            hasActvieClass = true;
                        }
                    });

                    if (!hasActvieClass) {
                        angular.forEach(item.children, function(column) {

                            if (column.id != idToRemove) {

                                // get width value
                                columnWidth = parseFloat(column.options.original.width);
                                // calculate new value
                                columnWidth = (columnWidth + (freeSpace / newColumnsNumber) ).toFixed(2);
                                // update scope
                                //column.options.original.width = columnWidth + "%";
                                $scope.setOptionModel("width", columnWidth, column.id, "ct_column");
                            };
                        });
                    }
                }

                // update parent ID
                child.options.ct_parent = parseInt($scope.componentInsertId);

                // save in scope 
                $scope.componentBuffer = angular.copy(child);

                // remove from tree
                item.children.splice(index, 1);

                // remove children object if no children left
                if (item.children.length == 0) {
                    delete item.children;
                }

                if ( ( child.name == "ct_column" ||
                     ( child.name == "ct_div_block" && item.name == "ct_new_columns") )
                    && !$scope.reorderSameParent && !hasActvieClass) {
                    // make sure sum is always 100
                    $scope.checkColumnsWidthSum(item.children, "ct_div_block");
                }

                // save id
                $scope.idToInsert = index;
            }
        })
    }


    /**
     * update component's parent from Tree (callback in componentsReorder())
     * 
     * @since 1.2.0
     * @author Gagan Goraya.
     */

    $scope.updateComponentParentId = function(key, item) {

        if ($scope.log) {
            console.log("updateComponentParentId()", key);
        }
        
        item.options.ct_parent = parseInt($scope.componentInsertId);
        
    }

    /**
     * Reorder components in Tree
     * 
     * @since 0.3.1
     */

    $scope.componentsReorder = function(item, index, startParentId, endParentId, startParent, endParent) {

        if ($scope.log) {
            console.log(item, index, startParentId, endParentId, startParent, endParent);
        }
        
        try {
            var attr        = (item[0].attributes['ng-attr-tree-id']) ? 'ng-attr-tree-id' :     // when dragging in DOM tree
                                                                        'ng-attr-component-id', // when dragging component
                componentId = item[0].attributes[attr].value,
                parent      = item.parent();
            
            if(jQuery('body').hasClass('ct_inner') && (parseInt(endParentId) === 0 || parseInt(endParentId) > 100000 || typeof(endParentId) === 'undefined')) {
                //reassign the component to the ct_inner_content
                if(jQuery('.ct-inner-content.ct-component').length > 0) {
                    endParentId = parseInt(jQuery('.ct-inner-content.ct-component').attr('ng-attr-component-id'));
                }
            }

            // update ID to be actual parent ID instead of special drag area ID
            if (endParentId==99999){
                endParentId = endParent.attr('ng-attr-tree-actual-id');
                // increase index to offset the Icon builtin component 
                index++;
            }
            
            if (startParentId==99999){
                startParentId = startParent.attr('ng-attr-tree-actual-id');
            }

            // save parent ID in scope
            $scope.componentInsertId = endParentId;

            // save order in scope
            // $scope.oldComponentKey = 0;
            $scope.newComponentKey = index;
            $scope.reorderSameParent = ( startParentId == endParentId );

            // cut component from tree
            $scope.findParentComponentItem($scope.componentsTree, componentId, $scope.cutComponentFromTree);

            // find component to paste
            $scope.findComponentItem($scope.componentsTree.children, $scope.componentInsertId, $scope.pasteComponentToTree);

            // update the parent of the pasted item
            $scope.findComponentItem($scope.componentsTree.children, componentId, $scope.updateComponentParentId);

            var endParent   = $scope.getComponentById(endParentId),
                startParent = $scope.getComponentById(startParentId);

            // handle slide component
            if (endParent && endParent.hasClass("ct-slider")) {
                $scope.rebuildDOM(endParentId);
            }
            if (!$scope.reorderSameParent && startParent && startParent.hasClass("ct-slider")) {
                $scope.rebuildDOM(startParentId);
            }

            if (endParent && endParent.hasClass("ct-new-columns") && startParentId==endParentId) {
                $scope.rebuildDOM(endParentId);
            }
            else if (endParent && endParent.hasClass("ct-new-columns")) {
                $scope.rebuildDOM(endParentId);
                $scope.rebuildDOM(componentId, true);
            } 
            else {
                $scope.rebuildDOM(componentId, true);
            }

            // look if any parent should be rebuilt 
            $scope.rebuildDOMChangeParent(endParentId);
            $scope.rebuildDOMChangeParent(startParentId);

            $scope.updateDOMTreeNavigator(componentId, true);

            // check if it was the last child and startParent is AJAX element
            var parent = $scope.findComponentItem($scope.componentsTree.children, startParentId, $scope.getComponentItem);
            if ( !parent.children && $scope.isAJAXElement(parent.name)) {
                $scope.rebuildDOM(startParentId);
            }

            // check if it was the first child and endParent is AJAX element
            var parent = $scope.findComponentItem($scope.componentsTree.children, endParentId, $scope.getComponentItem);
            if ( parent.children && parent.children.length === 1 && $scope.isAJAXElement(parent.name)) {
                $scope.rebuildDOM(endParentId);
            }

            // disable undo option
            $scope.cancelDeleteUndo();
        
        }
        catch(error) {
            console.error(error);
            var message = error.toString();
            $scope.showErrorModal(0, 'There was an error. DO NOT SAVE THE PAGE. Please copy below error message and send to our support team.', message);
            $scope.$apply();
        }
    }


    /**
     * Check if we have selected or active components and wrap it with new component
     * 
     * @since 0.2.4
     */

    $scope.wrapWith = function(wrapperComponentName) {

        if ( $scope.isSelectableEnabled && $scope.isDOMNodesSelected ) {
            $scope.wrapSelectedComponentWith(wrapperComponentName);
            $scope.isDOMNodesSelected = false;
        }
        else {
            $scope.wrapComponentWith(wrapperComponentName);
        }
    }

    
    /**
     * Wrap component with new component
     * 
     * @since 0.1.5
     */

    $scope.wrapComponentWith = function(wrapperComponentName, componentId, parentId) {
        
        // component id to cut
        if (undefined === componentId) {
            componentId = $scope.component.active.id;
        }

        if (undefined === parentId) {
            parentId = $scope.component.active.parent.id;
        }

        if ($scope.log) {
            console.log("wrapComponentWith()", wrapperComponentName, componentId, parentId);
        }
        
        var newComponentId = $scope.component.id;
        
        newComponent = {
            id : newComponentId, 
            name : wrapperComponentName,
            currentId : $scope.component.active.id,
            currentName : $scope.component.active.name
        }

        // set component id to insert
        $scope.componentInsertId = newComponent.id;

        // cut component
        $scope.findParentComponentItem($scope.componentsTree, componentId, $scope.cutComponentFromTree);
        $scope.removeComponentFromDOM(componentId);
        $scope.removeDOMTreeNavigatorNode(componentId);
        
        // insert new component to the parent of cutted component
        $scope.findComponentItem($scope.componentsTree.children, parentId, $scope.insertComponentToTree, newComponent);

        // find component to paste
        $scope.findComponentItem($scope.componentsTree.children, $scope.componentInsertId, $scope.pasteComponentToTree);

        // update current active parent
        $scope.findParentComponentItem($scope.componentsTree, componentId, $scope.updateCurrentActiveParent);
        
        if (newComponent.currentName == "ct_span") {
            $scope.rebuildDOM(parentId);
            //$scope.activateComponent(parentId);
            $scope.updateDOMTreeNavigator(parentId);
        }
        else {
            $scope.rebuildDOM(newComponent.id);
            $scope.activateComponent(newComponent.id);
            if(parentId > 0 && parentId < 100000)
                $scope.updateDOMTreeNavigator(parentId);
            else
                $scope.updateDOMTreeNavigator();
            $scope.toggleNode(newComponent.id);
        }

        // disable undo option
        $scope.cancelDeleteUndo();

        return newComponentId;
    }


    /**
     * Wrap selected component(s) with new component
     *
     * @since 0.2.4
     */
    
    $scope.wrapSelectedComponentWith = function(wrapperComponentName) {

        var parent      = jQuery("#ct-dom-tree").find('.ct-selected-dom-node').first().parent().parent(),
            parentId    = parent.attr('ng-attr-tree-id'),
            nodes       = parent.children('.ct-dom-tree-node').has('.ct-selected-dom-node'),
            ids         = [];

        // get top level selected component ids
        nodes.each(function(){
            ids.push(jQuery(this).attr('ng-attr-tree-id'));
        });

        // create wrapper component
        newComponent = {
            id : $scope.component.id,
            name : wrapperComponentName,
            currentId : parentId,
            //currentName : $scope.component.active.name
        }

        // set component id to insert
        $scope.componentInsertId = newComponent.id;

        // insert new component to the parent of cutted component
        $scope.findComponentItem($scope.componentsTree.children, parentId, $scope.insertComponentToTree, newComponent);

        for (var i = ids.length - 1, id; id = ids[i], i >= 0; i--) {
            
            // cut component
            $scope.findParentComponentItem($scope.componentsTree, id, $scope.cutComponentFromTree);
            $scope.removeComponentFromDOM(id);

            // find component to paste
            $scope.findComponentItem($scope.componentsTree.children, $scope.componentInsertId, $scope.pasteComponentToTree);
        }

        // disable undo option
        $scope.cancelDeleteUndo();

        $scope.rebuildDOM(parentId);
        //$scope.updateDOMTreeNavigator(parentId);
    }


    /**
     * Remove parent of currently active component
     *
     * @since 0.1.8
     */

    $scope.removeActiveParent = function() {

        if ($scope.log) {
            console.log("removeActiveParent");
        }

        // component id to cut
        componentId = $scope.component.active.id;
        parentId    = $scope.component.active.parent.id

        component = {
            removeId    : $scope.component.active.id,
            removeName  : $scope.component.active.name,
            parentId    : $scope.component.active.parent.id,
            parentName  : $scope.component.active.parent.name
        }

        // update active parent
        $scope.findParentComponentItem($scope.componentsTree, parentId, $scope.updateCurrentActiveParent);

        // set component id to insert
        $scope.componentInsertId = $scope.component.active.parent.id;

        // cut component
        $scope.findParentComponentItem($scope.componentsTree, componentId, $scope.cutComponentFromTree);
        $scope.removeComponentFromDOM(componentId);

        // remove parent
        $scope.findParentComponentItem($scope.componentsTree, parentId, $scope.removeComponentFromTree, component);
        $scope.removeComponentFromDOM(parentId);

        // paste component
        $scope.findComponentItem($scope.componentsTree.children, $scope.componentInsertId, $scope.pasteComponentToTree);

        // update current active parent
        $scope.findParentComponentItem($scope.componentsTree, componentId, $scope.updateCurrentActiveParent);

        // disable undo option
        $scope.cancelDeleteUndo();
        
        $scope.rebuildDOM(componentId);
        $scope.removeDOMTreeNavigatorNode(parentId);
        $scope.updateDOMTreeNavigator(componentId);
    }
    

    /**
     * Callback for AJAX call. Build DOM and other stuff
     *
     * @since 0.2.3
     * @author Ilya K.
     */

    $scope.builderInit = function(tree) {
        
        if ($scope.log) {
            console.log("builderInit()", tree);
        }

        // set scope tree to the one saved in WordPress
        $scope.componentsTree = tree;

        if (tree === false) {
            alert("Error occured while trying to build a page.");
            return;
        }

        // load Elements API component's templates with AJAX
        $scope.loadComponentsTemplates();
        $scope.loadElementsPresets();

        // show builder ui
        parent.document.getElementById("oxygen-ui").style.display = "block";

        // some functions
        $scope.outputCSSOptions();

		$parentScope.current_post_meta_keys = tree.meta_keys;

        // auto save page revisions
        /*var autoSaveTimer = setInterval(function() {
            $scope.savePage(true);
        }, 1000 * 60 * 2);*/

        if(angular.element('body').hasClass('ct_inner')) {

            $scope.outerTemplateData['edit_link'] = tree.outerTemplateData['edit_link'];
            $scope.outerTemplateData['template_name'] = tree.outerTemplateData['template_name'];
            
            angular.element('body').on('click', function(e) {
                // if there exists an inner content area that the user can insert components into, but mouse click is not inside that area
                if(angular.element('.ct-inner-content-workarea').length > 0 && 
                    angular.element(e.target).closest('.ct-inner-content-workarea').length < 1 &&
                    angular.element(e.target).closest('#oxygen-resize-box').length < 1 &&
                    !jQuery(this).hasClass('oxy-dragging-resize-box')) {
                    if( typeof $scope.choosingSelectorEnabled != 'undefined' && $scope.choosingSelectorEnabled == true ) return;
                    $scope.activateComponent(0);
                    $scope.component.active.name = 'ct_template';
                    $scope.component.active.parent.id = 0
                    var timeout = $timeout(function() {
                        $scope.$apply();
                        $timeout.cancel(timeout);
                    }, 0, false);
                }
            });
        }
        var timeout = $timeout(function() {
            $parentScope.adjustStickyHeaders(window);
            if (typeof oxygenVSBInitToggleState !== 'undefined') {
                oxygenVSBInitToggleState();
            }
            $timeout.cancel(timeout);
        }, 0, false);

        if (oxygenVSBInitTabs!==undefined) {
            var timeout2 = $timeout(function() {
                oxygenVSBInitTabs();
                $timeout.cancel(timeout2);
            }, 500, false);
            var timeout3 = $timeout(function() {
                oxygenVSBInitTabs();
                $timeout.cancel(timeout3);
            }, 1000, false);
        }

        if ($scope.fixShortcodes) {
            var fixShortcodesTimeout = $timeout(function() {
                if ($scope.fixShortcodesFound) {
                    $scope.showNoticeModal('<p>Broken shortcodes found and fixed. See console log.</p>');
                }
                else {
                    $scope.showNoticeModal('<p>Broken shortcodes NOT found.</p>');
                }
                $scope.$apply();
                $timeout.cancel(fixShortcodesTimeout);
            }, 5000, false);
        }
    }


    /**
     * Recursively find if tree node has media styles
     * 
     * @since 0.4.0
     * @author Ilya K.
     */

    $scope.findMedia = function(node, mediaName) {

        var returnVal,
            isBreak = false;
        
        angular.forEach(node, function(item) {

            if ( !isBreak ) {
                if ( item.options['media'] && 
                     item.options['media'][mediaName] && 
                     item.options['media'][mediaName][$scope.currentState] ) {
                    // do something if find
                    returnVal = true;
                    // stop the loop
                    isBreak = true;
                } 
                else {
                    // go deeper in Components Tree
                    if ( item.children ) {
                        returnVal = $scope.findMedia(item.children, mediaName);
                    }
                }
            }

        });

        if(typeof(returnVal) !== 'undefined')
            return returnVal
    }


    /**
     * Recursively find all component's built in components and return the number of this components
     * 
     * @since 2.0
     * @author Ilya K.
     */

    $scope.calculateBuiltInOffset = function(key, node, variable) {

        var count = 0;

        if (!node.children) {
            return count;
        }
        
        angular.forEach(node.children, function(item) {
            if ( item.options.oxy_builtin) {
                count++;
            } 
        });

        return count;
    }

    
    /**
     * Callback to a recursive function to find out if component is buitin or not
     * 
     * @since 2.0
     * @author Ilya K.
     */
    
    $scope.isBuiltinComponent = function() {
        var id = $scope.component.active.id;
        return $scope.builtinComponents[id];
    }


    /**
     * Recursively find components in Components Tree by name
     * and update tag name
     * 
     * @since 0.4.0
     * @author Ilya K.
     */

    $scope.updateTagsByName = function(id, node, variable) {

        angular.forEach(node.children, function(item) {

            if ( item.name == variable.from ) {
                // update tag
                $scope.updateTreeComponentTag(item.id, item, variable.to)
            } 

            // go deeper in Components Tree
            if ( item.children ) {
                $scope.updateTagsByName(item.id, item, variable);
            }

        });
    };

    
    /**
     * Get Component Active Selector
     * 
     * @since 2.0
     * @author Ilya K.
     */

    $scope.getComponentActiveSelector = function(id, node, variable) {

        variable.activeselector = node.options.activeselector;
    }


    /**
     * Check if API component parent can have passed child
     * 
     * @since 2.3
     * @author Ilya K.
     */

    $scope.canBeChild = function(parent, child) {

        if ($scope.componentsTemplates == undefined) {
            return true;
        }

        if ($scope.componentsTemplates[parent] !== undefined && 
            $scope.componentsTemplates[parent]['onlyChild'] !== undefined) {
            
            if ($scope.componentsTemplates[parent]['onlyChild']!==child) {
                return false;
            }
        }

        if ($scope.componentsTemplates[child] !== undefined && 
            $scope.componentsTemplates[child]['onlyParent'] !== undefined) {
            
            if ($scope.componentsTemplates[child]['onlyParent']!==parent) {
                return false;
            }
        }

        return true;
    }


    /**
     * Check if it is an API component
     * 
     * @since 2.3
     * @author Ilya K.
     */

    $scope.isAPIComponent = function(name) {

        if ($scope.componentsTemplates !== undefined && 
            $scope.componentsTemplates[name] !== undefined ) {
            return true;
        }

        return false;
    }
    
    /**
     * Helper to get list of element children
     * 
     * @since 3.6
     * @author Ilya K.
     */

    $scope.getElementChildren = function(id, name) {

        if (undefined==id) {
            id = $scope.component.active.id;
            name = $scope.component.active.name;
        }

        if (name=="oxy_posts_grid") {
            var easyPosts = $scope.getComponentById(id);
            if (easyPosts) {
                return easyPosts.find('.oxy-posts > *').toArray();
            }
        }

        if (name=="oxy_gallery") {
            var gallery = $scope.getComponentById(id);
            if (gallery) {
                return gallery.find('.oxy-gallery-item').toArray();
            }
        }

        if (name=="oxy_dynamic_list") {
            var repeater = $scope.getComponentById(id);
            if (repeater) {
                var children = repeater.children().toArray();
                // repeater has one hidden div we should not to use for preview
                children.splice(1,1);
                return children;
            }
        }

        var element = $scope.findComponentItem($scope.componentsTree.children, id, $scope.getComponentItem);

        return element.children ? element.children : [];
    }

});