Repository URL to install this package:
|
Version:
3.7.1 ▾
|
/**
* All Templates/Views related functions
*
*/
CTFrontendBuilder.controller("ControllerTemplates", function($scope, $parentScope, $timeout, $interval){
$scope.replaceAfterReusable = false;
$scope.template = {};
$scope.template.postData = {};
$scope.componentizeOptions = {};
$scope.componentizeOptions.name = "Re-usable component";
$scope.componentizeOptions.pageName = "Re-usable page";
$scope.componentizeOptions.setName = "My Design Set";
$scope.componentizeOptions.status = "public";
$scope.previewType = '';
$scope.separatorAdded = false;
/**
* Load term data by term id
*
* @since 0.3.3
* @author Ilya K.
*/
$scope.loadTemplatesTerm = function(id, type){
$scope.previewType = type;
// make AJAX call
$scope.loadTemplateData($scope.setTemplateData, id);
}
/**
* Callback to set post data
*
* @since 0.2.0
* @author Ilya K.
*/
$scope.setTemplateData = function(data) {
if ($scope.log) {
console.log("setTemplateData()", data);
}
// update body classes if set in data
if (undefined!==data.bodyClass) {
jQuery("body").addClass(data.bodyClass).addClass("oxygen-builder-body");
}
if (undefined!==data.postData) {
$scope.template.postData = data.postData;
}
// update edit link if set in data
if (undefined!==data.edit_link) {
$scope.template.postData['edit_link'] = data.edit_link;
}
if(undefined!==data.postsList || undefined!==data.termsList) {
var sentPreviewRequest = false;
if (undefined!==data.postsList) {
if (data.postsList === null) {
//alert("No posts found to preview");
}
else {
$scope.template.postsList = data.postsList;
// if default is set
if(data['default']) {
$scope.loadTemplatesTerm(data['default'].id, 'post');
sentPreviewRequest = true;
}
else if(data.postsList[0]) {
// load first post
$scope.loadTemplatesTerm(data.postsList[0].id, 'post');
sentPreviewRequest = true;
}
}
}
if (undefined!==data.termsList && data.termsList[0]) {
$scope.template.termsList = data.termsList;
// load first term
if(!sentPreviewRequest) {
$scope.loadTemplatesTerm(data.termsList[0].id, 'term');
sentPreviewRequest = true;
}
}
if(sentPreviewRequest) {
return;
}
}
if(data.meta_keys) {
$parentScope.current_post_meta_keys = data.meta_keys;
}
// rebuild DOM
// TODO: rebuild only code blocks??
$scope.rebuildDOM(0);
$scope.updateDOMTreeNavigator();
$scope.outputCSSOptions();
}
/**
* Load Re-usable part via AJAX by it's ID
* If componentId defined load only children into this component
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.loadReusablePart = function(viewPostId, componentId) {
if (componentId !== undefined) {
$scope.loadComponentsTree($scope.addReusableChildren, viewPostId, componentId);
}
else {
$scope.loadComponentsTree($scope.addReusable, viewPostId, componentId);
}
}
/**
* Insert "Re-usable part" Component to the Components Tree and rebuild the DOM
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.addReusable = function(data, viewPostId) {
var tree = {};
// response from oxygen server
if ( data[0] !== undefined && data[0]["content"] !== undefined ) {
tree = data[0]["content"];
}
// response from user install
else {
tree = data;
}
if ($scope.log) {
console.log("addReusable()", viewPostId);
}
var componentId = $scope.component.active.id;
var reusable = {
id: 0,
name: "ct_reusable",
options: {
view_id: viewPostId
}
}
var componentToInsert = $scope.getClosestPossibleInsert(componentId, tree, $scope.replaceAfterReusable);
$scope.componentBuffer = reusable;
$scope.componentInsertId = componentToInsert.id;
if (componentToInsert.index >= 0) {
$scope.newComponentKey = componentToInsert.index;
}
var insertedId = $scope.component.id;
// update new element ids and selector
$scope.updateNewComponentIds($scope.componentBuffer, componentToInsert);
// paste reusable to current tree
$scope.findComponentItem($scope.componentsTree.children, componentToInsert.id, $scope.pasteComponentToTree);
if($scope.replaceAfterReusable) {
$scope.removeComponentById($scope.replaceAfterReusable)
$scope.replaceAfterReusable = false;
}
// disable undo option
$scope.cancelDeleteUndo();
// updates
$scope.rebuildDOM(insertedId);
$scope.updateDOMTreeNavigator(insertedId);
$scope.activateComponent(insertedId);
// scroll to the component after selector is parsed
var timeout = $timeout(function() {
$parentScope.scrollToComponent($scope.getActiveComponent().attr('id'));
$interval.cancel(timeout);
}, 0, false);
// init Tabs if any inside the reusable
if (oxygenVSBInitTabs!==undefined) {
var timeout2 = $timeout(function() {
oxygenVSBInitTabs('#'+$scope.component.options[insertedId].selector);
$interval.cancel(timeout2);
}, 500, false);
var timeout3 = $timeout(function() {
oxygenVSBInitTabs('#'+$scope.component.options[insertedId].selector);
$interval.cancel(timeout3);
}, 1500, false);
}
$scope.unsavedChanges();
}
$scope.recursively_find_reusable = function(children) {
_.forEach(children, function(item) {
if(item.name == 'ct_reusable') {
// try looking if this post already exists in the db, how?
// import the post, store it in the db, and updates the IDS in the component
var post_id = item.options['view_id'];
}
if(item['children']) {
$scope.recursively_find_reusable(item['children']);
}
});
}
$scope.addPageFromSource = function(data, source, designSet) {
var componentId = $scope.component.active.id;
if(componentId < 0) {
iframeScope.activateComponent(0, 'root');
componentId = 0;
iframeScope.currentClass = false;
}
var contents = JSON.parse(data);
if(!contents || !contents.components || contents.components.length < 1) {
$scope.showErrorModal(0, 'Could not load the page. '+(contents['error']?contents['error']:'Try again!'));
return;
}
var components = false, classes = false;
if(contents['components']) {
components = contents['components'];
}
if(!components) {
$scope.showErrorModal(0, "Page not loaded, try again");
return;
}
if(contents['classes']) {
classes = contents['classes'];
}
var globalColorsMap = {};
if(contents['colors']) {
globalColorsMap = $scope.mergeIncomingSourceColors(contents['colors'], designSet);
}
var lookupTable = {};
if(contents['lookuptable']) {
lookupTable = contents['lookuptable'];
}
if(classes) {
// for all global color values in the classes, replace the id from globalColorsMap
if(Object.keys(globalColorsMap).length > 0) {
classes = $scope.ctMapSourceColors(classes, globalColorsMap);
}
// if data is from an old design set? then convert all static colors to variables
if(CtBuilderAjax.classicDesignsets.find(function(item) { return item == designSet;})) {
classes = $scope.ctCreateVariableColors(classes, designSet, lookupTable);
}
// all the classes are supposed to go under a folder that is named after the url of the source site.
// if a folder with this name does not exists, create one
if(!$scope.styleFolders.hasOwnProperty(designSet)) {
$scope.styleFolders[designSet] = { status: 1, key: designSet};
}
for(key in classes) {
classes[key]['parent'] = designSet;
}
$scope.classes = Object.assign(classes, $scope.classes);
$scope.classesCached = false;
}
var firstInsertedID = $scope.component.id;
_.each(components, function(component) {
var insertedId = $scope.component.id;
// for all global color values in the component options, replace the id from globalColorsMap
if(Object.keys(globalColorsMap).length > 0) {
component = $scope.ctMapSourceColors(component, globalColorsMap);
}
// if data is from an old design set? then convert all static colors to variables
if(CtBuilderAjax.classicDesignsets.find(function(item) { return item == designSet;})) {
component = $scope.ctCreateVariableColors(component, designSet, lookupTable);
}
var componentToInsert = { id: 0, index: iframeScope.getComponentById(componentId).index()+1};
//$scope.getClosestPossibleInsert(componentId, component);
$scope.componentBuffer = component;
if (componentToInsert.index >= 0) {
$scope.newComponentKey = componentToInsert.index;
}
// update new element ids and selector
$scope.updateNewComponentIds($scope.componentBuffer, componentToInsert);
// paste loaded content to current tree
$scope.findComponentItem($scope.componentsTree.children, componentToInsert.id, $scope.pasteComponentToTree);
// disable undo option
$scope.cancelDeleteUndo();
// updates
$scope.rebuildDOM(insertedId);
$scope.updateDOMTreeNavigator();
$scope.updateComponentCacheStyles(insertedId, function() {
// clear cache
$scope.cache.idCSS = "";
Object.keys($scope.cache.idStyles).map(function(key, index) {
$scope.cache.idCSS += $scope.cache.idStyles[key];
});
$scope.outputCSSStylesAfterWait('id', $scope.cache.idCSS);
});
componentId = insertedId;
});
$scope.rebuildDOM(0);
// updates
$scope.outputCSSOptions();
$scope.activateComponent(firstInsertedID);
var timeout = $timeout(function() {
$parentScope.scrollToComponent($scope.getActiveComponent().attr('id'));
$interval.cancel(timeout);
}, 0, false);
}
/**
* replace old global color IDs to the new stored ones, in the incoming data from a source site
*
* @since 2.1
* @author Gagan S Goraya
*/
$scope.ctMapSourceColors = function (settings, globalColorsMap) {
for(key in settings) {
if(typeof(settings[key]) === 'string') {
settings[key] = settings[key].replace(/color\((\d*)\)/g, function(matches, match) { return 'color('+globalColorsMap[parseInt(match)]+')'})
}
else if(settings[key] instanceof Array || settings[key] instanceof Object) {
settings[key] = $scope.ctMapSourceColors(settings[key], globalColorsMap);
}
}
return settings;
}
$scope.ctCreateVariableColorsCallback = function(match, designSet, lookupTable) {
// if a set with the source site's name does not exist, create one, if it exists, get its id
var set = _.findWhere(iframeScope.globalColorSets['sets'], {name: designSet});
if(typeof(set) === 'undefined') {
set = {
id : ++iframeScope.globalColorSets['setsIncrement'],
name : designSet
}
iframeScope.globalColorSets['sets'].push(set);
}
var colorVal = match.toLowerCase();
var nameFromLookupTable = false;
if(lookupTable[colorVal]) {
nameFromLookupTable = lookupTable[colorVal];
}
else if(lookupTable[colorVal.replace(/ /g, '')]) { // an rgb or rgba value with/out spaces
nameFromLookupTable = lookupTable[colorVal.replace(/ /g, '')];
}
if(colorVal.length === 4) { // then it is a 3 letter hex color, convert it into 6
colorVal = '#'+colorVal.substr(1).repeat(2);
if(nameFromLookupTable === false && lookupTable[colorVal]) {
nameFromLookupTable = lookupTable[colorVal];
}
}
var existingColor;
var firstColor = false;
// check if the color with the same lookup table name exists and it belongs to the same set
if(nameFromLookupTable !== false) {
// find the first occurance of this name in the table
_.each(lookupTable, function(item, key) {
if(firstColor===false) {
if(item == nameFromLookupTable) {
firstColor = key;
}
}
if(firstColor.length === 4) { // then it is a 3 letter hex color, convert it into 6
firstColor = '#'+firstColor.substr(1).repeat(2);
}
})
existingColor = _.findWhere(iframeScope.globalColorSets['colors'], {name: nameFromLookupTable, set: set['id']});
if(typeof(existingColor) !== 'undefined') {
if(firstColor !== colorVal) { // then change the color to match with the first occurance with the same name in the lookup table
colorVal = firstColor;
existingColor['value'] = firstColor;
}
}
}
// check if the color with the same value exists and it belongs to the same set
if(typeof(existingColor) === 'undefined') {
existingColor = _.findWhere(iframeScope.globalColorSets['colors'], {sourceVal: colorVal, set: set['id']});
}
if(typeof(existingColor) === 'undefined') {
if(nameFromLookupTable == 'IGNORE') {
return colorVal;
}
//create a new colors in this set
existingColor = {
id: ++iframeScope.globalColorSets['colorsIncrement'],
name: nameFromLookupTable?nameFromLookupTable:'color #'+iframeScope.globalColorSets['colorsIncrement'],
value: colorVal, // this color value will be used to render the components
sourceVal: colorVal, // this will keep a record of the original color value that this name was assigned to
set: set['id']
}
iframeScope.globalColorSets['colors'].push(existingColor);
}
return 'color('+existingColor['id']+')';
}
/**
* create color variables out of static colors in the provided data
*
* @since 2.1
* @author Gagan S Goraya
*/
$scope.ctCreateVariableColors = function (settings, designSet, lookupTable ) {
for(key in settings) {
if(typeof(settings[key]) === 'string') {
settings[key] = settings[key].replace(/#[0-9|a-f]{3,6}|rgba\([\d|\s]*\,[\d|\s]*\,[\d|\s]*\,[^\)]*\)/ig, function(match) {
return $scope.ctCreateVariableColorsCallback(match, designSet, lookupTable)
}
)
}
else if(settings[key] instanceof Array || settings[key] instanceof Object) {
settings[key] = $scope.ctCreateVariableColors(settings[key], designSet, lookupTable);
}
}
// find the set id
var set = _.find(iframeScope.globalColorSets['sets'], {name: designSet});
if(typeof(set) === 'undefined') {
return settings;
}
var sortedColors = [];
// reorder global colors as per the lookup table
_.each(lookupTable, function(name) {
var found = _.find(iframeScope.globalColorSets['colors'], {set: set['id'], name: name });
var index;
if(found) {
index = iframeScope.globalColorSets['colors'].indexOf(found);
}
if(typeof(index) !== 'undefined') {
sortedColors.push(found);
iframeScope.globalColorSets['colors'].splice(index, 1);
}
})
iframeScope.globalColorSets['colors'] = sortedColors.concat(iframeScope.globalColorSets['colors']);
return settings;
}
/**
* merge incoming global colors from a source site
*
* @since 2.1
* @author Gagan S Goraya
*/
$scope.mergeIncomingSourceColors = function(colors, designSet) {
// merge incoming colors into existing global colors and generate a map of incoming id against new id
var globalColorsMap = {};
// set Name for the incoming colors
// if a set with the source site's name does not exist, create one, if it exists, get its id
var set = _.findWhere(iframeScope.globalColorSets['sets'], {name: designSet});
if(typeof(set) === 'undefined') {
set = {
id : ++iframeScope.globalColorSets['setsIncrement'],
name : designSet
}
iframeScope.globalColorSets['sets'].push(set);
}
for(key in colors) {
// check if the color with the same name exists and it belongs to the same set
var existingColor = _.findWhere(iframeScope.globalColorSets['colors'], {name: colors[key]['name'], set: set['id']});
if(typeof(existingColor) !== 'undefined') {
globalColorsMap[parseInt(key)] = parseInt(existingColor['id']);
}
else {
//create a new colors in this set
iframeScope.globalColorSets['colors'].push({
id: ++iframeScope.globalColorSets['colorsIncrement'],
name: colors[key]['name'],
value: colors[key]['value'],
set: set['id'],
});
globalColorsMap[parseInt(key)] = parseInt(iframeScope.globalColorSets['colorsIncrement']);
}
}
return globalColorsMap;
}
$scope.addComponentFromSource = function(data, componentId, source, designSet) {
if (componentId == undefined || !componentId) {
componentId = $scope.component.active.id;
}
if(componentId < 0) {
iframeScope.activateComponent(0, 'root');
componentId = 0;
iframeScope.currentClass = false;
}
var contents = JSON.parse(data);
if(!contents || !contents.component || contents.component.length < 1) {
$scope.showErrorModal(0, 'Could not load the component. '+(contents['error']?contents['error']:'Try again!'));
return;
}
var component = false, classes = false;
if(contents['component']) {
component = contents['component'];
}
if(!component) {
$scope.showErrorModal(0, "Component not loaded, try again");
return;
}
if(contents['classes']) {
classes = contents['classes'];
}
var globalColorsMap = {};
if(contents['colors']) {
globalColorsMap = $scope.mergeIncomingSourceColors(contents['colors'], designSet);
}
var lookupTable = {};
if(contents['lookuptable']) {
lookupTable = contents['lookuptable'];
}
// for all global color values in the component options, replace the id from globalColorsMap
if(Object.keys(globalColorsMap).length > 0) {
component = $scope.ctMapSourceColors(component, globalColorsMap);
}
// if data is from an old design set? then convert all static colors to variables
if(CtBuilderAjax.classicDesignsets.find(function(item) { return item == designSet;})) {
component = $scope.ctCreateVariableColors(component, designSet, lookupTable);
}
// if the component uses any re-usable items, find if those already exist in the db, if not, pull them to the db and update the post IDS and stuff
// if(component['children']) {
// $scope.recursively_find_reusable(component['children']);
// console.log(component);
// }
var insertedId = $scope.component.id;
var componentToInsert = $scope.getClosestPossibleInsert(componentId, component);
$scope.componentBuffer = component;
if (componentToInsert.index >= 0) {
$scope.newComponentKey = componentToInsert.index;
}
if(classes) {
// for all global color values in the classes, replace the id from globalColorsMap
if(Object.keys(globalColorsMap).length > 0) {
classes = $scope.ctMapSourceColors(classes, globalColorsMap);
}
// if data is from an old design set? then convert all static colors to variables
if(CtBuilderAjax.classicDesignsets.find(function(item) { return item == designSet;})) {
classes = $scope.ctCreateVariableColors(classes, designSet, lookupTable);
}
// all the classes are supposed to go under a folder that is named after the url of the source site.
// if a folder with this name does not exists, create one
if(!$scope.styleFolders.hasOwnProperty(designSet)) {
$scope.styleFolders[designSet] = { status: 1, key: designSet};
}
for(key in classes) {
classes[key]['parent'] = designSet;
}
$scope.classes = Object.assign(classes, $scope.classes);
$scope.classesCached = false;
}
// update new element ids and selector
$scope.updateNewComponentIds($scope.componentBuffer, componentToInsert);
// paste loaded content to current tree
$scope.findComponentItem($scope.componentsTree.children, componentToInsert.id, $scope.pasteComponentToTree);
// disable undo option
$scope.cancelDeleteUndo();
// updates
$scope.rebuildDOM(insertedId);
$scope.updateDOMTreeNavigator();
$scope.updateComponentCacheStyles(insertedId, function() {
// clear cache
$scope.cache.idCSS = "";
Object.keys($scope.cache.idStyles).map(function(key, index) {
$scope.cache.idCSS += $scope.cache.idStyles[key];
});
$scope.outputCSSStylesAfterWait('id', $scope.cache.idCSS);
});
// updates
$scope.outputCSSOptions();
$scope.activateComponent(insertedId);
var timeout = $timeout(function() {
$parentScope.scrollToComponent($scope.getActiveComponent().attr('id'));
$interval.cancel(timeout);
}, 0, false);
}
/**
* Insert "Re-usable part" chidlren to the Components Tree and build each child DOM node
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.addReusableChildren = function(data, viewPostId, componentId) {
if (componentId == undefined) {
componentId = $scope.component.active.id;
}
if ($scope.log) {
console.log("addReusableChildren()", data, viewPostId, componentId);
}
var tree = {};
// response from oxygen server
if ( data[0] !== undefined && data[0]["content"] !== undefined ) {
// do nothing
}
// response from user install
else {
tree = data;
}
var componentToInsert = $scope.getClosestPossibleInsert(componentId, tree),
firstInsertedID = -1;
angular.forEach(tree.children, function(child, key) {
var insertedId = $scope.component.id;
if (firstInsertedID<0) {
firstInsertedID = insertedId;
}
$scope.componentBuffer = child;
if (componentToInsert.index >= 0) {
$scope.newComponentKey = componentToInsert.index + key;
}
// update new element ids and selector
$scope.updateNewComponentIds($scope.componentBuffer, componentToInsert);
// paste loaded content to current tree
$scope.findComponentItem($scope.componentsTree.children, componentToInsert.id, $scope.pasteComponentToTree);
// disable undo option
$scope.cancelDeleteUndo();
// updates
$scope.rebuildDOM(insertedId);
$scope.updateDOMTreeNavigator();
$scope.updateComponentCacheStyles(insertedId, function() {
// clear cache
$scope.cache.idCSS = "";
Object.keys($scope.cache.idStyles).map(function(key, index) {
$scope.cache.idCSS += $scope.cache.idStyles[key];
});
$scope.outputCSSStylesAfterWait('id', $scope.cache.idCSS);
});
// init Tabs if any inside the reusable
if (oxygenVSBInitTabs!==undefined) {
var timeout2 = $timeout(function() {
oxygenVSBInitTabs('#'+$scope.component.options[insertedId].selector);
$interval.cancel(timeout2);
}, 500, false);
var timeout3 = $timeout(function() {
oxygenVSBInitTabs('#'+$scope.component.options[insertedId].selector);
$interval.cancel(timeout3);
}, 1500, false);
}
})
$scope.activateComponent(firstInsertedID);
// scroll to the component after selector is parsed
var timeout = $timeout(function() {
$parentScope.scrollToComponent($scope.getActiveComponent().attr('id'));
$interval.cancel(timeout);
}, 0, false);
// updates
$scope.outputCSSOptions();
// hide dialog widnow for API
$parentScope.hideDialogWindow();
}
/**
* Insert "Re-usable part" Content to the DOM
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.addReusableContent = function(data, componentId) {
if (!data) {
return;
}
if ($scope.log) {
console.log("addReusableContent()", data);
}
if(data['edit_link']) {
$scope.reusableEditLinks[componentId] = data['edit_link'];
}
var componentToInsert = $scope.getComponentById(componentId);
if (componentToInsert) {
componentToInsert.append(data.post_content);
// replace dnd-type attribute from "ct-reusable" to original type
var contentObject = jQuery(data.post_content);
if(contentObject) {
var classList = [];
if(contentObject.attr("class")) {
classList = contentObject.attr("class").split(/\s+/);
}
for(i = 0; i < classList.length; ++i) {
if (classList[i].indexOf("ct-") >= 0) {
var type = classList[i].replace(/\-/g, '_');
$scope.component.options[componentId].dndtype = type;
break;
}
}
}
}
// holder for reusable CSS
$scope.reusableCSS = {};
$scope.reusableCSS.styles = "";
// add this item CSS
if (data.post_tree) {
$scope.generateTreeCSS(data.post_tree, $scope.reusableCSS, function() {
// output CSS styles generated for all reusable's items
$scope.outputCSSStyles("ct-re-usable-styles-"+data.ID, $scope.reusableCSS.styles);
});
}
}
/**
* Get closest possibly nestable for re-usable part component ID and children count
*
* @since 0.2.3
*/
$scope.getClosestPossibleInsert = function(componentId, tree, atleastParent) {
if ($scope.log) {
console.log("getClosestPossibleInsert()", componentId, tree);
}
if (undefined == componentId) {
return {};
}
var componentToInsertIn = $scope.getComponentById(componentId);
insertData = {
componentToInsertIn : componentToInsertIn,
componentInsertId : 0,
parentId : 0,
index : (componentId > 0 ) ? componentToInsertIn.index() + 1 : -1
},
i = 0;
var isNestable = insertData.componentToInsertIn[0].attributes['is-nestable'],
isReusable = insertData.componentToInsertIn[0].classList.contains("ct_reusable");
var treeJSON = JSON.stringify(tree),
hasSection = (treeJSON.indexOf('"name":"ct_section"') > -1),
hasLink = (treeJSON.indexOf('"name":"ct_link"') > -1 ||
treeJSON.indexOf('"name":"ct_link_text"') > -1);
function findClosestParent(insertData, className) {
var parent = insertData.componentToInsertIn,
parentId = insertData.parentId;
while ( !parent.hasClass(className) && !parent.hasClass('ct-builder') ) {
parent = parent.parent();
}
insertData.parentId = parent[0].getAttribute('ng-attr-component-id');
// found component in parents
if (insertData.parentId != 0) {
insertData.index = parent.index() + 1;
// go level up from component
insertData.componentToInsertIn = jQuery(parent).parent().closest("[is-nestable]");
insertData.componentInsertId = insertData.componentToInsertIn[0].getAttribute('ng-attr-component-id');
}
else {
// set previous parent back
insertData.parentId = parentId;
}
}
// search for section in parents
if (hasSection) {
findClosestParent(insertData, "ct_section");
}
// search for link wrapper in parents
if (hasLink) {
findClosestParent(insertData, "ct_link");
}
if (insertData.parentId == 0) {
//console.log(componentToInsertIn, isNestable, isReusable);
// adding to nestable component
if ( isNestable && !isReusable && !atleastParent ) {
insertData.componentInsertId = componentId;
insertData.index = -1
}
// find nestable parent
else {
while ( ((!isNestable || atleastParent) || isReusable) && i < 10 ) {
insertData.componentToInsertIn = jQuery(insertData.componentToInsertIn).parent().closest("[is-nestable]");
if ( insertData.componentToInsertIn ) {
isNestable = insertData.componentToInsertIn[0].attributes['is-nestable'];
isReusable = insertData.componentToInsertIn[0].classList.contains("ct_reusable");
insertData.componentInsertId = insertData.componentToInsertIn[0].getAttribute('ng-attr-component-id');
}
if(isNestable)
atleastParent = false;
// prevent infinite loop
i++;
}
}
}
return {
id: insertData.componentInsertId,
index: insertData.index,
}
}
/**
* Check if component can be componentized as Re-usable part
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.isCanComponentize = function(id, name) {
var exclude = ['ct_reusable','oxy_header_row','ct_slide','oxy_tab','oxy_tab_content']
if (exclude.indexOf(name)>-1) {
return false;
}
if ( id > 0 ) {
return true;
}
else {
return false;
}
}
/**
* Send Tree node to save as View CPT
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.saveReusable = function(id, saveToBlock) {
if( typeof saveToBlock === 'undefined') saveToBlock = false;
$scope.cancelDeleteUndo();
if (undefined === id) {
id = $scope.component.active.id;
}
var promptTitle = saveToBlock ? "New Block Name" : "New Re-Usable Name";
var name = prompt(promptTitle, $scope.componentizeOptions.name);
if (name!=null) {
$parentScope.showLoadingOverlay("saveReusable()");
$scope.componentizeOptions.name = name;
// choose components to componentize
if ( $scope.isSelectableEnabled && $scope.isDOMNodesSelected ) {
var parent = jQuery("#ct-dom-tree").find('.ct-selected-dom-node').first().parent().parent(),
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'));
});
$scope.selectedComponents = [];
// add all components from tree
for (var i = 0, id; id = ids[i], i <= ids.length - 1; i++) {
$scope.findComponentItem($scope.componentsTree.children, id, $scope.addSelectedComponent);
}
$scope.saveComponentAsView(null, $scope.selectedComponents, saveToBlock);
}
else {
// save component as View CPT
$scope.findComponentItem($scope.componentsTree.children, id, $scope.saveComponentAsView, saveToBlock);
}
}
else {
alert("Name can't be empty");
}
}
/**
* Replace components saved as re-usable with actual Re-usable component
*
* @since 1.0.1
* @author Ilya K.
*/
$scope.replaceReusablePart = function(id, post_id) {
$scope.replaceAfterReusable = id;
$scope.loadReusablePart(post_id);
};
/**
* Show componentize dialog
*
* @since 0.4.0
* @author Ilya K.
*/
$scope.showComponentize = function(id) {
$parentScope.showDialogWindow();
$scope.componentizeOptions.id = id;
$parentScope.dialogForms['showComponentizeForm'] = true;
}
/**
* Send Tree node to save on server
*
* @since 0.2.3
* @author Ilya K.
*/
$scope.componentize = function() {
// TODO: add validation here to not send request to the server
// choose components to componentize
if ( $scope.isSelectableEnabled && $scope.isDOMNodesSelected ) {
var parent = jQuery("#ct-dom-tree").find('.ct-selected-dom-node').first().parent().parent(),
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'));
});
$scope.selectedComponents = [];
// add all components from tree
for (var i = 0, id; id = ids[i], i <= ids.length - 1; i++) {
$scope.findComponentItem($scope.componentsTree.children, id, $scope.addSelectedComponent);
}
$scope.postComponentize(null, $scope.selectedComponents);
}
else {
if ($scope.componentizeOptions.idToUpdate>0) {
// update existed component
$scope.componentizeOptions.assetId = null;
$scope.findComponentItem($scope.componentsTree.children, $scope.componentizeOptions.id, $scope.postComponentize);
$scope.componentizeOptions.id = null;
}
else {
// post new component
$scope.postAsset($scope.componentizeOptions.screenshot, function(){
$scope.findComponentItem($scope.componentsTree.children, $scope.componentizeOptions.id, $scope.postComponentize);
$scope.componentizeOptions.id = null;
});
}
}
$parentScope.hideDialogWindow();
}
/**
* Show componentize dialog for page
*
* @since 0.4.0
* @author Ilya K.
*/
$scope.showPageComponentize = function(id) {
$parentScope.showDialogWindow();
$parentScope.dialogForms['showPageComponentizeForm'] = true;
}
/**
* Send whole Components Tree to server
*
* @since 0.4.0
* @author Ilya K.
*/
$scope.tryPageComponentize = function() {
// post component to server
$scope.postAsset($scope.componentizeOptions.screenshot, function(){
$scope.pageComponentize();
});
$parentScope.hideDialogWindow();
}
/**
* Simply adds one component to scope variable to save selectaed as componentize
*
* @since 0.2.4
*/
$scope.addSelectedComponent = function(key, component) {
$scope.selectedComponents.push(component);
}
/**
* Add widget component
*
* @since 0.2.3
*/
$scope.addWidget = function(className, idBase, prettyName) {
var widgetId = $scope.component.id;
$scope.addComponent('ct_widget', 'widget');
// update class name
$scope.component.options[widgetId]['model']['class_name'] = className;
$scope.setOption(widgetId, "ct_widget", "class_name");
// update id base
$scope.component.options[widgetId]['model']['id_base'] = idBase;
$scope.setOption(widgetId, "ct_widget", "id_base");
// update niceName
$scope.component.options[widgetId]['model']['pretty_name'] = prettyName;
$scope.setOption(widgetId, "ct_widget", "pretty_name");
$scope.component.options[widgetId]['nicename'] = prettyName + " (#"+widgetId+")";
$scope.updateFriendlyName(widgetId);
//$scope.rebuildDOM(widgetId);
}
/**
* Add sidebar component
*
* @since 2.0
*/
$scope.addSidebar = function(id) {
var sidebarId = $scope.component.id;
$scope.addComponent('ct_sidebar', 'sidebar');
// update class name
$scope.setOptionModel("sidebar_id", id, sidebarId, "ct_sidebar");
$scope.rebuildDOM(sidebarId);
}
/**
* Load widget form with options
*
* @since 0.2.3
*/
$scope.loadWidgetForm = function() {
$scope.renderWidget($scope.component.active.id, true);
}
/**
* Apply widget options and render
*
* @since 0.2.3
*/
$scope.applyWidgetInstance = function(id) {
if ($scope.log) {
console.log("applyWidgetInstance()",id);
}
if (!id) {
id = $scope.component.active.id;
}
var widgetOptions = $scope.component.options[id];
if (widgetOptions.name != "ct_widget") {
return false;
}
var values = {},
inputs = jQuery('#ct-widget-form',window.parent.document).serializeArray(),
// get base
idBase = widgetOptions['original']['id_base'];
// add uncheked checkboxes
var checkboxes = jQuery('#ct-widget-form input:checkbox',window.parent.document).map(function() {
if ( !this.checked )
return { name: this.name, value: this.checked ? this.value : false };
});
for (i = 0; i < checkboxes.length; i++) {
inputs.push(checkboxes[i]);
}
jQuery.each(inputs, function(i, field) {
// remove widget-id_base[1][...]
var name = field.name.replace(idBase, "");
//console.log(name);
name = name.replace(/widget-\[\d\]\[/, "");
//console.log(name);
name = name.replace("]", "");
//console.log(name);
// Parse multidimensional names
if (name.indexOf('[') > -1) {
var newName = name.replace(/\[(.*)\]/, ''),
key = name.replace(newName, ''),
keys = [],
matches = key.match(/\[[^\]]*\]/g);
var parseNames = function(object, value, head, tail) {
if (tail.length == 0) {
object[head] = value;
} else {
if('undefined' == typeof object[head] ) {
object[head] = {};
}
parseNames(object[head], value, tail.shift(), tail);
}
}
for (j = 0; j < matches.length; j++) {
if ('[]' == matches[j])
continue;
keys.push(matches[j].replace( /\[|\]/g, '' ));
}
if (keys.length > 0) {
var keysCopy = keys.slice();
if ('undefined' == typeof values[newName]) {
values[newName] = {};
}
parseNames(values[newName], field.value, keysCopy.shift(), keysCopy);
} else {
if ('undefined' == typeof values[newName]) {
values[newName] = [];
}
values[newName].push(field.value);
}
}
// Regular names
else {
if (name!=="")
values[name] = field.value;
}
});
// update model and Components Tree
$scope.component.options[$scope.component.active.id]['model']['instance'] = values;
$scope.setOption($scope.component.active.id, "ct_widget", "instance");
// render widget with new options
$scope.renderWidget($scope.component.active.id);
}
/**
* Show dialog with a form for HTML component
*
* @since 0.2.3
*/
$scope.loadHTMLForm = function() {
$parentScope.showDialogWindow();
angular.element("#ct-html-component-form").val($scope.component.options[$scope.component.active.id]['model']['html']);
}
/**
* Apply all code block parts: PHP, JS, CSS
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.applyCodeBlock = function(componentId, updateTree) {
if ($scope.log) {
console.log("applyCodeBlock()", componentId, updateTree);
}
$scope.applyCodeBlockPHP(componentId, updateTree);
$scope.applyCodeBlockJS(componentId, updateTree);
$scope.applyCodeBlockCSS(componentId, updateTree);
}
/**
* Get executed code block PHP
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.applyCodeBlockPHP = function(componentId, updateTree) {
if (componentId === undefined) {
componentId = $scope.component.active.id;
}
if (updateTree === undefined) {
updateTree = true;
}
if ($scope.log) {
console.log("applyCodeBlockPHP()", componentId);
}
// update Tree
if (updateTree) {
$scope.setOption(componentId, 'ct_code_block', 'code-php', false, false);
}
var selector = "#" + $scope.component.options[componentId]['selector'],
code = $scope.getOption('code-php', componentId);
$scope.execCode(code, selector, $scope.insertElementContent);
}
/**
* Get executed code block JS
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.applyCodeBlockJS = function(componentId, updateTree) {
$scope.applyingComponentJS = true;
jQuery(".oxygen-code-error-container", $parentScope.oxygenUIElement).hide().html("");
if (componentId === undefined) {
componentId = $scope.component.active.id;
}
if (updateTree === undefined) {
updateTree = true;
}
if ($scope.log) {
console.log("applyCodeBlockJS()", componentId, updateTree);
}
// update Tree
if (updateTree) {
$scope.setOption(componentId, 'ct_code_block', 'code-js', false, false);
}
// output to DOM
code = $scope.getOption('code-js', componentId);
$scope.outputJSScript("js-code-", componentId, code)
$scope.applyingComponentJS = false;
}
/**
* Get executed code block CSS
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.applyCodeBlockCSS = function(componentId, updateTree) {
if (componentId === undefined) {
componentId = $scope.component.active.id;
}
if (updateTree === undefined) {
updateTree = true;
}
if ($scope.log) {
console.log("applyCodeBlockCSS()", componentId, updateTree);
}
// update Tree
if (updateTree) {
$scope.setOption(componentId, 'ct_code_block', 'code-css', false, false);
}
// output to <head>
code = $scope.getOption('code-css', componentId);
var selector = $scope.component.options[componentId]['selector'];
code = code.replace(new RegExp("%%ELEMENT_ID%%", 'g'), selector);
code = $scope.replaceGlobalColors(code);
$scope.outputCSSStyles("ct_code_block_css_"+componentId, code);
}
/**
* Append <script> element to DOM with passed code
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.outputJSScript = function(name, id, code) {
if ($scope.log){
console.log("outputJSScript()", name, id)
}
// replace %%ELEMENT_ID%% with actual id attribute
var selector = $scope.component.options[id]['selector'];
code = code.replace(new RegExp("%%ELEMENT_ID%%", 'g'), selector);
// remove old script
var oldScript = document.getElementById(name+id);
oldScript && document.body.removeChild(oldScript);
// create new script
var script = document.createElement('script');
script.type = 'text/javascript';
script.setAttribute("id", name+id);
try {
script.appendChild(document.createTextNode(code));
document.body.appendChild(script);
} catch (e) {
script.text = code;
document.body.appendChild(script);
}
}
/**
* Add window.onerror function to output errors as notice in JS tab
*
* @since 1.0.1
* @author Ilya K.
*/
$scope.setupJSErrorNotice = function() {
window.onerror = function oxygenErrorHandler(errorMsg, url, lineNumber) {
if ( $scope.applyingComponentJS ) {
jQuery(".oxygen-code-error-container", $parentScope.oxygenUIElement).show().html(errorMsg+" on line #"+lineNumber);
}
else {
if (typeof url == "object") {
return;
}
// don't show AOS selector error, it doesn't affect the builder in any way
if (url.indexOf !== undefined && url.indexOf("/component-framework/vendor/aos/aos.js")>-1) {
return;
}
$scope.showNoticeModal("<div>"+errorMsg+" on line #"+lineNumber+" in "+url+"</div>");
}
}
}
/**
* Insert text/html content to any HTML element by selector
*
* @since 0.2.4
* @author Ilya K.
*/
$scope.insertElementContent = function(result, placholderSelector, isUI) {
if ($scope.log) {
console.log("insertElementContent()", result, placholderSelector)
}
var component = angular.element(placholderSelector);
component.html("");
$scope.cleanInsert("<span class='removeOnInsert'>"+result+"</span>", component);
$scope.adjustResizeBox();
}
/**
* Get ID of a Link Wrapper
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.getLinkId = function(id) {
if ( undefined === id ) {
id = $scope.component.active.id;
}
// get closest parent
var link = jQuery("[ng-attr-component-id='"+id+"']", "#ct-builder").closest(".ct-links");
// link found
if (link.length > 0) {
return link.attr("ng-attr-component-id");
}
// not found
else {
return false;
}
}
/**
* Get ID of a Link Wrapper
*
* @since 0.3.1
* @author Ilya K.
*/
$scope.addSeparator = function(id) {
$scope.separatorAdded = true;
}
/**
* Add classes styles to components tree to pass with API
*
* @since 0.4.0
* @author Ilya K.
*/
$scope.addComponentClassesStyles = function(component) {
if (component.options && component.options.classes !== undefined ) {
for (var className in $scope.classes) {
if (component.options && component.options.classes.indexOf(className) > -1) {
var index = component.options.classes.indexOf(className);
component.options.classes.splice(index,1);
component.options.classes.push({
'name': className,
'styles': $scope.classes[className]
})
}
}
}
// loop children
for(var key in component.children) {
if (component.children.hasOwnProperty(key)) {
// get child
var child = component.children[key];
$scope.addComponentClassesStyles(child);
}
}
}
/**
* Parse components tree from API to add classes styles
*
* @since 0.4.0
* @author Ilya K.
*/
$scope.parseTreeClassesStyles = function(component) {
if (component.options && component.options.classes !== undefined ) {
for (var key in component.options.classes) {
if ( component.options.classes.hasOwnProperty(key)) {
if (component.options.classes[key]["name"] != undefined) {
var className = component.options.classes[key]["name"],
styles = component.options.classes[key]["styles"];
// add class name back to the tree
component.options.classes[key] = className;
// check if class exist
if ( $scope.classes[className] !== undefined ) {
//console.log("'"+className+"' class already exist in your install. Styles for this class won't be added.");
}
else {
// add class styles to global classes object
$scope.classes[className] = styles;
}
}
}
}
$scope.classesCached = false;
$scope.outputCSSOptions();
}
// loop children
for(var key in component.children) {
if (component.children.hasOwnProperty(key)) {
// get child
var child = component.children[key];
$scope.parseTreeClassesStyles(child);
}
}
}
});
/**
* jQuery serializeObject
* @copyright 2014, macek <paulmacek@gmail.com>
* @link https://github.com/macek/jquery-serialize-object
* @license BSD
* @version 2.5.0
*/
!function(e,i){if("function"==typeof define&&define.amd)define(["exports","jquery"],function(e,r){return i(e,r)});else if("undefined"!=typeof exports){var r=require("jquery");i(exports,r)}else i(e,e.jQuery||e.Zepto||e.ender||e.$)}(this,function(e,i){function r(e,r){function n(e,i,r){return e[i]=r,e}function a(e,i){for(var r,a=e.match(t.key);void 0!==(r=a.pop());)if(t.push.test(r)){var u=s(e.replace(/\[\]$/,""));i=n([],u,i)}else t.fixed.test(r)?i=n([],r,i):t.named.test(r)&&(i=n({},r,i));return i}function s(e){return void 0===h[e]&&(h[e]=0),h[e]++}function u(e){switch(i('[name="'+e.name+'"]',r).attr("type")){case"checkbox":return"on"===e.value?!0:e.value;default:return e.value}}function f(i){if(!t.validate.test(i.name))return this;var r=a(i.name,u(i));return l=e.extend(!0,l,r),this}function d(i){if(!e.isArray(i))throw new Error("formSerializer.addPairs expects an Array");for(var r=0,t=i.length;t>r;r++)this.addPair(i[r]);return this}function o(){return l}function c(){return JSON.stringify(o())}var l={},h={};this.addPair=f,this.addPairs=d,this.serialize=o,this.serializeJSON=c}var t={validate:/^[a-z_][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i,key:/[a-z0-9_]+|(?=\[\])/gi,push:/^$/,fixed:/^\d+$/,named:/^[a-z0-9_]+$/i};return r.patterns=t,r.serializeObject=function(){return new r(i,this).addPairs(this.serializeArray()).serialize()},r.serializeJSON=function(){return new r(i,this).addPairs(this.serializeArray()).serializeJSON()},"undefined"!=typeof i.fn&&(i.fn.serializeObject=r.serializeObject,i.fn.serializeJSON=r.serializeJSON),e.FormSerializer=r,r});