Skip to content

Instantly share code, notes, and snippets.

@awdng
Last active April 13, 2016 15:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save awdng/34732bb436e9e05f145bee8d7b8a8381 to your computer and use it in GitHub Desktop.
Save awdng/34732bb436e9e05f145bee8d7b8a8381 to your computer and use it in GitHub Desktop.
/*
* ng formBuilder directive
* built by Arne Wieding (arne@wieding.com)
* v0.2a 2014
*/
(function(module) {
'use strict';
module.factory('formBuilderApi', function() {
return {
forms: {},
createForm: function(name) {
var newForm = {
submitted: false,
hasErrors: false,
isDirty: false,
data: {},
fields: [],
fieldsByName: {},
errors: {},
submitFail: function(allErrors) {
var self = this;
this.errors = allErrors;
angular.forEach(allErrors, function(fieldErrors, fieldName) {
self.fieldsByName[fieldName].errors = [];
angular.forEach(fieldErrors, function(value) {
self.fieldsByName[fieldName].errors.push(value.message);
});
});
this.hasErrors = true;
},
resetErrors: function() {
this.errors = {};
this.hasErrors = false;
},
submitSuccess: function() {
this.resetErrors();
angular.forEach(this.fields, function(fieldData) {
if (fieldData.dirty) {
fieldData.lastChangeSuccess = true;
fieldData.dirty = false;
}
fieldData.errors = [];
});
},
bindFormData: function(data) {
this.data = data;
},
addField: function(name, data) {
var self = this;
data.errors = [];
data.dirty = false;
data.lastChangeSuccess = false;
data.locked = false;
data.name = name;
if (!('value' in data)) {
data.value = this.data[name];
}
if (('locked' in data) && data.locked === true) {
data.locked = true;
}
if (('handler' in data)) { // register handler if passed
data.onChange = function() {
var field = {};
field[name] = self.fieldsByName[name].value;
data.handler(field);
//self.onChangeHandler(field);// fix missing request
data.dirty = true;
};
} else { // register global onChange Handler
data.onChange = function() {
var field = {};
field[name] = self.fieldsByName[name].value;
self.onChangeHandler(field);
data.dirty = true;
};
}
data.checkError = function() {
if (data.errors.length > 0) {
return true;
}
return false;
};
this.fieldsByName[name] = data;
this.fields.push(data);
},
onChangeHandler: null
};
this.forms[name] = newForm;
return this.forms[name];
},
getForm: function(name) {
return this.forms[name];
}
};
});
module.directive('formBuilder', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/form.html',
scope: {},
link: function(scope, element, attrs) {
scope.form = formBuilderApi.getForm(attrs.name);
scope.formName = attrs.name;
}
};
});
module.directive('formBuilder.rowWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/row.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.readonlyWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/readonly.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.textWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/text.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.textareaWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/textarea.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.richtextWidget', function($compile, formBuilderApi, $modal) {
var ModalCtrl = function($scope, $modalInstance, data) {
$scope.data = data;
$scope.ok = function() {
$modalInstance.close($scope.data);
};
$scope.cancel = function() {
$modalInstance.dismiss('Abbrechen');
};
};
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/richtext.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
scope.openModal = function() {
var modalInstance = $modal.open({
templateUrl: 'formbuilder/template/widgets/richtext_modal.html',
controller: ModalCtrl,
resolve: {
data: function() {
return scope.data;
}
}
});
modalInstance.result.then(function() {
scope.data.onChange();
}, function() {
//
});
};
}
};
});
module.directive('formBuilder.dateWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/date.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.timeWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/time.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.numberWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/number.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.choiceWidget', function($compile, formBuilderApi, $filter) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/choice.html',
scope: {
data: '=fieldData',
expanded: '=',
multiple: '='
},
link: function(scope, element, attrs) {
scope.selectSingle = function() {
// console.log(scope.data);
if (typeof scope.data.value == 'undefined') {//fix initial value
scope.data.value = scope.data.options[0].value;
}
var selected = $filter('filter')(scope.data.options, {value: scope.data.value});
return (scope.data.value && selected.length) ? selected[0].text : 'Keine Auswahl';
};
scope.radioList = function() {
var selected = $filter('filter')(scope.data.options, {value: scope.data.value});
return (scope.data.value && selected.length) ? selected[0].text : 'Keine Auswahl';
};
scope.checkboxList = function() {
var selected = [];
angular.forEach(scope.data.options, function(s) {
if (scope.data.value.indexOf(s.value) >= 0) {
selected.push(s.text);
}
});
return selected.length ? selected.join(', ') : 'Keine Auswahl';
};
}
};
});
module.directive('formBuilder.imageUploadWidget', function($compile, formBuilderApi, $http, $timeout, $upload) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/imageUpload.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
var app = angular.module('biologica');
scope.onFileSelect = function($files) {
for (var i = 0; i < $files.length; i++) {
var file = $files[i];
if (typeof global_company_id != 'undefined') {
//company
var uploadURL = app.getApiPath() + '/companies/' + global_company_id + '/media'; //upload.php script
} else {
//therapist
var uploadURL = app.getApiPath() + '/media';
}
$upload.upload({
url: uploadURL, //upload.php script, node.js route, or servlet url
file: file
}).progress(function(evt) {
var progr = parseInt(100.0 * evt.loaded / evt.total);
scope.dynamicProgress = progr;
}).success(function(result, status, headers, config) {
scope.dynamicProgress = 0;
//scope.data.value = result.entity.id;
scope.data.value = result.entity;
scope.data.onChange();
});
}
};
}
};
});
module.directive('formBuilder.labelWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/label.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
module.directive('formBuilder.errorWidget', function($compile, formBuilderApi) {
return {
restrict: 'E',
templateUrl: 'formbuilder/template/widgets/error.html',
scope: {
data: '=fieldData'
},
link: function(scope, element, attrs) {
//
}
};
});
})(angular.module('formBuilder', [
'textAngular',
'angularFileUpload',
'xeditable',
'ui.bootstrap',
'localytics.directives',
'formbuilder.templates'
]
).run(function(editableOptions, datepickerConfig, datepickerPopupConfig, timepickerConfig) {
editableOptions.theme = 'bs2'; // options 'bs3','bs2', 'default'
datepickerPopupConfig.showButtonBar = false;
datepickerConfig.startingDay = '1';
datepickerConfig.currentText = 'Heute';
timepickerConfig.showMeridian = false;
}
));
// initalize default templates
angular.module("formbuilder.templates", []).run(["$templateCache", function($templateCache) {
$templateCache.put("formbuilder/template/form.html",
"<li data-ng-repeat=\"data in form.fields\" ng-if=\"data.type !== 'hidden'\"> \
<form-builder.row-widget field-data='data'></form-builder-row-widget> \
</li>");
$templateCache.put("formbuilder/template/widgets/row.html",
"<form-builder.error-widget field-data='data'></form-builder.error-widget> \
<div class='input-label'> \
<form-builder.label-widget field-data='data'></form-builder.label-widget> \
</div> \
<div class='input-value' ng-class=\"{'control-group success': !data.checkError(), 'control-group error': data.checkError()}\" tooltip='{{ data.attr.placeholder }}'> \
<i ng-show='data.checkError()' class='icon status-icon icon-remove-sign'></i> \
<i ng-show='!data.checkError() && data.lastChangeSuccess' class='icon status-icon icon-ok-sign'></i> \
<form-builder.readonly-widget ng-if=\"data.type == 'readonly'\" field-data='data'></form-builder.readonly-widget> \
<form-builder.text-widget ng-if=\"data.type == 'text'\" field-data='data'></form-builder.text-widget> \
<form-builder.textarea-widget ng-if=\"data.type == 'textarea'\" field-data='data' class=\"text-area\"></form-builder.textarea-widget> \
<form-builder.richtext-widget ng-if=\"data.type == 'richtext'\" field-data='data'></form-builder.richtext-widget> \
<form-builder.date-widget ng-if=\"data.type == 'date'\" field-data='data' class=\"date-widget\"></form-builder.date-widget> \
<form-builder.time-widget ng-if=\"data.type == 'time'\" field-data='data'></form-builder.time-widget> \
<form-builder.number-widget ng-if=\"data.type == 'number'\" field-data='data'></form-builder.number-widget> \
<form-builder.image-upload-widget ng-if=\"data.type == 'imageupload'\" field-data='data'></form-builder.image-upload-widget> \
<form-builder.choice-widget ng-if=\"data.type == 'choice'\" field-data='data' expanded='data.expanded' multiple='data.multiple'></form-builder.choice-widget> \
<div class='auto-form-edit' ng-if=\"data.type != 'readonly'\"><i class='icon icon-pencil'></i>&nbsp; Bearbeiten</div> \
</div>");
$templateCache.put("formbuilder/template/widgets/text.html",
"<a href=\"#\" editable-text=\"data.value\" onaftersave=\"data.onChange()\"> \
{{ data.value || 'Leer' }} \
</a>");
$templateCache.put("formbuilder/template/widgets/textarea.html",
"<a href=\"#\" editable-textarea=\"data.value\" e-rows=\"data.rows\" e-cols=\"data.cols\" onaftersave=\"data.onChange()\"> \
{{ data.value || 'Leer' }} \
</a>");
$templateCache.put("formbuilder/template/widgets/readonly.html",
"<a class=\"editable editable-click\">{{ data.value || 'Leer'}}</a>");
$templateCache.put("formbuilder/template/widgets/richtext.html",
"<span href=\"#\" class=\"editable editable-click\" ng-click=\"openModal()\">{{ data.value || 'Leer'}}</span>");
$templateCache.put("formbuilder/template/widgets/richtext_modal.html",
"<div> \
<div class=\"modal-header message-reply-header\"> \
<button class=\"btn btn-default pull-right close-respond\" type=\"button\" ng-click=\"cancel()\"><i class=\"icon icon-remove-sign\"></i></button> \
<h3>Bearbeiten</h3> \
</div> \
<div class=\"modal-body\"> \
<div class=\"modal-content-container\">\
<text-angular text-angular-name=\"composeMessage\" placeholder=\"data.placeholder\" ng-model=\"data.value\" class=\"ta-editor\" ta-toolbar=\"[['h1','h2','h3', 'p'],['bold', 'italics', 'ul', 'ol', 'redo', 'undo', 'clear'],[ 'underline'],['justifyCenter', 'justifyLeft', 'justifyRight']]\"/> \
</div> \
</div> \
<div class=\"modal-footer\"> \
<button class=\"btn btn-success pull-left respond\" type=\"button\" ng-click=\"ok()\">OK</button> \
<button class=\"btn btn-warning pull-right respond\" type=\"button\" ng-click=\"cancel()\">Abbrechen</button> \
</div> \
</div>");
$templateCache.put("formbuilder/template/widgets/date.html",
"<a href=\"#\" editable-bsdate=\"data.value\" e-datepicker-popup=\"dd.MM.yyyy\" e-current-text=\"Heute\" onaftersave=\"data.onChange()\"> \
{{ (data.value | date:'dd.MM.yyyy') || 'Leer' }} \
</a>");
$templateCache.put("formbuilder/template/widgets/time.html",
"<a href=\"#\" editable-bstime=\"data.value\" e-show-meridan=\"true\" e-minute-step=\"1\" onaftersave=\"data.onChange()\"> \
{{ (data.value | date:\"HH:mm\") || 'Leer' }} \
</a>");
$templateCache.put("formbuilder/template/widgets/number.html",
"<a href=\"#\" editable-number=\"data.value\" onaftersave=\"data.onChange()\"> \
{{ data.value || 'Leer' }} \
</a>");
$templateCache.put("formbuilder/template/widgets/choice.html",
"<a ng-if=\"!multiple && !expanded\" href=\"#\" editable-select=\"data.value\" e-ng-options=\"s.value as s.text for s in data.options\" onaftersave=\"data.onChange()\"> \
{{ selectSingle() }} \
</a> \
\
<select ng-if=\"multiple && !expanded\" multiple chosen \
data-placeholder=\"Wählen Sie mehrere aus\" \
ng-model=\"data.value\" \
ng-options=\"s.value as s.text for s in data.options\" \
ng-change=\"data.onChange()\"> \
</select> \
\
<a ng-if=\"!multiple && expanded\" href=\"#\" editable-radiolist=\"data.value\" e-ng-options=\"s.value as s.text for s in data.options\" onaftersave=\"data.onChange()\"> \
{{ radioList() }} \
</a> \
\
<a ng-if=\"multiple && expanded\" href=\"#\" editable-checklist=\"data.value\" e-ng-options=\"s.value as s.text for s in data.options\" onaftersave=\"data.onChange()\"> \
{{ checkboxList() }} \
</a>");
$templateCache.put("formbuilder/template/widgets/imageUpload.html",
"<input type=\"hidden\" ng-model=\"data.value.id\"> \
<input style=\"margin-left:10px;margin-top:-3px;\" type=\"file\" ng-file-select=\"onFileSelect($files)\" class=\"btn btn-link btn-small\" /> \
<span> \
<img ng-src='{{ data.value.url }}'/> \
</span> \
\
<div class=\"progress progress-striped active\" ng-show=\"dynamicProgress > 0\" style=\"margin: 20px;\"> \
<div class=\"bar\" style=\"width: {{dynamicProgress}}%;\"></div> \
</div>");
$templateCache.put("formbuilder/template/widgets/label.html",
"{{ data.label }}");
$templateCache.put("formbuilder/template/widgets/error.html",
"<div ng-show=\"data.checkError()\" class=\"alert alert-error\"> \
<i class=\"icon icon-remove-sign\"></i>&nbsp;<a class=\"close\" data-dismiss=\"alert\">×</a> \
<span ng-repeat=\"error in data.errors\">{{ error }}</span> \
</div>");
}]);
angular.module('company').controller('CompanyProfileCtrl', function($scope, $routeParams, Restangular, ngTableParams, $filter, $location, $q, $upload, formBuilderApi) {
var app = angular.module('app');
var init = function() {
Restangular.one('companies/' + global_company_id).get().then(function (company) {
// bind the company model data to the form
$scope.companyForm.bindFormData(company);
// define form fields
$scope.companyForm.addField('name', {
label: 'Name',
type: 'text'
});
$scope.companyForm.addField('street', {
label: 'Strasse',
type: 'text'
});
$scope.companyForm.addField('short_description', {
label: 'Beschreibung',
type: 'textarea',
handler : function(field){
$scope.companyForm.onChangeHandler({shortDescription:field.short_description});
}
});
$scope.companyForm.addField('zipcode', {
label: 'Postleitzahl',
type: 'text'
});
$scope.companyForm.addField('city', {
label: 'Stadt',
type: 'choice',
options: [
{
value: 'Leipzig',
text: 'Leipzig'
},
{
value: 'Berlin',
text: 'Berlin'
},
{
value: 'München',
text: 'München'
}
],
expanded: false,
multiple: false
});
$scope.companyForm.addField('phone', {
label: 'Telefon',
type: 'text'
});
$scope.companyForm.addField('fax', {
label: 'Fax',
type: 'text'
});
$scope.companyForm.addField('web', {
label: 'Internet',
type: 'text'
});
$scope.companyForm.addField('email', {
label: 'Email',
type: 'text'
});
$scope.companyForm.addField('logo', {
label: 'Logo',
type: 'imageupload'
});
});
};
// create new form Object
$scope.companyForm = formBuilderApi.createForm('companyForm');
// register form field save handler
$scope.companyForm.onChangeHandler = function(fieldData) {
Restangular.one('companies/' + global_company_id).patch(fieldData).then(function () {
$scope.companyForm.submitSuccess();
}, function(response) {
$scope.companyForm.submitFail(response.data);
});
};
// init controller
init.apply(this, arguments);
});
<div>
<form-builder name="companyForm"></form-builder>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment