@eform/ng-formio-builder
Version:
The Angular.js form builder component.
443 lines (397 loc) • 15.4 kB
JavaScript
/*eslint max-statements: 0*/
var _cloneDeep = require('lodash/cloneDeep');
var _each = require('lodash/each');
var _omitBy = require('lodash/omitBy');
var _groupBy = require('lodash/groupBy');
var _upperFirst = require('lodash/upperFirst');
var _merge = require('lodash/merge');
var _capitalize = require('lodash/capitalize');
module.exports = ['debounce', function(debounce) {
return {
replace: true,
templateUrl: 'formio/formbuilder/builder.html',
scope: {
form: '=?',
src: '=',
url: '=?',
type: '=',
onSave: '=',
onCancel: '=',
options: '<'
},
controller: [
'$scope',
'formioComponents',
'ngDialog',
'Formio',
'FormioUtils',
'dndDragIframeWorkaround',
'$interval',
'$timeout',
function(
$scope,
formioComponents,
ngDialog,
Formio,
FormioUtils,
dndDragIframeWorkaround,
$interval,
$timeout
) {
$scope.options = $scope.options || {};
// Add the components to the scope.
var submitButton = angular.copy(formioComponents.components.button.settings);
if (!$scope.form) {
$scope.form = {};
}
if (!$scope.form.components) {
$scope.form.components = [];
}
if (!$scope.form.display) {
$scope.form.display = 'form';
}
if (!$scope.options.noSubmit && !$scope.form.components.length) {
$scope.form.components.push(submitButton);
}
$scope.hideCount = 2;
$scope.form.page = 0;
var baseUrl = $scope.options.baseUrl || Formio.getBaseUrl();
var formSrc = $scope.url || $scope.src || Formio.getProjectUrl();
$scope.formio = new Formio(formSrc, {base: baseUrl});
var setNumPages = function() {
if (!$scope.form) {
return;
}
if ($scope.form.display !== 'wizard') {
return;
}
var numPages = 0;
$scope.form.components.forEach(function(component) {
if (component.type === 'panel') {
numPages++;
}
});
$scope.form.numPages = numPages;
// Add a page if none is found.
if (numPages === 0) {
$scope.newPage();
}
// Make sure the page doesn't excede the end.
if ((numPages > 0) && ($scope.form.page >= numPages)) {
$scope.form.page = numPages - 1;
}
};
// Load the form.
if ($scope.src && $scope.formio && $scope.formio.formId) {
$scope.formio.loadForm().then(function(form) {
$scope.form = form;
if (!$scope.form.display) {
$scope.form.display = 'form';
}
if (!$scope.options.noSubmit && $scope.form.components.length === 0) {
$scope.form.components.push(submitButton);
}
$scope.showPage(0);
});
}
$scope.$on('iframe-componentUpdate', function(event, data) {
FormioUtils.eachComponent($scope.form.components, function(component) {
if (data.id && component.id) {
if (component.id === data.id) {
component.overlay = data.overlay;
}
}
else if (component.key === data.key) {
component.overlay = data.overlay;
}
});
});
$scope.$on('iframe-componentClick', function(event, data) {
FormioUtils.eachComponent($scope.form.components, function(component) {
if (data.id && component.id) {
if (component.id === data.id) {
$scope.$broadcast('editComponent', component);
}
}
else if (component.key === data.key) {
$scope.$broadcast('editComponent', component);
}
});
});
// Ensure we always have a page set.
$scope.$watch('form.page', function(page) {
if (page === undefined) {
$scope.showPage(0);
}
});
$scope.$watch('form.display', function(display) {
$scope.hideCount = (display === 'wizard') ? 1 : 2;
});
// Ensure that they don't remove components by canceling the edit modal.
$scope.$watch('form._id', function(_id) {
if (!_id) {
return;
}
FormioUtils.eachComponent($scope.form.components, function(component) {
delete component.isNew;
}, true);
});
// Make sure they can switch back and forth between wizard and pages.
$scope.$on('formDisplay', function(event, display) {
$scope.form.display = display;
setNumPages();
$timeout(function() {
$scope.showPage(0);
$scope.$apply();
});
});
// Return the form pages.
$scope.pages = function() {
var pages = [];
$scope.form.components.forEach(function(component) {
if (component.type === 'panel') {
if (component.title) {
pages.push(component.title);
}
else {
pages.push('Page ' + (pages.length + 1));
}
}
});
return pages;
};
$scope.getPage = function() {
var pageNum = 0;
for (var i = 0; i < $scope.form.components.length; i++) {
var component = $scope.form.components[i];
if (component.type === 'panel') {
if (i === $scope.form.page) {
break;
}
pageNum++;
}
}
return pageNum;
};
// Show the form page.
/* eslint-disable max-depth */
$scope.showPage = function(page) {
var pageNum = 0;
if ($scope.form && $scope.form.components) {
for (var i = 0; i < $scope.form.components.length; i++) {
var component = $scope.form.components[i];
if (component.type === 'panel') {
if (pageNum === page) {
pageNum = i;
break;
}
pageNum++;
}
}
}
$scope.form.page = pageNum;
};
/* eslint-enable max-depth */
$scope.newPage = function() {
var index = $scope.form.numPages;
var pageNum = index + 1;
var component = {
type: 'panel',
title: 'Page ' + pageNum,
isNew: false,
components: [],
input: false,
key: 'page' + pageNum
};
$scope.form.numPages++;
$scope.$emit('newPage', {
index: index,
component: component
});
$scope.form.components.splice(index, 0, component);
};
// Ensure the number of pages is always correct.
$scope.$watch('form.components.length', function() {
setNumPages();
});
$scope.formComponents = _cloneDeep(formioComponents.components);
_each($scope.formComponents, function(component, key) {
component.settings.isNew = true;
if (component.settings.hasOwnProperty('builder') && !component.settings.builder || component.disabled) {
delete $scope.formComponents[key];
}
});
$scope.pdftypes = [
$scope.formComponents.textfield,
$scope.formComponents.number,
$scope.formComponents.password,
$scope.formComponents.email,
$scope.formComponents.phoneNumber,
$scope.formComponents.currency,
$scope.formComponents.checkbox,
$scope.formComponents.signature,
$scope.formComponents.select,
$scope.formComponents.textarea,
$scope.formComponents.datetime
];
$scope.formComponentGroups = _cloneDeep(_omitBy(formioComponents.groups, 'disabled'));
$scope.formComponentsByGroup = _groupBy($scope.formComponents, function(component) {
return component.group;
});
// Get the resource fields.
var resourceEnabled = !formioComponents.groups.resource || !formioComponents.groups.resource.disabled;
if ($scope.formio && resourceEnabled) {
$scope.formComponentsByGroup.resource = {};
$scope.formComponentGroups.resource = {
title: $scope.options.resourceTitle || 'Existing Resource Fields',
panelClass: 'subgroup-accordion-container',
subgroups: {}
};
var query = {params: {
type: 'resource',
limit: 4294967295,
select: '_id,title,name,components'
}};
if ($scope.options && $scope.options.resourceFilter) {
query.params.tags = $scope.options.resourceFilter;
}
$scope.formio.loadForms(query).then(function(resources) {
// Iterate through all resources.
_each(resources, function(resource) {
var resourceKey = resource.name;
// Add a legend for this resource.
$scope.formComponentsByGroup.resource[resourceKey] = [];
$scope.formComponentGroups.resource.subgroups[resourceKey] = {
title: resource.title
};
// Iterate through each component.
FormioUtils.eachComponent(resource.components, function(component) {
if (component.type === 'button') return;
if ($scope.options && $scope.options.resourceFilter && (!component.tags || component.tags.indexOf($scope.options.resourceFilter) === -1)) return;
var componentName = component.label;
if (!componentName && component.key) {
componentName = _upperFirst(component.key);
}
$scope.formComponentsByGroup.resource[resourceKey].push(_merge(
_cloneDeep(formioComponents.components[component.type], true),
{
title: componentName,
group: 'resource',
subgroup: resourceKey,
settings: component
},
{
settings: {
label: component.label,
key: component.key,
lockKey: true,
source: (!$scope.options.noSource ? resource._id : undefined),
isNew: true
}
}
));
}, true);
});
});
}
var update = function() {
$scope.$emit('formUpdate', $scope.form);
};
// Add a new component.
$scope.$on('formBuilder:add', update);
$scope.$on('formBuilder:update', update);
$scope.$on('formBuilder:remove', update);
$scope.$on('formBuilder:edit', function() {
$scope.$broadcast('iframeMessage', {name: 'form', data: angular.copy($scope.form)});
update();
});
$scope.saveSettings = function() {
ngDialog.closeAll(true);
$scope.$broadcast('iframeMessage', {name: 'form', data: angular.copy($scope.form)});
update();
};
$scope.capitalize = _capitalize;
// Set the root list height to the height of the formbuilder for ease of form building.
var rootlistEL = angular.element('.rootlist');
var formbuilderEL = angular.element('.formbuilder');
$interval(function setRootListHeight() {
var listHeight = rootlistEL.height('inherit').height();
var builderHeight = formbuilderEL.height();
if ((builderHeight - listHeight) > 100) {
rootlistEL.height(builderHeight);
}
}, 1000);
// Add to scope so it can be used in templates
$scope.dndDragIframeWorkaround = dndDragIframeWorkaround;
$scope.showPage(0);
}
],
link: function(scope, element) {
var minHeight = 200;
var headerOffset = 50;
var bottomOffset = 15;
var scrollSidebar = function() {
// Disable all buttons within the form.
angular.element('.formbuilder').find('button').attr('disabled', 'disabled');
// Make the left column follow the form.
var formComponents = angular.element('.formcomponents');
var formBuilder = angular.element('.formbuilder');
if (formComponents.length !== 0 && formBuilder.length !== 0) {
var windowEl = angular.element(window);
var windowHeight = windowEl.height();
var windowScrollTop = windowEl.scrollTop();
var windowScrollBottom = windowScrollTop + windowHeight;
var formBuilderOffsetTop = formBuilder.offset().top;
var formBuilderHeight = formBuilder.outerHeight();
var formBuilderOffsetBottom = formBuilderOffsetTop + formBuilderHeight;
var height = 0;
if (windowHeight > formBuilderHeight) {
formComponents.css('height', formBuilderHeight);
return;
}
if (windowScrollBottom < formBuilderOffsetTop
|| windowScrollTop > formBuilderOffsetBottom) {
// Form Builder is not visible.
return;
}
else if (windowScrollTop < formBuilderOffsetTop) {
// Top part of Form Builder is visible.
height = windowScrollBottom - formBuilderOffsetTop - bottomOffset;
}
else if (windowScrollBottom < formBuilderOffsetBottom) {
// Form builder is visible.
height = windowHeight - headerOffset - bottomOffset;
}
else {
// Bottom part of Form Builder is visible.
height = formBuilderOffsetBottom - windowScrollTop - headerOffset - bottomOffset;
}
if (height < minHeight) {
height = minHeight;
}
var maxScroll = formBuilderHeight - height - bottomOffset;
var scroll = windowScrollTop - formBuilderOffsetTop + headerOffset;
if (scroll < 0) {
scroll = 0;
}
if (scroll > maxScroll) {
scroll = maxScroll;
}
// Necessary fix for header.
if (scroll > 0 && scroll < headerOffset) {
height -= scroll;
}
formComponents.css({
'margin-top': scroll,
height: height
});
}
};
window.onscroll = debounce(scrollSidebar, 100, false);
scrollSidebar();
element.on('$destroy', function() {
window.onscroll = null;
});
}
};
}];