Demo drag-drop form-builder with native html5 drag drop and anglar ui sortable directive.
A Pen by Lam Phuoc Thinh on CodePen.
Demo drag-drop form-builder with native html5 drag drop and anglar ui sortable directive.
A Pen by Lam Phuoc Thinh on CodePen.
<html lang="en" ng-app="DragDropApp"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js" type="text/javascript"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js" type="text/javascript"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular-animate.js" type="text/javascript"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/374704/sortable.js"></script> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" /> | |
<title>Drag drop form builder</title> | |
</head> | |
<body ng-controller="DragDropCtrl"> | |
<div class="container-fluid"> | |
<div class="row"> | |
<div class="col-md-3" id="sideBar"> | |
<div class="tab-container text-center"> | |
<ul class="neo-nav clearfix" role="tablist"> | |
<li role="presentation" class="active"><a href="#addFieldTab" id="addFieldTab_lnk" aria-controls="home" role="tab" data-toggle="tab">Add a Field</a></li><!-- | |
--><li role="presentation"><a href="#fieldSettingTab" id="fieldSettingTab_lnk" aria-controls="profile" role="tab" data-toggle="tab">Field Settings</a></li> | |
</ul> | |
</div> | |
<div class="tab-content" id="sidebar-tab-content"> | |
<div role="tabpanel" class="tab-pane active" id="addFieldTab"> | |
<p> | |
<a role="button" data-toggle="collapse" href="#stdFields"> | |
<i class="fa fa-lg fa-plus-square-o"></i><i class="fa fa-lg fa-minus-square-o"></i> STANDARD FIELDS | |
</a> | |
</p> | |
<div class="collapse in" id="stdFields"> | |
<ul ng-model="dragElements" class="sortable-stdFields"> | |
<li draggable="true" class="dragElement-wrapper" ng-repeat="ele in dragElements" element-draggable data-index="{{$index}}"> | |
<div class="drag-element" ng-click="addElement(ele)" > | |
<i class="fa fa-cogs"></i> {{ele.Name}} | |
</div> | |
</li> | |
</ul> | |
</div> | |
</div> | |
<div role="tabpanel" class="tab-pane" id="fieldSettingTab"> | |
<div ng-repeat="set in current_field.Settings" ng-switch on="set.Type"> | |
<div > | |
<div class="form-group " ng-switch-when="text"> | |
<label for="{{set.Name.replace(' ','_')}}">{{set.Name}}</label> | |
<input ng-change="current_field.ChangeFieldSetting(set.Value, set.Name)" type="text" ng-model="set.Value" class="form-control" id="{{set.Name.replace(' ','_')}}" value="{{set.Value}}" placeholder="{{set.Name}}"> | |
</div> | |
<div class="form-group" ng-switch-when="string"> | |
<label >{{set.Name}}</label> | |
<br> | |
<span >{{set.Value}}</span> | |
</div> | |
<div class="form-group" ng-switch-when="label"> | |
<label class="pale">{{set.Name}}</label> | |
</div> | |
<div class="form-group " ng-switch-when="dropdown" > | |
<label >{{set.Name}}</label> | |
<select class="form-control" ng-model="set.Value" > | |
<option ng-repeat="op in set.PossibleValue">{{op}}</option> | |
</select> | |
</div> | |
<div class="form-group" ng-switch-when="radio"> | |
<div ng-repeat="val in set.PossibleValue" class="radio"> | |
<label> | |
<input type="radio" name="optionsRadios" value="{{val.Checked}}" ng-checked="val.Checked"> | |
{{val.Text}} | |
</label> | |
</div> | |
</div> | |
<div class="form-group" ng-switch-when="dropdown_increment"> | |
<label class="control-label"> | |
Choices | |
</label> | |
<div ng-repeat="val in set.PossibleValue" class="radio" class="form-control"> | |
<i class="fa fa-sort fa-lg"> | |
</i> | |
| |
<i class="fa fa-circle-o fa-lg"> | |
</i> | |
<input type="text" value="val.Text" ng-model="val.Text"> | |
</div> | |
</div> | |
<div ng-switch-when="checkBoxZone"> | |
<a role="button" data-toggle="collapse" href="#chkBoxZone"> | |
<i class="fa fa-lg fa-plus-square-o"></i><i class="fa fa-lg fa-minus-square-o"></i> {{set.Name}} | |
</a> | |
<div class="collapse in" id="chkBoxZone"> | |
<div class="form-group" ng-repeat="op in set.Options"> | |
<label class="checkbox-inline" > | |
<input type="checkbox" value="{{op.Value}}" ng-model="op.Value"> {{op.Name}} | |
</label> | |
</div> | |
</div> | |
</div> | |
<p ng-switch-default> | |
Unknown | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="col-md-9 text-center" id="main-content"> | |
<ul class="neo-nav" role="tablist"> | |
<li role="presentation" class="active"><a href="#addFieldTab" aria-controls="home" role="tab" data-toggle="tab">Form Builder</a></li><!-- | |
--><li role="presentation"><a href="#" aria-controls="profile" role="tab" data-toggle="tab">Entity & Field Permission</a></li><!-- | |
--><li role="presentation" class=""><a href="#" aria-controls="home" role="tab" data-toggle="tab">Detail Builder</a></li><!-- | |
--><li role="presentation"><a href="#" aria-controls="profile" role="tab" data-toggle="tab">Workflows</a></li> | |
</ul> | |
<div class="tab-content"> | |
<div role="tabpanel" class="tab-pane active text-left" id="formBuilderContent"> | |
<p> | |
<i class="fa fa-pencil fa-lg"></i> | |
<span class="lead">Employee</span> | |
</p> | |
<div class="container-fluid" id="dropZone"> | |
<ul class="row sortable-formbuilder" element-drop ui-sortable="formbuilderSortableOpts" ng-model="formFields" id="sortable-formbuilder-ul"> | |
<li ng-repeat="field in formFields" element-drop data-index="{{$index}}" ng-switch on="field.Type" ng-class="field.GetFieldSetting('Column Span').Value == 1 ? 'col-md-6 sortable-field' : 'col-md-12 sortable-field' " ng-click="activeField(field)" data-index="{{$index}}"> | |
<div class="form-group " ng-switch-when="text" ng-class="field.Active ? 'active-field' : '' " > | |
<input type="text" class="input-as-label" ng-model="field.Name" value="{{field.Name + (field.GetFieldSetting('Required') ? '*': '')}}" ng-change="field.ChangeFieldSetting(field.Name,'Field Label')"/> | |
<span ng-if="field.GetFieldSetting('Required').Value" class="orange-txt">*</span> | |
<!--<label ng-if="!field.Active" for="{{field.Name.replace(' ','_') + field.id}}">{{field.Name}}<span ng-if="field.GetFieldSetting('Required')">*</span></label> --> | |
<input type="text" class="form-control" id="{{field.Name.replace(' ','_') + field.id}}" value="{{field.Value}}" placeholder="{{field.Name}}"> | |
<i class ="fa fa-lg fa-minus-square-o remove-ico" ng-click="removeElement($index)" ng-if="field.Active"></i> | |
</div> | |
<div class="form-group k" ng-switch-when="date" ng-class="field.Active ? 'active-field' : '' "> | |
<input type="text" class="input-as-label" ng-model="field.Name" value="{{field.Name + (field.GetFieldSetting('Required').Value ? '*': '')}}" ng-change="field.ChangeFieldSetting(field.Name,'Field Label')"/> | |
<span ng-if="field.GetFieldSetting('Required').Value" class="orange-txt">*</span> | |
<div class="has-feedback"> | |
<input type="text" class="form-control" placeholder="{{field.Name}}"> | |
<span class="glyphicon glyphicon-calendar form-control-feedback custom-feedback" aria-hidden="true"></span> | |
<span id="inputSuccess2Status" class="sr-only">(success)</span> | |
</div> | |
<i class ="fa fa-lg fa-minus-square-o remove-ico" ng-if="field.Active" ng-click="removeElement($index)"></i> | |
</div> | |
<div class="form-group " ng-switch-when="dropdown" ng-class="field.Active ? 'active-field' : '' "> | |
<input type="text" class="input-as-label" ng-model="field.Name" value="{{field.Name + (field.GetFieldSetting('Required').Value ? '*': '')}}" ng-change="field.ChangeFieldSetting(field.Name,'Field Label')"/> | |
<span ng-if="field.GetFieldSetting('Required').Value" class="orange-txt">*</span> | |
<select class="form-control" > | |
<option ng-repeat="val in field.GetFieldSetting('Choice').PossibleValue"> | |
{{val.Text}} | |
</option> | |
</select> | |
<i class ="fa fa-lg fa-minus-square-o remove-ico" ng-if="field.Active" ng-click="removeElement($index)"></i> | |
</div> | |
<div class="form-group " ng-switch-when="textarea" ng-class="field.Active ? 'active-field' : '' "> | |
<input type="text" class="input-as-label" ng-model="field.Name" value="{{field.Name + (field.GetFieldSetting('Required').Value ? '*': '')}}" ng-change="field.ChangeFieldSetting(field.Name,'Field Label')"/> | |
<span ng-if="field.GetFieldSetting('Required').Value" class="orange-txt">*</span> | |
<textarea class="form-control" id="{{field.Name.replace(' ','_') + field.id}}" rows="4"></textarea> | |
<i class ="fa fa-lg fa-minus-square-o remove-ico" ng-if="field.Active" ng-click="removeElement($index)"></i> | |
</div> | |
<div ng-switch-default> | |
</div> | |
</li> | |
<li class="drop-to-add col-md-12" element-drop> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</body> | |
</html> |
/** | |
* Created by thinhlam on 10/16/15. | |
*/ | |
(function() { | |
var guid = 1; | |
var app = angular.module('DragDropApp', ['ui.sortable']); | |
app.controller('DragDropCtrl', function($scope) { | |
$scope.dragElements = [{ | |
'Name': "Single Text", | |
'Type': "text", | |
'Settings': [{ | |
'Name': 'Field Label', | |
'Value': 'Single Text', | |
'Type': 'text' | |
}, { | |
'Name': 'Short Label', | |
'Value': 'Single Text', | |
'Type': 'text' | |
}, { | |
'Name': 'Internal Name', | |
'Value': 'xSingle_Text', | |
'Type': 'text' | |
}, { | |
'Name': 'Field Type', | |
'Value': 'Single Text', | |
'Type': 'string' | |
}, { | |
'Name': 'Single Line Text Options', | |
'Value': '', | |
'Type': 'label' | |
}, { | |
'Name': 'Max Input Length', | |
'Value': '50', | |
'Type': 'text' | |
}, { | |
'Name': 'Url Template', | |
'Value': '', | |
'Type': 'text' | |
}, { | |
'Name': 'Column Span', | |
'Value': '1', | |
'Type': 'dropdown', | |
'PossibleValue': ['1', '2'] | |
}, { | |
'Name': 'General Options', | |
'Type': 'checkBoxZone', | |
'Options': [{ | |
'Name': 'Required', | |
'Value': false | |
}, { | |
'Name': 'Show on list', | |
'Value': false | |
}, { | |
'Name': 'Unique', | |
'Value': false | |
}, { | |
'Name': 'Index', | |
'Value': false | |
}] | |
} | |
] | |
}, { | |
'Name': "Date", | |
'Type': "date", | |
'Settings': [{ | |
'Name': 'Field Label', | |
'Value': 'Field Label', | |
'Type': 'text' | |
}, { | |
'Name': 'Short Label', | |
'Value': 'Short Label', | |
'Type': 'text' | |
}, { | |
'Name': 'Internal Name', | |
'Value': 'Internal Name', | |
'Type': 'text' | |
}, { | |
'Name': 'Field Type', | |
'Value': 'Date', | |
'Type': 'string' | |
}, { | |
'Name': 'Display Type', | |
'Value': '', | |
'Type': 'radio', | |
'PossibleValue': [ | |
{ | |
'Text' : 'DateTimeInstance', | |
'Checked' : true | |
}, | |
{ | |
'Text' : 'DateTimeLocal', | |
'Checked' : false | |
}, | |
{ | |
'Text' : 'DateLocal', | |
'Checked' : false | |
}, | |
{ | |
'Text' : 'Time', | |
'Checked' : false | |
}, | |
] | |
}, { | |
'Name': 'Column Span', | |
'Value': '1', | |
'Type': 'dropdown', | |
'PossibleValue': ['1', '2'] | |
}, { | |
'Name': 'General Options', | |
'Type': 'checkBoxZone', | |
'Options': [{ | |
'Name': 'Required', | |
'Value': false | |
}, { | |
'Name': 'Show on list', | |
'Value': false | |
}, { | |
'Name': 'Unique', | |
'Value': false | |
}, { | |
'Name': 'Index', | |
'Value': false | |
}] | |
} | |
] | |
}, { | |
'Name': "Singe Selection", | |
"Type": "dropdown", | |
'Settings': [{ | |
'Name': 'Field Label', | |
'Value': 'Field Label', | |
'Type': 'text' | |
}, { | |
'Name': 'Short Label', | |
'Value': '', | |
'Type': 'text' | |
}, { | |
'Name': 'Internal Name', | |
'Value': 'Short Label', | |
'Type': 'text' | |
}, { | |
'Name': 'Field Type', | |
'Value': 'Single Selection', | |
'Type': 'string' | |
}, { | |
'Name': 'Display Type', | |
'Value': '', | |
'Type': 'radio', | |
'PossibleValue': [ | |
{ | |
'Text' : 'Dropdown', | |
'Checked' : true | |
}, | |
{ | |
'Text' : 'Radio List', | |
'Checked' : false | |
} | |
] | |
}, { | |
'Name': 'Choice', | |
'Type': 'dropdown_increment', | |
'PossibleValue': [ | |
{ | |
'Text':'Choice 1', | |
} | |
, { | |
'Text': 'Choice 2' | |
} | |
] | |
}, { | |
'Name': 'Column Span', | |
'Value': '1', | |
'Type': 'dropdown', | |
'PossibleValue': ['1', '2'] | |
}, { | |
'Name': 'General Options', | |
'Type': 'checkBoxZone', | |
'Options': [{ | |
'Name': 'Required', | |
'Value': false | |
}, { | |
'Name': 'Show on list', | |
'Value': false | |
}, { | |
'Name': 'Unique', | |
'Value': false | |
}, { | |
'Name': 'Index', | |
'Value': false | |
}] | |
} | |
] | |
}, { | |
'Name': "Pagaraph Text", | |
"Type": "textarea", | |
'Settings': [{ | |
'Name': 'Field Label', | |
'Value': '', | |
'Type': 'text' | |
}, { | |
'Name': 'Short Label', | |
'Value': '', | |
'Type': 'text' | |
}, { | |
'Name': 'Internal Name', | |
'Value': '', | |
'Type': 'text' | |
}, { | |
'Name': 'Field Type', | |
'Value': 'Paragraph Text', | |
'Type': 'string' | |
}, { | |
'Name': 'Column Span', | |
'Value': '1', | |
'Type': 'dropdown', | |
'PossibleValue': ['1', '2'] | |
}, { | |
'Name': 'General Options', | |
'Type': 'checkBoxZone', | |
'Options': [{ | |
'Name': 'Required', | |
'Value': false | |
}, { | |
'Name': 'Enable Rich Text', | |
'Value': false | |
}, { | |
'Name': 'Active', | |
'Value': true | |
}, { | |
'Name': 'Hidden', | |
'Value': false | |
}] | |
} | |
] | |
}]; | |
$scope.formFields = []; | |
$scope.current_field = {}; | |
var createNewField = function() { | |
return { | |
'id': ++guid, | |
'Name': '', | |
'Settings': [], | |
'Active': true, | |
'ChangeFieldSetting': function(Value, SettingName) { | |
switch (SettingName) { | |
case 'Field Label': | |
case 'Short Label': | |
case 'Internal Name': | |
$scope.current_field.Name = Value; | |
$scope.current_field.Settings[0].Value = $scope.current_field.Name; | |
$scope.current_field.Settings[1].Value = $scope.current_field.Name; | |
$scope.current_field.Settings[2].Value = 'x' + $scope.current_field.Name.replace(/\s/g, '_'); | |
break; | |
default: | |
break; | |
} | |
}, | |
'GetFieldSetting': function(settingName) { | |
var result = {}; | |
var settings = this.Settings; | |
$.each(settings, function(index, set) { | |
if (set.Name == settingName) { | |
result = set; | |
return; | |
} | |
}); | |
if (!Object.keys(result).length) { | |
//Continue to search settings in the checkbox zone | |
$.each(settings[settings.length - 1].Options, function(index, set) { | |
if (set.Name == settingName) { | |
result = set; | |
return; | |
} | |
}); | |
} | |
return result; | |
} | |
}; | |
} | |
$scope.changeFieldName = function(Value) { | |
$scope.current_field.Name = Value; | |
$scope.current_field.Settings[0].Value = $scope.current_field.Name; | |
$scope.current_field.Settings[1].Value = $scope.current_field.Name; | |
$scope.current_field.Settings[2].Value = 'x' + $scope.current_field.Name.replace(/\s/g, '_'); | |
} | |
$scope.removeElement = function(idx){ | |
if($scope.formFields[idx].Active) { | |
$('#addFieldTab_lnk').tab('show'); | |
$scope.current_field = {}; | |
} | |
$scope.formFields.splice(idx, 1); | |
}; | |
$scope.addElement = function(ele, idx) { | |
$scope.current_field.Active = false; | |
$scope.current_field = createNewField(); | |
//Merge setting from template object | |
angular.merge($scope.current_field, ele); | |
if (typeof idx == 'undefined') { | |
$scope.formFields.push($scope.current_field); | |
} else { | |
$scope.formFields.splice(idx, 0, $scope.current_field); | |
$('#fieldSettingTab_lnk').tab('show'); | |
} | |
}; | |
$scope.activeField = function(f) { | |
$scope.current_field.Active = false; | |
$scope.current_field = f; | |
f.Active = true; | |
$('#fieldSettingTab_lnk').tab('show'); | |
}; | |
$scope.formbuilderSortableOpts = { | |
'ui-floating': true, | |
}; | |
}); | |
app.directive('elementDraggable', ['$document', function($document) { | |
return { | |
link: function(scope, element, attr) { | |
element.on('dragstart', function(event) { | |
event.originalEvent.dataTransfer.setData('templateIdx', $(element).data('index')); | |
}); | |
} | |
}; | |
}]); | |
app.directive('elementDrop', ['$document', function($document) { | |
return { | |
link: function(scope, element, attr) { | |
element.on('dragover', function(event) { | |
event.preventDefault(); | |
}); | |
$('.drop').on('dragenter', function(event) { | |
event.preventDefault(); | |
}) | |
element.on('drop', function(event) { | |
event.stopPropagation(); | |
var self = $(this); | |
scope.$apply(function() { | |
var idx = event.originalEvent.dataTransfer.getData('templateIdx'); | |
var insertIdx = self.data('index') | |
scope.addElement(scope.dragElements[idx], insertIdx); | |
}); | |
}); | |
} | |
}; | |
}]); | |
})(); | |
$(function() { | |
// Code here | |
var dh = $(document).height(); | |
$('#sidebar-tab-content').height(dh - 115); | |
$('#main-content').height(dh - 10); | |
}); |
body { | |
padding-top: 20px; | |
color: #75736f; | |
overflow: hidden; | |
} | |
::-webkit-scrollbar { | |
display: none; | |
} | |
a { | |
color: #75736f; | |
} | |
a:focus, | |
a:hover, | |
a:visited, | |
a:active, | |
a:link { | |
text-decoration: none !important; | |
color: #75736f; | |
} | |
ul.neo-nav { | |
border: 2px solid #75736f; | |
display: inline-block; | |
-moz-padding-start: 0px; | |
-webkit-padding-start: 0px; | |
padding-start: 0px; | |
border-radius: 5px; | |
} | |
ul.neo-nav li { | |
display: inline-block; | |
list-style: none; | |
} | |
ul.neo-nav li a { | |
color: #75736f; | |
padding: 10px 30px; | |
font-weight: bold; | |
display: block; | |
} | |
ul.neo-nav li.active a { | |
color: white; | |
} | |
ul.neo-nav li:not(:first-child) { | |
border-left: 2px solid #75736f; | |
} | |
ul.neo-nav li.active { | |
background-color: #75736f; | |
color: white; | |
} | |
ul.neo-nav li a:hover, | |
ul.neo-nav li a:visited, | |
ul.neo-nav li a:active, | |
ul.neo-nav li a:link { | |
text-decoration: none; | |
} | |
#main-content { | |
background-color: whitesmoke; | |
border-left: 1px solid #ddd; | |
padding-top: 20px; | |
padding-bottom: 20px; | |
z-index: 1; | |
overflow-y: scroll; | |
} | |
#sideBar { | |
padding-top: 20px; | |
padding-left: 0px; | |
padding-right: 0px; | |
z-index: 2; | |
} | |
.tab-container { | |
border-bottom: 1px solid gray; | |
padding-bottom: 20px; | |
} | |
#addFieldTab { | |
padding-top: 20px; | |
padding-left: 20px; | |
} | |
#fieldSettingTab { | |
padding: 20px; | |
} | |
#stdFields {} | |
#formBuilderContent { | |
background-color: white; | |
padding: 20px; | |
} | |
.dragElement-wrapper { | |
width: 50%; | |
display: inline-block; | |
margin-bottom: 20px; | |
} | |
.dragElement-wrapper .drag-element { | |
display: block; | |
width: 90%; | |
padding: 10px; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
cursor: pointer; | |
} | |
.dragElement-wrapper .drag-element i { | |
margin-right: 5px; | |
} | |
[draggable] { | |
-moz-user-select: none; | |
-khtml-user-select: none; | |
-webkit-user-select: none; | |
user-select: none; | |
/* Required to make elements draggable in old WebKit */ | |
-khtml-user-drag: element; | |
-webkit-user-drag: element; | |
} | |
#chkBoxZone { | |
margin-top: 15px; | |
} | |
.input-as-label { | |
border: none; | |
box-shadow: none; | |
display: inline-block; | |
max-width: 100%; | |
margin-bottom: 5px; | |
font-weight: 700; | |
background-color: transparent; | |
} | |
.active-field { | |
background-color: #f1fafc; | |
} | |
.form-group { | |
padding: 10px; | |
position: relative; | |
} | |
.form-group i.remove-ico { | |
position: absolute; | |
right: 5px; | |
top: 5px; | |
cursor: pointer; | |
} | |
.pale { | |
opacity: 50%; | |
} | |
.sortable-formbuilder, | |
.sortable-stdFields { | |
padding-left: 0px; | |
-moz-padding-start: 0px; | |
-webkit-padding-start: 0px; | |
-khtml-padding-start: 0px; | |
-o-padding-start: 0px; | |
padding-start: 0px; | |
list-style: none; | |
min-height: 40px; | |
} | |
.drop-to-add { | |
height: 40px; | |
} | |
a[data-toggle="collapse"] i.fa-plus-square-o { | |
display: none; | |
} | |
a[data-toggle="collapse"] i.fa-minus-square-o { | |
display: inline-block; | |
} | |
a[data-toggle="collapse"].collapsed i.fa-plus-square-o { | |
display: inline-block; | |
} | |
a[data-toggle="collapse"].collapsed i.fa-minus-square-o { | |
display: none; | |
} | |
.orange-txt { | |
color: orange; | |
} | |
#sidebar-tab-content{ | |
overflow-y: scroll; | |
} |