<!doctype html>
<html ng-app="formlyExample">
<head>
<!-- script src="https://cdn.rawgit.com/zhaber/datetimepicker/master/datetimepicker.js"></script -->
<link href="https://cdn.rawgit.com/zhaber/datetimepicker/master/datetimepicker.css" type="text/css" rel="stylesheet">
<!-- Twitter bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel="stylesheet">
<!-- apiCheck is used by formly to validate its api -->
<script src="//npmcdn.com/api-check"></script>
<!-- This is the latest version of angular (at the time this template was created) -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<!-- Angular-UI Bootstrap has tabs directive we want -->
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.1/ui-bootstrap-tpls.min.js"></script>
<!-- This is the latest version of formly core. -->
<script src="//npmcdn.com/angular-formly"></script>
<!-- This is the latest version of formly bootstrap templates -->
<script src="//npmcdn.com/angular-formly-templates-bootstrap"></script>
<!-- This is the latest version of angular-js-bootstrap-datetimepicker (at the time this template was created) -->
<script src="datetimepicker.js"></script>
<script src="example.js"></script>
<title>Angular Formly Example</title>
</head>
<body ng-controller="MainCtrl as vm">
<div class="container">
<h1>angular-formly example: {{vm.exampleTitle}}</h1>
<div>
This is a very basic example of how to use the <a hreh="angular-js-bootstrap-datetimepicker">UI Boostrap date and time picker</a> with angular-formly.
</div>
<hr />
<form ng-submit="vm.onSubmit()" name="vm.form" novalidate>
<formly-form model="vm.model" fields="vm.fields" options="vm.options" form="vm.form">
<button type="submit" class="btn btn-primary submit-button" ng-disabled="vm.form.$invalid">Submit</button>
<button type="button" class="btn btn-default" ng-click="vm.options.resetModel()">Reset</button>
</formly-form>
</form>
<hr />
<h2>Model Value</h2>
<pre>{{vm.model | json}}</pre>
<h2>Fields <small>(note, functions are not shown)</small></h2>
<pre>{{vm.originalFields | json}}</pre>
<h2>Form</h2>
<pre>{{vm.form | json}}</pre>
</div>
<div style="margin-top:30px">
<small>
This is an example for the
<a href="https://github.com/formly-js/angular-formly">angular-formly</a>
project made with ♥ by
<strong>
<span ng-if="!vm.author.name || !vm.author.url">
{{vm.author.name || 'anonymous'}}
</span>
<a ng-if="vm.author.url" ng-href="{{::vm.author.url}}">
{{vm.author.name}}
</a>
</strong>
<br />
This example is running angular version "{{vm.env.angularVersion}}" and formly version "{{vm.env.formlyVersion}}"
</small>
</div>
</body>
</html>
/* global angular */
(function() {
'use strict';
console.clear();
var app = angular.module('formlyExample', ['formly', 'formlyBootstrap', 'ui.bootstrap', 'ui.bootstrap.datetimepicker']);
app.run(function(formlyConfig) {
var ngModelAttrs = {};
var attributes = [
'ng-model',
'min-date',
'max-date',
'date-disabled',
'day-format',
'month-format',
'year-format',
'year-range',
'day-header-format',
'day-title-format',
'month-title-format',
'date-format',
'date-options',
'hour-step',
'minute-step',
'show-meridian',
'meridians',
'readonly-time',
'readonly-date',
'hidden-time',
'hidden-date',
'mousewheel',
'show-spinners',
'current-text',
'clear-text',
'close-text'
];
var bindings = [
'ng-model',
'min-date',
'max-date',
'date-disabled',
'day-format',
'month-format',
'year-format',
'year-range',
'day-header-format',
'day-title-format',
'month-title-format',
'date-format',
'date-options',
'hour-step',
'minute-step',
'show-meridian',
'readonly-time',
'readonly-date',
'hidden-time',
'hidden-date'
];
angular.forEach(attributes, function(attr) {
ngModelAttrs[camelize(attr)] = {
attribute: attr
};
});
angular.forEach(bindings, function(binding) {
ngModelAttrs[camelize(binding)] = {
bound: binding
};
});
function camelize(string) {
string = string.replace(/[\-_\s]+(.)?/g,
function(match, chr) {
return chr ? chr.toUpperCase() : '';
});
// Ensure 1st char is always lowercase
return string.replace(/^([A-Z])/, function(match, chr) {
return chr ? chr.toLowerCase() : '';
});
}
formlyConfig.setType({
name: 'datetimepicker',
template: '<br><datetimepicker ng-model="model[options.key]" show-spinners="true" date-format="M/d/yyyy" date-options="dateOptions"></datetimepicker>',
wrapper: ['bootstrapLabel'],
defaultOptions: {
ngModelAttrs: ngModelAttrs,
templateOptions: {
label: 'Time',
minDate: '04/01/2016'
}
}
});
});
app.controller('MainCtrl', function MainCtrl(formlyVersion) {
var vm = this;
// funcation assignment
vm.onSubmit = onSubmit;
// variable assignment
vm.author = { // optionally fill in your info below :-)
name: 'Mark Ostrander',
url: 'https://github.com/ostranme'
};
vm.exampleTitle = 'Angular UI Bootstrap Date Time Picker'; // add this
vm.env = {
angularVersion: angular.version.full,
formlyVersion: formlyVersion
};
vm.model = {};
vm.options = {};
vm.fields = [
{
key: 'datetime',
type: 'datetimepicker',
templateOptions: {
label: 'Date and Time Picker',
datepickerPopup: 'dd-MMMM-yyyy'
},
expressionProperties: {
'templateOptions.hiddenTime': 'model.hide_time',
'templateOptions.hiddenDate': 'model.hide_date'
}
}, {
key: 'hide_time',
type: 'checkbox',
'templateOptions': {
label: 'Hide Time'
}
},{
key: 'hide_date',
type: 'checkbox',
'templateOptions': {
label: 'Hide Date'
}
}
];
vm.originalFields = angular.copy(vm.fields);
// function definition
function onSubmit() {
vm.options.updateInitialValue();
alert(JSON.stringify(vm.model), null, 2);
}
});
})();
angular.module('ui.bootstrap.datetimepicker', ["ui.bootstrap.dateparser", "ui.bootstrap.datepicker", "ui.bootstrap.timepicker"])
.directive('datepickerPopup', function () {
return {
restrict: 'EAC',
require: 'ngModel',
link: function (scope, element, attr, controller) {
//remove the default formatter from the input directive to prevent conflict
controller.$formatters.shift();
}
}
})
.directive('datetimepicker', [
function () {
if (angular.version.full < '1.4.0') {
return {
restrict: 'EA',
template: "<div class=\"alert alert-danger\">Angular 1.4.0 or above is required for datetimepicker to work correctly</div>"
};
}
return {
restrict: 'EA',
require: 'ngModel',
scope: {
ngModel: '=',
dayFormat: "=",
monthFormat: "=",
yearFormat: "=",
dayHeaderFormat: "=",
dayTitleFormat: "=",
monthTitleFormat: "=",
yearRange: "=",
minDate: "=",
maxDate: "=",
dateOptions: "=?",
dateDisabled: "&",
dateNgClick: "&",
hourStep: "=",
dateOpened: "=",
minuteStep: "=",
showMeridian: "=",
meredians: "=",
mousewheel: "=",
readonlyTime: "=",
readonlyDate: "=",
hiddenTime: "=",
hiddenDate: "="
},
template: function (elem, attrs) {
function dashCase(name) {
return name.replace(/[A-Z]/g, function (letter, pos) {
return (pos ? '-' : '') + letter.toLowerCase();
});
}
function createAttr(innerAttr, dateTimeAttrOpt) {
var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
if (attrs[dateTimeAttr]) {
return dashCase(innerAttr) + "=\"" + dateTimeAttr + "\" ";
} else {
return '';
}
}
function createFuncAttr(innerAttr, funcArgs, dateTimeAttrOpt, defaultImpl) {
var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
if (attrs[dateTimeAttr]) {
return dashCase(innerAttr) + "=\"" + dateTimeAttr + "({" + funcArgs + "})\" ";
} else {
return angular.isDefined(defaultImpl) ? dashCase(innerAttr) + "=\"" + defaultImpl + "\"" : "";
}
}
function createEvalAttr(innerAttr, dateTimeAttrOpt) {
var dateTimeAttr = angular.isDefined(dateTimeAttrOpt) ? dateTimeAttrOpt : innerAttr;
if (attrs[dateTimeAttr]) {
return dashCase(innerAttr) + "=\"" + attrs[dateTimeAttr] + "\" ";
} else {
return dashCase(innerAttr) + " ";
}
}
function createAttrConcat(previousAttrs, attr) {
return previousAttrs + createAttr.apply(null, attr)
}
var dateTmpl = "<div class=\"datetimepicker-wrapper\">" +
"<input class=\"form-control\" type=\"text\" " +
"name=\"datepicker\"" +
"ng-change=\"date_change($event)\" " +
"is-open=\"innerDateOpened\" " +
"datepicker-options=\"dateOptions\" " +
"uib-datepicker-popup=\"{{dateFormat}}\"" +
"ng-model=\"ngModel\" " + [
["dayFormat"],
["monthFormat"],
["yearFormat"],
["dayHeaderFormat"],
["dayTitleFormat"],
["monthTitleFormat"],
["yearRange"],
["ngHide", "hiddenDate"],
["ngDisabled", "readonlyDate"]
].reduce(createAttrConcat, '') +
createFuncAttr("ngClick",
"$event: $event, opened: opened",
"dateNgClick",
"open($event)") +
createEvalAttr("currentText", "currentText") +
createEvalAttr("clearText", "clearText") +
createEvalAttr("datepickerAppendToBody", "datepickerAppendToBody") +
createEvalAttr("closeText", "closeText") +
createEvalAttr("placeholder", "placeholder") +
"/>\n" +
"</div>\n";
var timeTmpl = "<div class=\"datetimepicker-wrapper\" name=\"timepicker\" ng-model=\"time\" ng-change=\"time_change()\" style=\"display:inline-block\">\n" +
"<uib-timepicker " + [
["hourStep"],
["minuteStep"],
["showMeridian"],
["meredians"],
["mousewheel"],
["min", "minDate"],
["max", "maxDate"],
["ngHide", "hiddenTime"],
["ngDisabled", "readonlyTime"]
].reduce(createAttrConcat, '') +
createEvalAttr("showSpinners", "showSpinners") +
"></timepicker>\n" +
"</div>";
// form is isolated so the directive is registered as one component in the parent form (not date and time)
var tmpl = "<ng-form name=\"datetimepickerForm\" isolate-form>" + dateTmpl + timeTmpl + "</ng-form>";
return tmpl;
},
controller: ['$scope', '$attrs',
function ($scope, $attrs) {
$scope.createDateOptionsWatch = function(dateAttr) {
$scope.$watch(dateAttr, function (value) {
var date = new Date(value);
$scope.dateOptions[dateAttr] = date;
if ($scope[dateAttr]) {
$scope[dateAttr] = date;
}
}, true);
}
$scope.date_change = function () {
// If we changed the date only, set the time (h,m) on it.
// This is important in case the previous date was null.
// This solves the issue when the user set a date and time, cleared the date, and chose another date,
// and then, the time was cleared too - which is unexpected
var time = $scope.time;
if ($scope.ngModel && $scope.time) { // if ngModel is null, that's because the user cleared the date field
$scope.ngModel.setHours(time.getHours(), time.getMinutes(), 0, 0);
}
};
$scope.time_change = function () {
if ($scope.ngModel && $scope.time) {
// convert from ISO format to Date
if (!($scope.ngModel instanceof Date)) $scope.ngModel = new Date($scope.ngModel);
$scope.ngModel.setHours($scope.time.getHours(), $scope.time.getMinutes(), 0, 0);
} else {
$scope.ngModel = new Date();
}
};
$scope.open = function ($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.innerDateOpened = true;
};
$attrs.$observe('dateFormat', function(newDateFormat, oldValue) {
$scope.dateFormat = newDateFormat;
});
$scope.dateOptions = angular.isDefined($scope.dateOptions) ? $scope.dateOptions : {};
$scope.createDateOptionsWatch('minDate');
$scope.createDateOptionsWatch('maxDate');
$scope.dateOptions.dateDisabled = $scope.dateDisabled;
}
],
link: function (scope, element, attrs, ctrl) {
var firstTimeAssign = true;
scope.$watch(function () {
return scope.ngModel;
}, function (newTime) {
var timeElement = document.evaluate(".//*[@ng-model='time']",
element[0], null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
// if a time element is focused, updating its model will cause hours/minutes to be formatted by padding with leading zeros
if (timeElement && !timeElement.contains(document.activeElement)) {
if (newTime === null || newTime === '') { // if the newTime is not defined
if (firstTimeAssign) { // if it's the first time we assign the time value
// create a new default time where the hours, minutes, seconds and milliseconds are set to 0.
newTime = new Date();
newTime.setHours(0, 0, 0, 0);
} else { // clear the time
scope.time = null;
return;
}
}
// Update timepicker (watch on ng-model in timepicker does not use object equality),
// also if the ngModel was not a Date, convert it to date
newTime = new Date(newTime);
if (isNaN(newTime.getTime()) === false) {
scope.time = newTime; // change the time in timepicker
if (firstTimeAssign) {
firstTimeAssign = false;
}
}
}
}, true);
scope.$watch(function () {
return scope.datetimepickerForm.$error;
}, function (errors) {
Object.keys(ctrl.$error).forEach(function (error) {
ctrl.$setValidity(error, true);
});
Object.keys(errors).forEach(function (error) {
ctrl.$setValidity(error, false);
});
}, true);
scope.$watch(function () {
return scope.datetimepickerForm.timepicker.$touched || scope.datetimepickerForm.datepicker.$touched;
}, function (touched) {
if (touched) {
ctrl.$setTouched();
}
});
scope.$watch(function () {
return scope.datetimepickerForm.$dirty;
}, function (dirty) {
if (dirty) {
ctrl.$setDirty();
}
});
scope.$watch('dateOpened', function (value) {
scope.innerDateOpened = value;
});
scope.$watch('innerDateOpened', function (value) {
if (angular.isDefined(scope.dateOpened)) {
scope.dateOpened = value;
}
});
}
}
}
]).directive('isolateForm', [function () {
return {
restrict: 'A',
require: '?form',
link: function (scope, elm, attrs, ctrl) {
if (!ctrl) {
return;
}
// Do a copy of the controller
var ctrlCopy = {};
angular.copy(ctrl, ctrlCopy);
// Get the parent of the form
var parent = elm.parent().controller('form');
if (!parent) {
return;
}
// Remove parent link to the controller
parent.$removeControl(ctrl);
// Replace form controller with an "isolated form"
var isolatedFormCtrl = {
$setValidity: function (validationToken, isValid, control) {
ctrlCopy.$setValidity(validationToken, isValid, control);
parent.$setValidity(validationToken, true, ctrl);
}
};
angular.extend(ctrl, isolatedFormCtrl);
}
};
}]);