@eform/ng-formio-builder
Version:
The Angular.js form builder component.
344 lines (297 loc) • 12 kB
JavaScript
var _isNumber = require('lodash/isNumber');
var _camelCase = require('lodash/camelCase');
var _assign = require('lodash/assign');
var _upperFirst = function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
module.exports = [
'$scope',
'$element',
'$rootScope',
'formioComponents',
'FormioUtils',
'ngDialog',
'dndDragIframeWorkaround',
'$timeout',
'BuilderUtils',
function(
$scope,
$element,
$rootScope,
formioComponents,
FormioUtils,
ngDialog,
dndDragIframeWorkaround,
$timeout,
BuilderUtils
) {
$scope.builder = true;
$rootScope.builder = true;
$scope.hideCount = (_isNumber($scope.hideDndBoxCount) ? $scope.hideDndBoxCount : 1);
$scope.$watch('hideDndBoxCount', function(hideCount) {
$scope.hideCount = hideCount ? hideCount : 1;
});
$scope.formComponents = formioComponents.components;
if (!$scope.component) {
$scope.component = $scope.form;
$scope.$watch('form', function(newForm) {
if ($scope.component.type === 'form') {
$scope.component = newForm;
}
});
}
// Components depend on this existing
if (!$scope.data) {
$scope.data = {};
}
$scope.emit = function() {
var args = [].slice.call(arguments);
args[0] = 'formBuilder:' + args[0];
$scope.$emit.apply($scope, args);
};
$scope.$on('editComponent', function(event, component) {
$scope.editComponent(component);
});
$scope.$on('fbDragDrop', function(event, component) {
component.settings.id = Math.random().toString(36).substring(7);
component.settings.overlay = {
page: '1',
top: component.fbDropY,
left: component.fbDropX
};
$scope.addComponent(component.settings);
});
$scope.addComponent = function(component, index) {
delete component.hideLabel;
// Allow changing default lock options.
if ($scope.options && $scope.options.noLockKeys) {
delete component.source;
delete component.lockKey;
}
// Add parent key to default key if parent is present.
// Sometimes $scope.component is the parent but columns and tables it is actually the column.
var parent = $scope.parent || $scope.component;
if (parent.type !== 'form' && parent.type !== 'resource' && component.isNew && component.key) {
$scope.parentKey = parent.key;
component.key = $scope.parentKey + _upperFirst(component.key);
} else {
$scope.parentKey = '';
}
if (index === 'undefined') {
index = -1;
}
// If this is a root component and the display is a wizard, then we know
// that they dropped the component outside of where it is supposed to go...
// Instead append or prepend to the components array.
if ($scope.component.display === 'wizard') {
var pageIndex = (index === 0) ? 0 : $scope.form.components[$scope.form.page].components.length;
index = pageIndex;
$scope.$apply(function() {
$scope.form.components[$scope.form.page].components.splice(pageIndex, 0, component);
});
}
// Only edit immediately for components that are not resource comps.
if (component.isNew && !component.lockConfiguration && (!component.key || (component.key.indexOf('.') === -1))) {
$scope.editComponent(component, index);
}
else {
// Ensure the component has a key.
component.key = component.key || component.label || 'component';
BuilderUtils.uniquify($scope.form, component);
// Update the component to not be flagged as new anymore.
FormioUtils.eachComponent([component], function(child) {
delete child.isNew;
}, true);
}
// Refresh all CKEditor instances
$scope.$broadcast('ckeditor.refresh');
dndDragIframeWorkaround.isDragging = false;
$scope.emit('add');
$scope.$broadcast('iframeMessage', {name: 'addElement', data: component});
// Make sure that they don't ever add a component on the bottom of the submit button.
var lastComponent = $scope.component.components[$scope.component.components.length - 1];
if (
(lastComponent) &&
(lastComponent.type === 'button') &&
(lastComponent.action === 'submit')
) {
// There is only one element on the page.
if ($scope.component.components.length === 1) {
index = 0;
}
else if (index >= $scope.component.components.length) {
index -= 1;
}
}
// Add the component to the components array.
$scope.component.components.splice(index, 0, component);
$timeout($scope.$apply.bind($scope));
// Return true since this will tell the drag-and-drop list component to not insert into its own array.
return true;
};
$scope.updateKey = function(component) {
if (!component.lockKey && component.isNew) {
if ($scope.data.hasOwnProperty(component.key)) {
delete $scope.data[component.key];
}
if (component.label) {
var invalidRegex = /^[^A-Za-z_]*|[^A-Za-z0-9\-_]*/g;
component.key = _camelCase($scope.parentKey + ' ' + component.label.replace(invalidRegex, ''));
}
BuilderUtils.uniquify($scope.form, component);
$scope.data[component.key] = component.multiple ? [''] : '';
}
};
var remove = function(component) {
if ($scope.component.components.indexOf(component) !== -1) {
$scope.component.components.splice($scope.component.components.indexOf(component), 1);
$scope.emit('remove', component);
$scope.$broadcast('iframeMessage', {name: 'removeElement', data: component});
}
};
$scope.saveComponent = function(component) {
$scope.emit('update', component);
$scope.$broadcast('iframeMessage', {name: 'updateElement', data: component});
ngDialog.closeAll(true);
};
$scope.removeComponent = function(component, shouldConfirm) {
if (shouldConfirm) {
// Show confirm dialog before removing a component
ngDialog.open({
template: 'formio/components/confirm-remove.html',
showClose: false
}).closePromise.then(function(e) {
var cancelled = e.value === false || e.value === '$closeButton' || e.value === '$document' || e.value === '$escape';
if (!cancelled) {
remove(component);
}
});
}
else {
remove(component);
}
};
// Edit a specific component.
$scope.editComponent = function(component, index) {
index = index || 0;
$scope.formComponent = formioComponents.components[component.type] || formioComponents.components.custom;
// No edit view available
if (!$scope.formComponent.hasOwnProperty('views')) {
return;
}
// Create child isolate scope for dialog
var childScope = $scope.$new(false);
childScope.component = component;
childScope.data = {};
childScope.index = index;
if (component.key) {
childScope.data[component.key] = component.multiple ? [''] : '';
}
var previousSettings = angular.copy(component);
// Make sure this component has a key.
if (!component.key) {
component.key = component.type;
}
// Open the dialog.
$scope.updateKey(component);
var originalKey = component.key;
ngDialog.open({
template: 'formio/components/settings.html',
scope: childScope,
className: 'ngdialog-theme-default component-settings',
controller: ['$scope', 'Formio', '$controller', function($scope, Formio, $controller) {
$scope.editorVisible = true;
// Allow the component to add custom logic to the edit page.
if ($scope.formComponent && $scope.formComponent.onEdit) {
$controller($scope.formComponent.onEdit, {$scope: $scope});
}
$scope.$watch('component.multiple', function(value) {
$scope.data[$scope.component.key] = value ? [''] : '';
});
var editorDebounce = null;
$scope.$watchCollection('component.wysiwyg', function() {
$scope.editorVisible = false;
if (editorDebounce) {
clearTimeout(editorDebounce);
}
editorDebounce = setTimeout(function() {
$scope.editorVisible = true;
}, 200);
});
// Watch the settings label and auto set the key from it.
$scope.$watch('component.label', function() {
$scope.updateKey($scope.component);
});
}]
}).closePromise.then(function(e) {
var cancelled = e.value === false || e.value === '$closeButton' || e.value === '$document' || e.value === '$escape';
if (cancelled) {
if (component.isNew) {
return remove(component);
}
// Revert to old settings, but use the same object reference
_assign(component, previousSettings);
return;
}
// If there is no component label, then set it to the key and set hide label to ensure reverse compatibility.
// Don't calculate for components that don't have a label.
if (!component.label && ['panel', 'content', 'fieldset', 'table', 'well'].indexOf(component.type) === -1) {
component.key = originalKey;
component.label = component.key || component.type;
component.hideLabel = true;
}
FormioUtils.eachComponent([component], function(child) {
delete child.isNew;
}, true);
$scope.emit('edit', component);
});
};
// Clone form component
$scope.cloneComponent = function(component) {
var newComponent = angular.copy(component);
// If we are in a panel, the cloned component container should be the parent.components,
// Otherwise it should be the form.components.
var container;
if ($scope.$parent.component && $scope.$parent.component.type === 'columns') {
var column = _.find($scope.$parent.component.columns, function(column) {
return _.find(column.components, {key: newComponent.key});
});
container = column.components;
}
else if ($scope.$parent.component) {
container = $scope.$parent.component.components;
}
else {
container = $scope.form.components;
}
// Add the new component right after the original.
var index = container.indexOf(component);
container.splice(index + 1, 0, newComponent);
// timeout: wait for the form to have the new component.
$timeout(function() {
FormioUtils.eachComponent([newComponent], function(child) {
child.isNew = true;
BuilderUtils.uniquify($scope.form, child);
delete child.isNew;
}, true);
});
};
$scope.copyComponent = function(component) {
window.sessionStorage.setItem('componentClipboard', JSON.stringify(angular.copy(component)));
}
$scope.pasteComponent = function() {
var component;
try {
component = JSON.parse(window.sessionStorage.getItem('componentClipboard'));
}
catch(e) {
console.log('Error fetching componentClipboard');
}
if (component) {
$scope.cloneComponent(component);
}
}
// Add to scope so it can be used in templates
$scope.dndDragIframeWorkaround = dndDragIframeWorkaround;
}
];