summaryrefslogtreecommitdiffstats
path: root/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0
diff options
context:
space:
mode:
authorxudan <xudan16@huawei.com>2018-07-06 05:16:40 -0400
committerxudan <xudan16@huawei.com>2018-07-06 05:21:42 -0400
commitb3e40f026d655501bfa581452c447784604ecb05 (patch)
tree406f8bfc1abc1b33f98153d03abd34ef7b0e2fe9 /3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0
parentb1b0ea32d1a296c7d055c5391261dcad6be48c63 (diff)
Move all web portal code to the new repo dovetail-webportal
This is only the first step to simply copy the file here. There still need some more work to make sure all work well. All the changes will be submitted with other patches to make it easily to review. JIRA: DOVETAIL-671 Change-Id: I64d32a9df562184166b6199e2719f298687d1a0a Signed-off-by: xudan <xudan16@huawei.com>
Diffstat (limited to '3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0')
-rwxr-xr-x3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.css220
-rwxr-xr-x3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.min.css7
-rwxr-xr-x3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.js2743
-rwxr-xr-x3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.min.js7
4 files changed, 2977 insertions, 0 deletions
diff --git a/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.css b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.css
new file mode 100755
index 0000000..b86cfae
--- /dev/null
+++ b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.css
@@ -0,0 +1,220 @@
+
+/* ==== editable-form ==== */
+
+/* class for single editable element */
+.editable-wrap {
+ display: inline-block;
+ white-space: pre;
+ margin: 0;
+}
+
+/* remove bottom-margin for bootstrap */
+.editable-wrap .editable-controls,
+.editable-wrap .editable-error {
+ margin-bottom: 0;
+}
+
+/* remove bottom-margin of inputs */
+.editable-wrap .editable-controls > input,
+.editable-wrap .editable-controls > select,
+.editable-wrap .editable-controls > textarea {
+ margin-bottom: 0;
+}
+
+/* keep buttons on the same line */
+.editable-wrap .editable-input {
+ display: inline-block;
+}
+
+.editable-buttons {
+ display: inline-block;
+ vertical-align: top;
+}
+
+.editable-buttons button {
+ margin-left: 5px;
+}
+
+/* in bootstrap width: 100% => buttons go outside the box */
+.editable-input.editable-has-buttons {
+ width: auto;
+}
+
+/* ==== editable-text ==== */
+
+/* fix padding issue on typeahead */
+.editable-text {
+ white-space: nowrap;
+}
+
+/* ==== editable-bsdate ==== */
+
+/* fix padding issue on bsdate popup */
+.editable-bsdate {
+ white-space: nowrap;
+}
+
+/* ==== editable-bstime ==== */
+
+/* fix padding issue on bstime */
+.editable-bstime {
+ white-space: nowrap;
+}
+
+/* workaround for bootstrap that sets width: 100% and inputs become too wide */
+.editable-bstime .editable-input input[type="text"] {
+ width: 46px;
+}
+
+/* less padding for .well */
+.editable-bstime .well-small {
+ margin-bottom: 0;
+ padding: 10px;
+}
+
+/* ==== editable-range ==== */
+
+.editable-range output {
+ display: inline-block;
+ min-width: 30px;
+ vertical-align: top;
+ text-align: center;
+}
+
+/* ==== editable-color ==== */
+
+.editable-color input[type="color"] {
+ width: 50px;
+}
+
+
+/* ==== editable-checkbox ==== */
+/* ==== editable-checklist ==== */
+/* ==== editable-radiolist ==== */
+
+.editable-checkbox label span,
+.editable-checklist label span,
+.editable-radiolist label span {
+ margin-left: 7px;
+ margin-right: 10px;
+}
+
+/* ==== element ==== */
+
+/* hiding element */
+.editable-hide {
+ display: none !important;
+}
+
+.editable-click,
+a.editable-click {
+ text-decoration: none;
+ color: #428bca;
+ border-bottom: dashed 1px #428bca;
+}
+
+.editable-click:hover,
+a.editable-click:hover {
+ text-decoration: none;
+ color: #2a6496;
+ border-bottom-color: #2a6496;
+}
+
+/* editable-empty */
+.editable-empty,
+.editable-empty:hover,
+.editable-empty:focus,
+a.editable-empty,
+a.editable-empty:hover,
+a.editable-empty:focus {
+ font-style: italic;
+ color: #DD1144;
+ text-decoration: none;
+}
+
+/* ui-bootstrap editable popover */
+.ui-popover-wrapper a {
+ /* make the link always show up */
+ display: inline !important;
+}
+
+.ui-popover-wrapper form {
+ display: none !important;
+}
+
+/* editable popover */
+.popover-wrapper > a {
+ /* make the link always show up */
+ display: inline !important;
+}
+
+.popover-wrapper {
+ /* make absolutely positioned children constrained to this box*/
+ display: inline;
+ position: relative;
+}
+
+.popover-wrapper form {
+ position: absolute;
+ top: -53px;
+ background: #FFF;
+ border: 1px solid #AAA;
+ border-radius: 5px;
+ padding: 7px;
+ width: auto;
+ display: inline-block;
+ left: 50%;
+ z-index: 101;
+}
+
+.popover-wrapper form:before {
+ content:"";
+ width: 0;
+ height: 0;
+ border-left: 10px solid transparent;
+ border-right: 10px solid transparent;
+ border-top: 10px solid #AAA;
+ position:absolute;
+ bottom:-10px;
+}
+
+.popover-wrapper form:after {
+ content:"";
+ width:0;
+ height:0;
+ border-left: 9px solid transparent;
+ border-right: 9px solid transparent;
+ border-top: 9px solid #FFF;
+ position:absolute;
+ bottom:-9px;
+}
+
+
+@media screen and (max-width: 750px) {
+ .popover-wrapper form {
+ margin-left: -60px;
+ }
+
+ .popover-wrapper form:before {
+ left:50px;
+ }
+
+ .popover-wrapper form:after {
+ left:51px;
+ }
+}
+
+@media screen and (min-width: 750px) {
+ .popover-wrapper form {
+ margin-left: -110px;
+ }
+
+ .popover-wrapper form:before {
+ left:100px;
+ }
+
+ .popover-wrapper form:after {
+ left:101px;
+ }
+}
+
diff --git a/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.min.css b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.min.css
new file mode 100755
index 0000000..b19ca26
--- /dev/null
+++ b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/css/xeditable.min.css
@@ -0,0 +1,7 @@
+/*!
+angular-xeditable - 0.8.0
+Edit-in-place for angular.js
+Build date: 2017-06-06
+*/
+
+.editable-wrap{display:inline-block;white-space:pre;margin:0}.editable-wrap .editable-controls,.editable-wrap .editable-error{margin-bottom:0}.editable-wrap .editable-controls>input,.editable-wrap .editable-controls>select,.editable-wrap .editable-controls>textarea{margin-bottom:0}.editable-wrap .editable-input{display:inline-block}.editable-buttons{display:inline-block;vertical-align:top}.editable-buttons button{margin-left:5px}.editable-input.editable-has-buttons{width:auto}.editable-text{white-space:nowrap}.editable-bsdate{white-space:nowrap}.editable-bstime{white-space:nowrap}.editable-bstime .editable-input input[type=text]{width:46px}.editable-bstime .well-small{margin-bottom:0;padding:10px}.editable-range output{display:inline-block;min-width:30px;vertical-align:top;text-align:center}.editable-color input[type=color]{width:50px}.editable-checkbox label span,.editable-checklist label span,.editable-radiolist label span{margin-left:7px;margin-right:10px}.editable-hide{display:none!important}.editable-click,a.editable-click{text-decoration:none;color:#428bca;border-bottom:dashed 1px #428bca}.editable-click:hover,a.editable-click:hover{text-decoration:none;color:#2a6496;border-bottom-color:#2a6496}.editable-empty,.editable-empty:hover,.editable-empty:focus,a.editable-empty,a.editable-empty:hover,a.editable-empty:focus{font-style:italic;color:#D14;text-decoration:none}.ui-popover-wrapper a{display:inline!important}.ui-popover-wrapper form{display:none!important}.popover-wrapper>a{display:inline!important}.popover-wrapper{display:inline;position:relative}.popover-wrapper form{position:absolute;top:-53px;background:#FFF;border:1px solid #AAA;border-radius:5px;padding:7px;width:auto;display:inline-block;left:50%;z-index:101}.popover-wrapper form:before{content:"";width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid #AAA;position:absolute;bottom:-10px}.popover-wrapper form:after{content:"";width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-top:9px solid #FFF;position:absolute;bottom:-9px}@media screen and (max-width:750px){.popover-wrapper form{margin-left:-60px}.popover-wrapper form:before{left:50px}.popover-wrapper form:after{left:51px}}@media screen and (min-width:750px){.popover-wrapper form{margin-left:-110px}.popover-wrapper form:before{left:100px}.popover-wrapper form:after{left:101px}} \ No newline at end of file
diff --git a/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.js b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.js
new file mode 100755
index 0000000..435b2c3
--- /dev/null
+++ b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.js
@@ -0,0 +1,2743 @@
+/*!
+angular-xeditable - 0.8.0
+Edit-in-place for angular.js
+Build date: 2017-06-06
+*/
+/**
+ * Angular-xeditable module
+ *
+ */
+angular.module('xeditable', [])
+
+
+/**
+ * Default options.
+ *
+ * @namespace editable-options
+ */
+//todo: maybe better have editableDefaults, not options...
+.value('editableOptions', {
+ /**
+ * Theme. Possible values `bs3`, `bs2`, `default`.
+ * Default is `default`
+ *
+ * @var {string} theme
+ * @memberOf editable-options
+ */
+ theme: 'default',
+ /**
+ * icon_set. Possible values `font-awesome`, `default`.
+ * Default is `default`
+ *
+ * @var {string} icon set
+ * @memberOf editable-options
+ */
+ icon_set: 'default',
+ /**
+ * Whether to show buttons for single editable element.
+ * Possible values `right`, `no`.
+ * Default is `right`
+ *
+ * @var {string} buttons
+ * @memberOf editable-options
+ */
+ buttons: 'right',
+ /**
+ * Default value for `blur` attribute of single editable element.
+ * Can be `cancel|submit|ignore`.
+ * Default is `cancel`
+ *
+ * @var {string} blurElem
+ * @memberOf editable-options
+ */
+ blurElem: 'cancel',
+ /**
+ * Default value for `blur` attribute of editable form.
+ * Can be `cancel|submit|ignore`.
+ * Default is `ignore`.
+ *
+ * @var {string} blurForm
+ * @memberOf editable-options
+ */
+ blurForm: 'ignore',
+ /**
+ * How input elements get activated. Possible values: `focus|select|none`.
+ * Default is `focus`
+ *
+ * @var {string} activate
+ * @memberOf editable-options
+ */
+ activate: 'focus',
+ /**
+ * Whether to disable x-editable. Can be overloaded on each element.
+ * Default is `false`
+ *
+ * @var {boolean} isDisabled
+ * @memberOf editable-options
+ */
+ isDisabled: false,
+
+ /**
+ * Event, on which the edit mode gets activated.
+ * Can be any event.
+ * Default is `click`
+ *
+ * @var {string} activationEvent
+ * @memberOf editable-options
+ */
+ activationEvent: 'click',
+
+ /**
+ * The default title of the submit button.
+ * Default is `Submit`
+ *
+ * @var {string} submitButtonTitle
+ * @memberOf editable-options
+ */
+ submitButtonTitle: 'Submit',
+
+ /**
+ * The default aria label of the submit button.
+ * Default is `Submit`
+ *
+ * @var {string} submitButtonAriaLabel
+ * @memberOf editable-options
+ */
+ submitButtonAriaLabel: 'Submit',
+
+ /**
+ * The default title of the cancel button.
+ * Default is `Cancel`
+ *
+ * @var {string} cancelButtonTitle
+ * @memberOf editable-options
+ */
+ cancelButtonTitle: 'Cancel',
+
+ /**
+ * The default aria label of the cancel button.
+ * Default is `Cancel`
+ *
+ * @var {string} cancelButtonAriaLabel
+ * @memberOf editable-options
+ */
+ cancelButtonAriaLabel: 'Cancel',
+
+ /**
+ * The default title of the clear button.
+ * Default is `Clear`
+ *
+ * @var {string} clearButtonTitle
+ * @memberOf editable-options
+ */
+ clearButtonTitle: 'Clear',
+
+ /**
+ * The default aria label of the clear button.
+ * Default is `Clear`
+ *
+ * @var {string} clearButtonAriaLabel
+ * @memberOf editable-options
+ */
+ clearButtonAriaLabel: 'Clear',
+
+ /**
+ * Whether to display the clear button.
+ * Default is `false`
+ *
+ * @var {boolean} displayClearButton
+ * @memberOf editable-options
+ */
+ displayClearButton: false
+});
+
+/*
+ Angular-ui bootstrap datepicker
+ http://angular-ui.github.io/bootstrap/#/datepicker
+ */
+angular.module('xeditable').directive('editableBsdate', ['editableDirectiveFactory', '$injector', '$parse',
+ function(editableDirectiveFactory, $injector, $parse) {
+
+ // Constants from Angular-ui bootstrap datepicker
+ uibDatepickerConfig = $injector.get('uibDatepickerConfig');
+ uibDatepickerPopupConfig = $injector.get('uibDatepickerPopupConfig');
+
+ var popupAttrNames = [
+ ['eIsOpen', 'is-open'],
+ ['eDateDisabled', 'date-disabled'],
+ ['eDatepickerPopup', 'uib-datepicker-popup'],
+ ['eShowButtonBar', 'show-button-bar'],
+ ['eCurrentText', 'current-text'],
+ ['eClearText', 'clear-text'],
+ ['eCloseText', 'close-text'],
+ ['eCloseOnDateSelection', 'close-on-date-selection'],
+ ['eDatepickerAppendToBody', 'datepicker-append-to-body'],
+ ['eOnOpenFocus', 'on-open-focus'],
+ ['eName', 'name'],
+ ['eDateDisabled', 'date-disabled'],
+ ['eAltInputFormats', 'alt-input-formats']
+ ];
+
+ var dateOptionsNames = [
+ ['eFormatDay', 'formatDay'],
+ ['eFormatMonth', 'formatMonth'],
+ ['eFormatYear', 'formatYear'],
+ ['eFormatDayHeader', 'formatDayHeader'],
+ ['eFormatDayTitle', 'formatDayTitle'],
+ ['eFormatMonthTitle', 'formatMonthTitle'],
+ ['eMaxMode', 'maxMode'],
+ ['eMinMode', 'minMode'],
+ ['eDatepickerMode', 'datepickerMode']
+ ];
+
+ return editableDirectiveFactory({
+ directiveName: 'editableBsdate',
+ inputTpl: '<div></div>',
+ render: function() {
+ /** This basically renders a datepicker as in the example shown in
+ ** http://angular-ui.github.io/bootstrap/#/datepicker
+ ** The attributes are all the same as in the bootstrap-ui datepicker with e- as prefix
+ **/
+ this.parent.render.call(this);
+
+ var attrs = this.attrs;
+ var scope = this.scope;
+
+ var inputDatePicker = angular.element('<input type="text" class="form-control" ng-model="$parent.$data"/>');
+
+ inputDatePicker.attr('uib-datepicker-popup', attrs.eDatepickerPopupXEditable || uibDatepickerPopupConfig.datepickerPopup );
+ inputDatePicker.attr('year-range', attrs.eYearRange || 20);
+ inputDatePicker.attr('ng-readonly', attrs.eReadonly || false);
+
+ for (var i = popupAttrNames.length - 1; i >= 0; i--) {
+ var popupAttr = attrs[popupAttrNames[i][0]];
+ if (typeof popupAttr !== 'undefined') {
+ inputDatePicker.attr(popupAttrNames[i][1], popupAttr);
+ }
+ }
+
+ if (attrs.eNgChange) {
+ inputDatePicker.attr('ng-change', attrs.eNgChange);
+ this.inputEl.removeAttr('ng-change');
+ }
+
+ if (attrs.eStyle) {
+ inputDatePicker.attr('style', attrs.eStyle);
+ this.inputEl.removeAttr('style');
+ }
+
+ var dateOptions = {
+ maxDate: scope.$eval(attrs.eMaxDate) || uibDatepickerConfig.maxDate,
+ minDate: scope.$eval(attrs.eMinDate) || uibDatepickerConfig.minDate,
+ showWeeks: attrs.eShowWeeks ? attrs.eShowWeeks.toLowerCase() === 'true' : uibDatepickerConfig.showWeeks,
+ startingDay: attrs.eStartingDay || 0,
+ initDate: scope.$eval(attrs.eInitDate) || new Date()
+ };
+
+ if (attrs.eDatepickerOptions) {
+ var eDatepickerOptions = $parse(attrs.eDatepickerOptions)(scope);
+ angular.extend(dateOptions, eDatepickerOptions);
+ }
+
+ for (var z = dateOptionsNames.length - 1; z >= 0; z--) {
+ var doAttr = attrs[dateOptionsNames[z][0]];
+ if (typeof doAttr !== 'undefined') {
+ dateOptions[dateOptionsNames[z][1]] = doAttr;
+ }
+ }
+
+ scope.dateOptions = dateOptions;
+
+ var showCalendarButton = angular.isDefined(attrs.eShowCalendarButton) ? attrs.eShowCalendarButton : "true";
+
+ //See if calendar button should be displayed
+ if (showCalendarButton === "true") {
+ var buttonDatePicker = angular.element('<button type="button" class="btn btn-default"><i class="glyphicon glyphicon-calendar"></i></button>');
+ var buttonWrapper = angular.element('<span class="input-group-btn"></span>');
+
+ buttonDatePicker.attr('ng-click', attrs.eNgClick);
+
+ buttonWrapper.append(buttonDatePicker);
+
+ this.inputEl.append(buttonWrapper);
+ } else {
+ //If no calendar button, display calendar popup on click of input field
+ inputDatePicker.attr('ng-click', attrs.eNgClick);
+ }
+
+ inputDatePicker.attr('datepicker-options', "dateOptions");
+
+ this.inputEl.prepend(inputDatePicker);
+
+ this.inputEl.removeAttr('class');
+ this.inputEl.removeAttr('ng-click');
+ this.inputEl.removeAttr('is-open');
+ this.inputEl.removeAttr('init-date');
+ this.inputEl.removeAttr('datepicker-popup');
+ this.inputEl.removeAttr('required');
+ this.inputEl.removeAttr('ng-model');
+ this.inputEl.removeAttr('date-picker-append-to-body');
+ this.inputEl.removeAttr('name');
+ this.inputEl.attr('class','input-group');
+ },
+ autosubmit: function() {
+ var self = this;
+ self.inputEl.bind('change', function() {
+ setTimeout(function() {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }, 500);
+ });
+
+ self.inputEl.bind('keydown', function(e) {
+ //submit on tab
+ if (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit') {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }
+ });
+ }
+ });
+}]);
+
+/*
+Angular-ui bootstrap editable timepicker
+http://angular-ui.github.io/bootstrap/#/timepicker
+*/
+angular.module('xeditable').directive('editableBstime', ['editableDirectiveFactory',
+ function(editableDirectiveFactory) {
+ return editableDirectiveFactory({
+ directiveName: 'editableBstime',
+ inputTpl: '<div uib-timepicker></div>',
+ render: function() {
+ this.parent.render.call(this);
+
+ // timepicker can't update model when ng-model set directly to it
+ // see: https://github.com/angular-ui/bootstrap/issues/1141
+ // so we wrap it into DIV
+ var div = angular.element('<div class="well well-small" style="display:inline-block;"></div>');
+
+ // move ng-model to wrapping div
+ div.attr('ng-model', this.inputEl.attr('ng-model'));
+ this.inputEl.removeAttr('ng-model');
+
+ // move ng-change to wrapping div
+ if(this.attrs.eNgChange) {
+ div.attr('ng-change', this.inputEl.attr('ng-change'));
+ this.inputEl.removeAttr('ng-change');
+ }
+
+ // wrap
+ this.inputEl.wrap(div);
+ }
+ });
+}]);
+//checkbox
+angular.module('xeditable').directive('editableCheckbox', ['editableDirectiveFactory',
+ function(editableDirectiveFactory) {
+ return editableDirectiveFactory({
+ directiveName: 'editableCheckbox',
+ inputTpl: '<input type="checkbox">',
+ render: function() {
+ this.parent.render.call(this);
+ this.inputEl.wrap('<label></label>');
+
+ if (this.attrs.eTitle) {
+ this.inputEl.parent().append('<span>' + this.attrs.eTitle + '</span>');
+ }
+ },
+ autosubmit: function() {
+ var self = this;
+ self.inputEl.bind('change', function() {
+ setTimeout(function() {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }, 500);
+ });
+ }
+ });
+}]);
+
+// checklist
+angular.module('xeditable').directive('editableChecklist', [
+ 'editableDirectiveFactory',
+ 'editableNgOptionsParser',
+ function(editableDirectiveFactory, editableNgOptionsParser) {
+ return editableDirectiveFactory({
+ directiveName: 'editableChecklist',
+ inputTpl: '<span></span>',
+ useCopy: true,
+ render: function() {
+ this.parent.render.call(this);
+ var parsed = editableNgOptionsParser(this.attrs.eNgOptions);
+ var ngChangeHtml = '';
+ var ngChecklistComparatorHtml = '';
+
+ if (this.attrs.eNgChange) {
+ ngChangeHtml = ' ng-change="' + this.attrs.eNgChange + '"';
+ }
+
+ if (this.attrs.eChecklistComparator) {
+ ngChecklistComparatorHtml = ' checklist-comparator="' + this.attrs.eChecklistComparator + '"';
+ }
+
+ var html = '<label ng-repeat="'+parsed.ngRepeat+'">'+
+ '<input type="checkbox" checklist-model="$parent.$parent.$data" checklist-value="'+parsed.locals.valueFn+'"' +
+ ngChangeHtml + ngChecklistComparatorHtml + '>'+
+ '<span ng-bind="'+parsed.locals.displayFn+'"></span></label>';
+
+ this.inputEl.removeAttr('ng-model');
+ this.inputEl.removeAttr('ng-options');
+ this.inputEl.removeAttr('ng-change');
+ this.inputEl.removeAttr('checklist-comparator');
+ this.inputEl.html(html);
+ }
+ });
+}]);
+
+angular.module('xeditable').directive('editableCombodate', ['editableDirectiveFactory', 'editableCombodate',
+ function(editableDirectiveFactory, editableCombodate) {
+ return editableDirectiveFactory({
+ directiveName: 'editableCombodate',
+ inputTpl: '<input type="text">',
+ render: function() {
+ this.parent.render.call(this);
+
+ var options = {
+ value: new Date(this.scope.$data)
+ };
+ var self = this;
+ angular.forEach(["format", "template", "minYear", "maxYear", "yearDescending", "minuteStep", "secondStep", "firstItem", "errorClass", "customClass", "roundTime", "smartDays"], function(name) {
+
+ var attrName = "e" + name.charAt(0).toUpperCase() + name.slice(1);
+
+ if (attrName in self.attrs) {
+ if (name == "minYear" || name == "maxYear" || name == "minuteStep" || name == "secondStep") {
+ options[name] = parseInt(self.attrs[attrName], 10);
+ } else {
+ options[name] = self.attrs[attrName];
+ }
+ }
+ });
+
+ var combodate = editableCombodate.getInstance(this.inputEl, options);
+ combodate.$widget.find('select').bind('change', function(e) {
+ //.replace is so this works in Safari
+ self.scope.$data = combodate.getValue() ?
+ (new Date(combodate.getValue().replace(/-/g, "/"))).toISOString() : null;
+ });
+ }
+ });
+ }
+]);
+
+/*
+Input types: text|password|email|tel|number|url|search|color|date|datetime|datetime-local|time|month|week|file
+*/
+
+(function() {
+
+ var camelCase = function(dashDelimitedString) {
+ return dashDelimitedString.toLowerCase().replace(/-(.)/g, function(match, word) {
+ return word.toUpperCase();
+ });
+ };
+
+ var types = 'text|password|email|tel|number|url|search|color|date|datetime|datetime-local|time|month|week|file'.split('|');
+
+ //todo: datalist
+
+ // generate directives
+ angular.forEach(types, function(type) {
+ var directiveName = camelCase('editable' + '-' + type);
+ angular.module('xeditable').directive(directiveName, ['editableDirectiveFactory',
+ function(editableDirectiveFactory) {
+ return editableDirectiveFactory({
+ directiveName: directiveName,
+ inputTpl: '<input type="'+type+'">',
+ render: function() {
+ this.parent.render.call(this);
+
+ //Add bootstrap simple input groups
+ if (this.attrs.eInputgroupleft || this.attrs.eInputgroupright) {
+ this.inputEl.wrap('<div class="input-group"></div>');
+
+ if (this.attrs.eInputgroupleft) {
+ var inputGroupLeft = angular.element('<span class="input-group-addon">' + this.attrs.eInputgroupleft + '</span>');
+ this.inputEl.parent().prepend(inputGroupLeft);
+ }
+
+ if (this.attrs.eInputgroupright) {
+ var inputGroupRight = angular.element('<span class="input-group-addon">' + this.attrs.eInputgroupright + '</span>');
+ this.inputEl.parent().append(inputGroupRight);
+ }
+ }
+
+ // Add label to the input
+ if (this.attrs.eLabel) {
+ var label = angular.element('<label>' + this.attrs.eLabel + '</label>');
+ if (this.attrs.eInputgroupleft || this.attrs.eInputgroupright) {
+ this.inputEl.parent().parent().prepend(label);
+ } else {
+ this.inputEl.parent().prepend(label);
+ }
+ }
+
+ // Add classes to the form
+ if (this.attrs.eFormclass) {
+ this.editorEl.addClass(this.attrs.eFormclass);
+ }
+ },
+ autosubmit: function() {
+ var self = this;
+ self.inputEl.bind('keydown', function(e) {
+ //submit on tab
+ if (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit') {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }
+ });
+ }
+ });
+ }]);
+ });
+
+ //`range` is bit specific
+ angular.module('xeditable').directive('editableRange', ['editableDirectiveFactory', '$interpolate',
+ function(editableDirectiveFactory, $interpolate) {
+ return editableDirectiveFactory({
+ directiveName: 'editableRange',
+ inputTpl: '<input type="range" id="range" name="range">',
+ render: function() {
+ this.parent.render.call(this);
+ this.inputEl.after('<output>' + $interpolate.startSymbol() + '$data' + $interpolate.endSymbol() + '</output>');
+ }
+ });
+ }]);
+
+}());
+
+
+/*
+ Tags input directive for AngularJS
+ https://github.com/mbenford/ngTagsInput
+ */
+angular.module('xeditable').directive('editableTagsInput', ['editableDirectiveFactory', 'editableUtils',
+ function(editableDirectiveFactory, editableUtils) {
+ var dir = editableDirectiveFactory({
+ directiveName: 'editableTagsInput',
+ inputTpl: '<tags-input></tags-input>',
+ useCopy: true,
+ render: function () {
+ this.parent.render.call(this);
+ this.inputEl.append(editableUtils.rename('auto-complete', this.attrs.$autoCompleteElement));
+ this.inputEl.removeAttr('ng-model');
+ this.inputEl.attr('ng-model', '$parent.$data');
+ }
+ });
+
+ var linkOrg = dir.link;
+
+ dir.link = function (scope, el, attrs, ctrl) {
+ var autoCompleteEl = el.find('editable-tags-input-auto-complete');
+
+ attrs.$autoCompleteElement = autoCompleteEl.clone();
+
+ autoCompleteEl.remove();
+
+ return linkOrg(scope, el, attrs, ctrl);
+ };
+
+ return dir;
+}]);
+// radiolist
+angular.module('xeditable').directive('editableRadiolist', [
+ 'editableDirectiveFactory',
+ 'editableNgOptionsParser',
+ '$interpolate',
+ function(editableDirectiveFactory, editableNgOptionsParser, $interpolate) {
+ return editableDirectiveFactory({
+ directiveName: 'editableRadiolist',
+ inputTpl: '<span></span>',
+ render: function() {
+ this.parent.render.call(this);
+ var parsed = editableNgOptionsParser(this.attrs.eNgOptions),
+ ngChangeHtml = '',
+ ngNameHtml = '';
+
+ if (this.attrs.eNgChange) {
+ ngChangeHtml = ' ng-change="' + this.attrs.eNgChange + '"';
+ }
+
+ if (this.attrs.eName) {
+ ngNameHtml = ' name="' + this.attrs.eName + '"';
+ }
+
+ var html = '<label data-ng-repeat="'+parsed.ngRepeat+'">'+
+ '<input type="radio" data-ng-disabled="::' +
+ this.attrs.eNgDisabled +
+ '" data-ng-model="$parent.$parent.$data" data-ng-value="' + $interpolate.startSymbol() +
+ '::' + parsed.locals.valueFn + $interpolate.endSymbol() +'"' +
+ ngChangeHtml + ngNameHtml + '>'+
+ '<span data-ng-bind="::'+parsed.locals.displayFn+'"></span></label>';
+
+ this.inputEl.removeAttr('ng-model');
+ this.inputEl.removeAttr('ng-options');
+ this.inputEl.removeAttr('ng-change');
+ this.inputEl.html(html);
+ },
+ autosubmit: function() {
+ var self = this;
+ self.inputEl.bind('change', function() {
+ setTimeout(function() {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }, 500);
+ });
+ }
+ });
+}]);
+
+//select
+angular.module('xeditable').directive('editableSelect', ['editableDirectiveFactory',
+ function(editableDirectiveFactory) {
+ return editableDirectiveFactory({
+ directiveName: 'editableSelect',
+ inputTpl: '<select></select>',
+ render: function() {
+ this.parent.render.call(this);
+
+ if (this.attrs.ePlaceholder) {
+ var placeholder = angular.element('<option value="">' + this.attrs.ePlaceholder + '</option>');
+ this.inputEl.append(placeholder);
+ }
+ },
+ autosubmit: function() {
+ var self = this;
+
+ if (!self.attrs.hasOwnProperty("eMultiple")) {
+ self.inputEl.bind('change', function () {
+ self.scope.$apply(function () {
+ self.scope.$form.$submit();
+ });
+ });
+ }
+ }
+ });
+}]);
+//textarea
+angular.module('xeditable').directive('editableTextarea', ['editableDirectiveFactory',
+ function(editableDirectiveFactory) {
+ return editableDirectiveFactory({
+ directiveName: 'editableTextarea',
+ inputTpl: '<textarea></textarea>',
+ addListeners: function() {
+ var self = this;
+ self.parent.addListeners.call(self);
+ // submit textarea by ctrl+enter even with buttons
+ if (self.single && self.buttons !== 'no') {
+ self.autosubmit();
+ }
+ },
+ autosubmit: function() {
+ var self = this;
+ self.inputEl.bind('keydown', function(e) {
+ if (self.attrs.submitOnEnter) {
+ if (e.keyCode === 13 && !e.shiftKey) {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }
+ } else if ((e.ctrlKey || e.metaKey) && (e.keyCode === 13) ||
+ (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit')) {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }
+ });
+ }
+ });
+}]);
+
+/*
+ jQuery UI Datepicker for AngularJS
+ https://github.com/angular-ui/ui-date
+ */
+angular.module('xeditable').directive('editableUidate', ['editableDirectiveFactory',
+ function(editableDirectiveFactory) {
+ return editableDirectiveFactory({
+ directiveName: 'editableUidate',
+ inputTpl: '<input class="form-control" />',
+ render: function() {
+ this.parent.render.call(this);
+ this.inputEl.attr('ui-date', this.attrs.eUiDate);
+ this.inputEl.attr('placeholder', this.attrs.ePlaceholder);
+ }
+ });
+}]);
+
+/*
+ AngularJS-native version of Select2 and Selectize
+ https://github.com/angular-ui/ui-select
+ */
+angular.module('xeditable').directive('editableUiSelect',['editableDirectiveFactory', 'editableUtils',
+ function(editableDirectiveFactory, editableUtils) {
+ var dir = editableDirectiveFactory({
+ directiveName: 'editableUiSelect',
+ inputTpl: '<ui-select></ui-select>',
+ render: function () {
+ this.parent.render.call(this);
+ this.inputEl.append(editableUtils.rename('ui-select-match', this.attrs.$matchElement));
+ this.inputEl.append(editableUtils.rename('ui-select-choices', this.attrs.$choicesElement));
+ this.inputEl.removeAttr('ng-model');
+ this.inputEl.attr('ng-model', '$parent.$parent.$data');
+ },
+ autosubmit: function() {
+ var self = this;
+ self.inputEl.bind('change', function() {
+ setTimeout(function() {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }, 500);
+ });
+
+ self.inputEl.bind('keydown', function(e) {
+ //submit on tab
+ if (e.keyCode === 9 && self.editorEl.attr('blur') === 'submit') {
+ self.scope.$apply(function() {
+ self.scope.$form.$submit();
+ });
+ }
+ });
+ }
+ });
+
+ var linkOrg = dir.link;
+
+ dir.link = function (scope, el, attrs, ctrl) {
+ var matchEl = el.find('editable-ui-select-match');
+ var choicesEl = el.find('editable-ui-select-choices');
+
+ attrs.$matchElement = matchEl.clone();
+ attrs.$choicesElement = choicesEl.clone();
+
+ matchEl.remove();
+ choicesEl.remove();
+
+ return linkOrg(scope, el, attrs, ctrl);
+ };
+
+ return dir;
+ }]);
+/**
+ * EditableController class.
+ * Attached to element with `editable-xxx` directive.
+ *
+ * @namespace editable-element
+ */
+/*
+TODO: this file should be refactored to work more clear without closures!
+*/
+angular.module('xeditable').factory('editableController',
+ ['$q', 'editableUtils',
+ function($q, editableUtils) {
+
+ //EditableController function
+ EditableController.$inject = ['$scope', '$attrs', '$element', '$parse', 'editableThemes', 'editableIcons', 'editableOptions', '$rootScope', '$compile', '$q', '$sce', '$templateCache'];
+ function EditableController($scope, $attrs, $element, $parse, editableThemes, editableIcons, editableOptions, $rootScope, $compile, $q, $sce, $templateCache) {
+ var valueGetter;
+
+ //if control is disabled - it does not participate in waiting process
+ var inWaiting;
+
+ var self = this;
+
+ self.scope = $scope;
+ self.elem = $element;
+ self.attrs = $attrs;
+ self.inputEl = null;
+ self.editorEl = null;
+ self.single = true;
+ self.error = '';
+ self.theme = editableThemes[$attrs.editableTheme] || editableThemes[editableOptions.theme] || editableThemes['default'];
+ self.parent = {};
+
+ //will be undefined if icon_set is default and theme is default
+ var theme_name = $attrs.editableTheme || editableOptions.theme || 'default';
+ // The theme_name will not be correct if the theme set in options is unavailable
+ // However, in that case an undefined icon_set is not that bad...
+ var icon_set_option = $attrs.editableIconSet || editableOptions.icon_set;
+ self.icon_set = icon_set_option === 'default' ? editableIcons.default[theme_name] : editableIcons.external[icon_set_option];
+
+ //to be overwritten by directive
+ self.inputTpl = '';
+ self.directiveName = '';
+
+ // with majority of controls copy is not needed, but..
+ // copy MUST NOT be used for `select-multiple` with objects as items
+ // copy MUST be used for `checklist`
+ self.useCopy = false;
+
+ //runtime (defaults)
+ self.single = null;
+
+ /**
+ * Attributes defined with `e-*` prefix automatically transferred from original element to
+ * control.
+ * For example, if you set `<span editable-text="user.name" e-style="width: 100px"`>
+ * then input will appear as `<input style="width: 100px">`.
+ * See [demo](#text-customize).
+ *
+ * @var {any|attribute} e-*
+ * @memberOf editable-element
+ */
+
+ /**
+ * Whether to show ok/cancel buttons. Values: `right|no`.
+ * If set to `no` control automatically submitted when value changed.
+ * If control is part of form buttons will never be shown.
+ *
+ * @var {string|attribute} buttons
+ * @memberOf editable-element
+ */
+ self.buttons = 'right';
+
+ /**
+ * Whether to show the editable element in a ui-bootstrap popover. Values: `true|false`.
+ *
+ * @var {boolean|attribute} popover
+ * @memberOf editable-element
+ */
+ self.popover = false;
+
+ /**
+ * Action when control losses focus. Values: `cancel|submit|ignore`.
+ * Has sense only for single editable element.
+ * Otherwise, if control is part of form - you should set `blur` of form, not of individual element.
+ *
+ * @var {string|attribute} blur
+ * @memberOf editable-element
+ */
+ // no real `blur` property as it is transferred to editable form
+
+ //init
+ self.init = function(single) {
+ self.single = single;
+
+ self.name = $attrs.eName || $attrs[self.directiveName];
+ /*
+ if(!$attrs[directiveName] && !$attrs.eNgModel && ($attrs.eValue === undefined)) {
+ throw 'You should provide value for `'+directiveName+'` or `e-value` in editable element!';
+ }
+ */
+ if($attrs[self.directiveName]) {
+ valueGetter = $parse($attrs[self.directiveName]);
+ } else {
+ throw 'You should provide value for `'+self.directiveName+'` in editable element!';
+ }
+
+ // settings for single and non-single
+ if (!self.single) {
+ // hide buttons for non-single
+ self.buttons = 'no';
+ } else {
+ self.buttons = self.attrs.buttons || editableOptions.buttons;
+ }
+
+ //if name defined --> watch changes and update $data in form
+ if($attrs.eName) {
+ self.scope.$watch('$data', function(newVal){
+ self.scope.$form.$data[$attrs.eName] = newVal;
+ });
+ }
+
+ /**
+ * Called when control is shown.
+ * See [demo](#select-remote).
+ *
+ * @var {method|attribute} onshow
+ * @memberOf editable-element
+ */
+ if($attrs.onshow) {
+ self.onshow = function() {
+ return self.catchError($parse($attrs.onshow)($scope));
+ };
+ }
+
+ /**
+ * Called when control is hidden after both save or cancel.
+ *
+ * @var {method|attribute} onhide
+ * @memberOf editable-element
+ */
+ if ($attrs.onhide) {
+ self.onhide = function() {
+ return $parse($attrs.onhide)($scope);
+ };
+ }
+
+ /**
+ * Called when control is cancelled.
+ *
+ * @var {method|attribute} oncancel
+ * @memberOf editable-element
+ */
+ if ($attrs.oncancel) {
+ self.oncancel = function() {
+ return $parse($attrs.oncancel)($scope);
+ };
+ }
+
+ /**
+ * Called during submit before value is saved to model.
+ * See [demo](#onbeforesave).
+ *
+ * @var {method|attribute} onbeforesave
+ * @memberOf editable-element
+ */
+ if ($attrs.onbeforesave) {
+ self.onbeforesave = function() {
+ return self.catchError($parse($attrs.onbeforesave)($scope));
+ };
+ }
+
+ /**
+ * Called during submit after value is saved to model.
+ * See [demo](#onaftersave).
+ *
+ * @var {method|attribute} onaftersave
+ * @memberOf editable-element
+ */
+ if ($attrs.onaftersave) {
+ self.onaftersave = function() {
+ return self.catchError($parse($attrs.onaftersave)($scope));
+ };
+ }
+
+ if ($attrs.popover) {
+ self.popover = self.attrs.popover;
+ }
+
+ // watch change of model to update editable element
+ // now only add/remove `editable-empty` class.
+ // Initially this method called with newVal = undefined, oldVal = undefined
+ // so no need initially call handleEmpty() explicitly
+ $scope.$parent.$watch($attrs[self.directiveName], function(newVal, oldVal) {
+ self.setLocalValue();
+ self.handleEmpty();
+ });
+ };
+
+ self.render = function() {
+ var theme = self.theme;
+
+ //build input
+ self.inputEl = angular.element(self.inputTpl);
+
+ //build controls
+ self.controlsEl = angular.element(theme.controlsTpl);
+ self.controlsEl.append(self.inputEl);
+
+ //build buttons
+ if(self.buttons !== 'no') {
+ self.buttonsEl = angular.element(theme.buttonsTpl);
+ self.submitEl = angular.element(theme.submitTpl);
+ self.resetEl = angular.element(theme.resetTpl);
+ self.cancelEl = angular.element(theme.cancelTpl);
+ self.submitEl.attr('title', editableOptions.submitButtonTitle);
+ self.submitEl.attr('aria-label', editableOptions.submitButtonAriaLabel);
+ self.cancelEl.attr('title', editableOptions.cancelButtonTitle);
+ self.cancelEl.attr('aria-label', editableOptions.cancelButtonAriaLabel);
+ self.resetEl.attr('title', editableOptions.clearButtonTitle);
+ self.resetEl.attr('aria-label', editableOptions.clearButtonAriaLabel);
+
+ if (self.icon_set) {
+ self.submitEl.find('span').addClass(self.icon_set.ok);
+ self.cancelEl.find('span').addClass(self.icon_set.cancel);
+ self.resetEl.find('span').addClass(self.icon_set.clear);
+ }
+
+ self.buttonsEl.append(self.submitEl).append(self.cancelEl);
+
+ if (editableOptions.displayClearButton) {
+ self.buttonsEl.append(self.resetEl);
+ }
+
+ self.controlsEl.append(self.buttonsEl);
+
+ self.inputEl.addClass('editable-has-buttons');
+ }
+
+ //build error
+ self.errorEl = angular.element(theme.errorTpl);
+ self.controlsEl.append(self.errorEl);
+
+ //build editor
+ self.editorEl = angular.element(self.single ? theme.formTpl : theme.noformTpl);
+ self.editorEl.append(self.controlsEl);
+
+ // transfer `e-*|data-e-*|x-e-*` attributes
+ for (var k in $attrs.$attr) {
+ if(k.length <= 1) {
+ continue;
+ }
+ var transferAttr = false;
+ var nextLetter = k.substring(1, 2);
+
+ // if starts with `e` + uppercase letter
+ if (k.substring(0, 1) === 'e' && nextLetter === nextLetter.toUpperCase()) {
+ transferAttr = k.substring(1); // cut `e`
+ } else {
+ continue;
+ }
+
+ // exclude `form` and `ng-submit`,
+ if (transferAttr === 'Form' || transferAttr === 'NgSubmit') {
+ continue;
+ }
+
+ var firstLetter = transferAttr.substring(0, 1);
+ var secondLetter = transferAttr.substring(1, 2);
+
+ // convert back to lowercase style
+ if (secondLetter === secondLetter.toUpperCase() &&
+ firstLetter === firstLetter.toUpperCase()) {
+ transferAttr = firstLetter.toLowerCase() + '-' + editableUtils.camelToDash(transferAttr.substring(1));
+ } else {
+ transferAttr = firstLetter.toLowerCase() + editableUtils.camelToDash(transferAttr.substring(1));
+ }
+
+ // workaround for attributes without value (e.g. `multiple = "multiple"`)
+ // except for 'e-value'
+ var attrValue = (transferAttr !== 'value' && $attrs[k] === '') ? transferAttr : $attrs[k];
+
+ // set attributes to input
+ self.inputEl.attr(transferAttr, attrValue);
+ }
+
+ self.inputEl.addClass('editable-input');
+ self.inputEl.attr('ng-model', '$parent.$data');
+
+ // add directiveName class to editor, e.g. `editable-text`
+ self.editorEl.addClass(editableUtils.camelToDash(self.directiveName));
+
+ if (self.single) {
+ self.editorEl.attr('editable-form', '$form');
+ // transfer `blur` to form
+ self.editorEl.attr('blur', self.attrs.blur || editableOptions.blurElem);
+ }
+
+ if (self.popover) {
+ var wrapper = angular.element('<div></div>');
+ wrapper.append(self.editorEl);
+ self.editorEl = wrapper;
+ $templateCache.put('popover.html', self.editorEl[0].outerHTML);
+ }
+
+ //apply `postrender` method of theme
+ if (angular.isFunction(theme.postrender)) {
+ theme.postrender.call(self);
+ }
+
+ };
+
+ // with majority of controls copy is not needed, but..
+ // copy MUST NOT be used for `select-multiple` with objects as items
+ // copy MUST be used for `checklist`
+ self.setLocalValue = function() {
+ self.scope.$data = self.useCopy ?
+ angular.copy(valueGetter($scope.$parent)) :
+ valueGetter($scope.$parent);
+ };
+
+ // reference of the scope to use for $compile
+ var newScope = null;
+ //show
+ self.show = function() {
+ // set value of scope.$data
+ self.setLocalValue();
+
+ /*
+ Originally render() was inside init() method, but some directives polluting editorEl,
+ so it is broken on second opening.
+ Cloning is not a solution as jqLite can not clone with event handler's.
+ */
+ self.render();
+
+ // insert into DOM
+ $element.after(self.editorEl);
+
+ // compile (needed to attach ng-* events from markup)
+ newScope = $scope.$new();
+ $compile(self.editorEl)(newScope);
+
+ // attach listeners (`escape`, autosubmit, etc)
+ self.addListeners();
+
+ // hide element
+ $element.addClass('editable-hide');
+
+ // onshow
+ return self.onshow();
+ };
+
+ //hide
+ self.hide = function() {
+
+ // destroy the scope to prevent memory leak
+ newScope.$destroy();
+
+ self.controlsEl.remove();
+ self.editorEl.remove();
+ $element.removeClass('editable-hide');
+
+ if (self.popover) {
+ $templateCache.remove('popover.html');
+ }
+
+ // onhide
+ return self.onhide();
+ };
+
+ // cancel
+ self.cancel = function() {
+ // oncancel
+ self.oncancel();
+ // don't call hide() here as it called in form's code
+ };
+
+ /*
+ Called after show to attach listeners
+ */
+ self.addListeners = function() {
+ // bind keyup for `escape`
+ self.inputEl.bind('keyup', function(e) {
+ if(!self.single) {
+ return;
+ }
+
+ // todo: move this to editable-form!
+ switch(e.keyCode) {
+ // hide on `escape` press
+ case 27:
+ self.scope.$apply(function() {
+ self.scope.$form.$cancel();
+ });
+ break;
+ }
+ });
+
+ // autosubmit when `no buttons`
+ if (self.single && self.buttons === 'no') {
+ self.autosubmit();
+ }
+
+ // click - mark element as clicked to exclude in document click handler
+ self.editorEl.bind('click', function(e) {
+ // ignore right/middle button click
+ if (e.which && e.which !== 1) {
+ return;
+ }
+
+ if (self.scope.$form.$visible) {
+ self.scope.$form._clicked = true;
+ }
+ });
+ };
+
+ // setWaiting
+ self.setWaiting = function(value) {
+ if (value) {
+ // participate in waiting only if not disabled
+ inWaiting = !self.inputEl.attr('disabled') &&
+ !self.inputEl.attr('ng-disabled') &&
+ !self.inputEl.attr('ng-enabled');
+ if (inWaiting) {
+ self.inputEl.attr('disabled', 'disabled');
+ if(self.buttonsEl) {
+ self.buttonsEl.find('button').attr('disabled', 'disabled');
+ }
+ }
+ } else {
+ if (inWaiting) {
+ self.inputEl.removeAttr('disabled');
+ if (self.buttonsEl) {
+ self.buttonsEl.find('button').removeAttr('disabled');
+ }
+ }
+ }
+ };
+
+ self.activate = function(start, end) {
+ setTimeout(function() {
+ var el = self.inputEl[0];
+
+ if (editableOptions.activate === 'focus' && el.focus) {
+ if (start !== undefined && start !== "" && el.setSelectionRange) {
+ end = end || start;
+ el.onfocus = function() {
+ setTimeout(function() {
+ try {
+ this.setSelectionRange(start,end);
+ } catch(e) {
+ //do nothing, this input doesn't support setSelectionRange
+ }
+ }.bind(this));
+ };
+ }
+
+ if (self.directiveName == 'editableRadiolist' || self.directiveName == 'editableChecklist' ||
+ self.directiveName == 'editableBsdate' || self.directiveName == 'editableTagsInput') {
+ //Set focus to first pristine element in the list
+ el.querySelector('.ng-pristine').focus();
+ } else {
+ el.focus();
+ }
+ } else if (editableOptions.activate === 'select') {
+ if (el.select){
+ el.select();
+ } else if (el.focus) {
+ el.focus();
+ }
+ }
+ }, 0);
+ };
+
+ self.setError = function(msg) {
+ if(!angular.isObject(msg)) {
+ $scope.$error = $sce.trustAsHtml(msg);
+ self.error = msg;
+ }
+ };
+
+ /*
+ Checks that result is string or promise returned string and shows it as error message
+ Applied to onshow, onbeforesave, onaftersave
+ */
+ self.catchError = function(result, noPromise) {
+ if (angular.isObject(result) && noPromise !== true) {
+ $q.when(result).then(
+ //success and fail handlers are equal
+ angular.bind(this, function(r) {
+ this.catchError(r, true);
+ }),
+ angular.bind(this, function(r) {
+ this.catchError(r, true);
+ })
+ );
+ //check $http error
+ } else if (noPromise && angular.isObject(result) && result.status &&
+ (result.status !== 200) && result.data && angular.isString(result.data)) {
+ this.setError(result.data);
+ //set result to string: to let form know that there was error
+ result = result.data;
+ } else if (angular.isString(result)) {
+ this.setError(result);
+ }
+ return result;
+ };
+
+ self.save = function() {
+ valueGetter.assign($scope.$parent,
+ self.useCopy ? angular.copy(self.scope.$data) : self.scope.$data);
+
+ // no need to call handleEmpty here as we are watching change of model value
+ // self.handleEmpty();
+ };
+
+ /*
+ attach/detach `editable-empty` class to element
+ */
+ self.handleEmpty = function() {
+ var val = valueGetter($scope.$parent);
+ var isEmpty = val === null || val === undefined || val === "" || (angular.isArray(val) && val.length === 0);
+ $element.toggleClass('editable-empty', isEmpty);
+ };
+
+ /*
+ Called when `buttons = "no"` to submit automatically
+ */
+ self.autosubmit = angular.noop;
+
+ self.onshow = angular.noop;
+ self.onhide = angular.noop;
+ self.oncancel = angular.noop;
+ self.onbeforesave = angular.noop;
+ self.onaftersave = angular.noop;
+ }
+
+ return EditableController;
+}]);
+
+/*
+editableFactory is used to generate editable directives (see `/directives` folder)
+Inside it does several things:
+- detect form for editable element. Form may be one of three types:
+ 1. autogenerated form (for single editable elements)
+ 2. wrapper form (element wrapped by <form> tag)
+ 3. linked form (element has `e-form` attribute pointing to existing form)
+
+- attach editableController to element
+
+Depends on: editableController, editableFormFactory
+*/
+angular.module('xeditable').factory('editableDirectiveFactory',
+['$parse', '$compile', 'editableThemes', '$rootScope', '$document', 'editableController', 'editableFormController', 'editableOptions',
+function($parse, $compile, editableThemes, $rootScope, $document, editableController, editableFormController, editableOptions) {
+
+ //directive object
+ return function(overwrites) {
+ return {
+ restrict: 'A',
+ scope: true,
+ require: [overwrites.directiveName, '?^form'],
+ controller: editableController,
+ link: function(scope, elem, attrs, ctrl) {
+ // editable controller
+ var eCtrl = ctrl[0];
+
+ // form controller
+ var eFormCtrl;
+
+ // this variable indicates is element is bound to some existing form,
+ // or it's single element who's form will be generated automatically
+ // By default consider single element without any linked form.ß
+ var hasForm = false;
+
+ // element wrapped by form
+ if (ctrl[1]) {
+ eFormCtrl = ctrl[1];
+ hasForm = attrs.eSingle === undefined;
+ } else if (attrs.eForm) { // element not wrapped by <form>, but we have `e-form` attr
+ var getter = $parse(attrs.eForm)(scope);
+ if (getter) { // form exists in scope (above), e.g. editable column
+ eFormCtrl = getter;
+ hasForm = true;
+ } else if (elem && typeof elem.parents === "function" && elem.parents().last().find('form[name='+attrs.eForm+']').length) { // form exists below or not exist at all: check document.forms
+ // form is below and not processed yet
+ eFormCtrl = null;
+ hasForm = true;
+ } else {
+ // form exists below or not exist at all: check document.forms
+ for (var i=0; i<$document[0].forms.length;i++) {
+ if ($document[0].forms[i].name === attrs.eForm) {
+ // form is below and not processed yet
+ eFormCtrl = null;
+ hasForm = true;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ if(hasForm && !attrs.eName) {
+ throw 'You should provide `e-name` for editable element inside form!';
+ }
+ */
+
+ //check for `editable-form` attr in form
+ /*
+ if(eFormCtrl && ) {
+ throw 'You should provide `e-name` for editable element inside form!';
+ }
+ */
+
+ // store original props to `parent` before merge
+ angular.forEach(overwrites, function(v, k) {
+ if(eCtrl[k] !== undefined) {
+ eCtrl.parent[k] = eCtrl[k];
+ }
+ });
+
+ // merge overwrites to base editable controller
+ angular.extend(eCtrl, overwrites);
+
+ // x-editable can be disabled using editableOption or edit-disabled attribute
+ var is_disabled = function() {
+ return angular.isDefined(attrs.editDisabled) ?
+ scope.$eval(attrs.editDisabled) :
+ editableOptions.isDisabled;
+ };
+
+ // init editable ctrl
+ eCtrl.init(!hasForm);
+
+ // publich editable controller as `$editable` to be referenced in html
+ scope.$editable = eCtrl;
+
+ // add `editable` class to element
+ elem.addClass('editable');
+
+ // hasForm
+ if(hasForm) {
+ if(eFormCtrl) {
+ scope.$form = eFormCtrl;
+ if(!scope.$form.$addEditable) {
+ throw 'Form with editable elements should have `editable-form` attribute.';
+ }
+ scope.$form.$addEditable(eCtrl);
+ } else {
+ // future form (below): add editable controller to buffer and add to form later
+ $rootScope.$$editableBuffer = $rootScope.$$editableBuffer || {};
+ $rootScope.$$editableBuffer[attrs.eForm] = $rootScope.$$editableBuffer[attrs.eForm] || [];
+ $rootScope.$$editableBuffer[attrs.eForm].push(eCtrl);
+ scope.$form = null; //will be re-assigned later
+ }
+ // !hasForm
+ } else {
+ // create editableform controller
+ scope.$form = editableFormController();
+ // add self to editable controller
+ scope.$form.$addEditable(eCtrl);
+
+ // if `e-form` provided, publish local $form in scope
+ if(attrs.eForm) {
+ ($parse(attrs.eForm).assign || angular.noop)(scope.$parent, scope.$form);
+ }
+
+ // bind click - if no external form defined
+ if(!attrs.eForm || attrs.eClickable) {
+ elem.addClass('editable-click');
+ elem.bind(editableOptions.activationEvent, function(e) {
+ e.preventDefault();
+ e.editable = eCtrl;
+ if(!is_disabled()) {
+ scope.$apply(function(){
+ scope.$form.$show();
+ });
+ }
+ });
+ }
+ }
+
+ }
+ };
+ };
+}]);
+
+/*
+Returns editableForm controller
+*/
+angular.module('xeditable').factory('editableFormController',
+ ['$parse', '$document', '$rootScope', 'editablePromiseCollection', 'editableUtils',
+ function($parse, $document, $rootScope, editablePromiseCollection, editableUtils) {
+
+ // array of opened editable forms
+ var shown = [];
+
+ //Check if the child element correspond or is a descendant of the parent element
+ var isSelfOrDescendant = function (parent, child) {
+ if (child == parent) {
+ return true;
+ }
+
+ var node = child.parentNode;
+ while (node !== null) {
+ if (node == parent) {
+ return true;
+ }
+ node = node.parentNode;
+ }
+ return false;
+ };
+
+ //Check if it is a real blur : if the click event appear on a shown editable elem, this is not a blur.
+ var isBlur = function(shown, event) {
+ var isBlur = true;
+
+ var editables = shown.$editables;
+ angular.forEach(editables, function(v){
+ var element = v.editorEl[0];
+ if (isSelfOrDescendant(element, event.target))
+ isBlur = false;
+
+ });
+ return isBlur;
+ };
+
+ // bind click to body: cancel|submit|ignore forms
+ $document.bind('click', function(e) {
+ // ignore right/middle button click
+ if (e.which && e.which !== 1) {
+ return;
+ }
+
+ var toCancel = [];
+ var toSubmit = [];
+ for (var i=0; i<shown.length; i++) {
+
+ // exclude clicked
+ if (shown[i]._clicked) {
+ shown[i]._clicked = false;
+ continue;
+ }
+
+ // exclude waiting
+ if (shown[i].$waiting) {
+ continue;
+ }
+
+ if (shown[i]._blur === 'cancel' && isBlur(shown[i], e)) {
+ toCancel.push(shown[i]);
+ }
+
+ if (shown[i]._blur === 'submit' && isBlur(shown[i], e)) {
+ toSubmit.push(shown[i]);
+ }
+ }
+
+ if (toCancel.length || toSubmit.length) {
+ $rootScope.$apply(function() {
+ angular.forEach(toCancel, function(v){ v.$cancel(); });
+ angular.forEach(toSubmit, function(v){ v.$submit(); });
+ });
+ }
+ });
+
+ $rootScope.$on('closeEdit', function() {
+ for(var i=0; i < shown.length; i++) {
+ shown[i].$hide();
+ }
+ });
+
+ var base = {
+ $addEditable: function(editable) {
+ //console.log('add editable', editable.elem, editable.elem.bind);
+ this.$editables.push(editable);
+
+ //'on' is not supported in angular 1.0.8
+ editable.elem.bind('$destroy', angular.bind(this, this.$removeEditable, editable));
+
+ //bind editable's local $form to self (if not bound yet, below form)
+ if (!editable.scope.$form) {
+ editable.scope.$form = this;
+ }
+
+ //if form already shown - call show() of new editable
+ if (this.$visible) {
+ editable.catchError(editable.show());
+ }
+ editable.catchError(editable.setWaiting(this.$waiting));
+ },
+
+ $removeEditable: function(editable) {
+ //arrayRemove
+ for(var i=0; i < this.$editables.length; i++) {
+ if(this.$editables[i] === editable) {
+ this.$editables.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ /**
+ * Shows form with editable controls.
+ *
+ * @method $show()
+ * @memberOf editable-form
+ */
+ $show: function() {
+ if (this.$visible) {
+ return;
+ }
+
+ this.$visible = true;
+
+ var pc = editablePromiseCollection();
+
+ //own show
+ pc.when(this.$onshow());
+
+ //clear errors
+ this.$setError(null, '');
+
+ //children show
+ angular.forEach(this.$editables, function(editable) {
+ pc.when(editable.show());
+ });
+
+ //wait promises and activate
+ pc.then({
+ onWait: angular.bind(this, this.$setWaiting),
+ onTrue: angular.bind(this, this.$activate),
+ onFalse: angular.bind(this, this.$activate),
+ onString: angular.bind(this, this.$activate)
+ });
+
+ // add to internal list of shown forms
+ // setTimeout needed to prevent closing right after opening (e.g. when trigger by button)
+ setTimeout(angular.bind(this, function() {
+ // clear `clicked` to get ready for clicks on visible form
+ this._clicked = false;
+ if(editableUtils.indexOf(shown, this) === -1) {
+ shown.push(this);
+ }
+ }), 0);
+ },
+
+ /**
+ * Sets focus on form field specified by `name`.<br/>
+ * When trying to set the focus on a form field of a new row in the editable table, the `$activate` call needs to be wrapped in a `$timeout` call so that the form is rendered before the `$activate` function is called.
+ *
+ * @method $activate(name)
+ * @param {string} name name of field
+ * @memberOf editable-form
+ */
+ $activate: function(name) {
+ var i,
+ selectionStart,
+ selectionEnd;
+ if (this.$editables.length) {
+ //activate by name
+ if (angular.isString(name)) {
+ for(i=0; i<this.$editables.length; i++) {
+ if (this.$editables[i].name === name) {
+ this.$editables[i].activate();
+ return;
+ }
+ }
+ }
+
+ //try activate error field
+ for(i=0; i<this.$editables.length; i++) {
+ if (this.$editables[i].error) {
+ this.$editables[i].activate();
+ return;
+ }
+ }
+
+ //by default activate first field
+ selectionStart = this.$editables[0].elem[0].selectionStart ?
+ this.$editables[0].elem[0].selectionStart :
+ this.$editables[0].elem[0].text ? this.$editables[0].elem[0].text.length :
+ this.$editables[0].elem[0].innerHTML ? this.$editables[0].elem[0].innerHTML.length : 0;
+ selectionEnd = this.$editables[0].elem[0].selectionEnd ?
+ this.$editables[0].elem[0].selectionEnd :
+ this.$editables[0].elem[0].text ? this.$editables[0].elem[0].text.length :
+ this.$editables[0].elem[0].innerHTML ? this.$editables[0].elem[0].innerHTML.length : 0;
+ this.$editables[0].activate(selectionStart, selectionEnd);
+ }
+ },
+
+ /**
+ * Hides form with editable controls without saving.
+ *
+ * @method $hide()
+ * @memberOf editable-form
+ */
+ $hide: function() {
+ if (!this.$visible) {
+ return;
+ }
+ this.$visible = false;
+ // self hide
+ this.$onhide();
+ // children's hide
+ angular.forEach(this.$editables, function(editable) {
+ editable.hide();
+ });
+
+ // remove from internal list of shown forms
+ editableUtils.arrayRemove(shown, this);
+ },
+
+ /**
+ * Triggers `oncancel` event and calls `$hide()`.
+ *
+ * @method $cancel()
+ * @memberOf editable-form
+ */
+ $cancel: function() {
+ if (!this.$visible) {
+ return;
+ }
+ // self cancel
+ this.$oncancel();
+ // children's cancel
+ angular.forEach(this.$editables, function(editable) {
+ editable.cancel();
+ });
+ // self hide
+ this.$hide();
+ },
+
+ $setWaiting: function(value) {
+ this.$waiting = !!value;
+ // we can't just set $waiting variable and use it via ng-disabled in children
+ // because in editable-row form is not accessible
+ angular.forEach(this.$editables, function(editable) {
+ editable.setWaiting(!!value);
+ });
+ },
+
+ /**
+ * Shows error message for particular field.
+ *
+ * @method $setError(name, msg)
+ * @param {string} name name of field
+ * @param {string} msg error message
+ * @memberOf editable-form
+ */
+ $setError: function(name, msg) {
+ angular.forEach(this.$editables, function(editable) {
+ if(!name || editable.name === name) {
+ editable.setError(msg);
+ }
+ });
+ },
+
+ $submit: function() {
+ if (this.$waiting) {
+ return;
+ }
+
+ //clear errors
+ this.$setError(null, '');
+
+ //children onbeforesave
+ var pc = editablePromiseCollection();
+ angular.forEach(this.$editables, function(editable) {
+ pc.when(editable.onbeforesave());
+ });
+
+ /*
+ onbeforesave result:
+ - true/undefined: save data and close form
+ - false: close form without saving
+ - string: keep form open and show error
+ */
+ pc.then({
+ onWait: angular.bind(this, this.$setWaiting),
+ onTrue: angular.bind(this, checkSelf, true),
+ onFalse: angular.bind(this, checkSelf, false),
+ onString: angular.bind(this, this.$activate)
+ });
+
+ //save
+ function checkSelf(childrenTrue){
+ var pc = editablePromiseCollection();
+ pc.when(this.$onbeforesave());
+ pc.then({
+ onWait: angular.bind(this, this.$setWaiting),
+ onTrue: childrenTrue ? angular.bind(this, this.$save) : angular.bind(this, this.$hide),
+ onFalse: angular.bind(this, this.$hide),
+ onString: angular.bind(this, this.$activate)
+ });
+ }
+ },
+
+ $save: function() {
+ // write model for each editable
+ angular.forEach(this.$editables, function(editable) {
+ editable.save();
+ });
+
+ //call onaftersave of self and children
+ var pc = editablePromiseCollection();
+ pc.when(this.$onaftersave());
+ angular.forEach(this.$editables, function(editable) {
+ pc.when(editable.onaftersave());
+ });
+
+ /*
+ onaftersave result:
+ - true/undefined/false: just close form
+ - string: keep form open and show error
+ */
+ pc.then({
+ onWait: angular.bind(this, this.$setWaiting),
+ onTrue: angular.bind(this, this.$hide),
+ onFalse: angular.bind(this, this.$hide),
+ onString: angular.bind(this, this.$activate)
+ });
+ },
+
+ $onshow: angular.noop,
+ $oncancel: angular.noop,
+ $onhide: angular.noop,
+ $onbeforesave: angular.noop,
+ $onaftersave: angular.noop
+ };
+
+ return function() {
+ return angular.extend({
+ $editables: [],
+ /**
+ * Form visibility flag.
+ *
+ * @var {bool} $visible
+ * @memberOf editable-form
+ */
+ $visible: false,
+ /**
+ * Form waiting flag. It becomes `true` when form is loading or saving data.
+ *
+ * @var {bool} $waiting
+ * @memberOf editable-form
+ */
+ $waiting: false,
+ $data: {},
+ _clicked: false,
+ _blur: null
+ }, base);
+ };
+}]);
+
+/**
+ * EditableForm directive. Should be defined in <form> containing editable controls.
+ * It add some usefull methods to form variable exposed to scope by `name="myform"` attribute.
+ *
+ * @namespace editable-form
+ */
+angular.module('xeditable').directive('editableForm',
+ ['$rootScope', '$parse', 'editableFormController', 'editableOptions',
+ function($rootScope, $parse, editableFormController, editableOptions) {
+ return {
+ restrict: 'A',
+ require: ['form'],
+ //require: ['form', 'editableForm'],
+ //controller: EditableFormController,
+ compile: function() {
+ return {
+ pre: function(scope, elem, attrs, ctrl) {
+ var form = ctrl[0];
+ var eForm;
+
+ //if `editableForm` has value - publish smartly under this value
+ //this is required only for single editor form that is created and removed
+ if(attrs.editableForm) {
+ if(scope[attrs.editableForm] && scope[attrs.editableForm].$show) {
+ eForm = scope[attrs.editableForm];
+ angular.extend(form, eForm);
+ } else {
+ eForm = editableFormController();
+ scope[attrs.editableForm] = eForm;
+ angular.extend(eForm, form);
+ }
+ } else { //just merge to form and publish if form has name
+ eForm = editableFormController();
+ angular.extend(form, eForm);
+ }
+
+ //read editables from buffer (that appeared before FORM tag)
+ var buf = $rootScope.$$editableBuffer;
+ var name = form.$name;
+ if(name && buf && buf[name]) {
+ angular.forEach(buf[name], function(editable) {
+ eForm.$addEditable(editable);
+ });
+ delete buf[name];
+ }
+ },
+ post: function(scope, elem, attrs, ctrl) {
+ var eForm;
+
+ if(attrs.editableForm && scope[attrs.editableForm] && scope[attrs.editableForm].$show) {
+ eForm = scope[attrs.editableForm];
+ } else {
+ eForm = ctrl[0];
+ }
+
+ /**
+ * Called when form is shown.
+ *
+ * @var {method|attribute} onshow
+ * @memberOf editable-form
+ */
+ if(attrs.onshow) {
+ eForm.$onshow = angular.bind(eForm, $parse(attrs.onshow), scope);
+ }
+
+ /**
+ * Called when form hides after both save or cancel.
+ *
+ * @var {method|attribute} onhide
+ * @memberOf editable-form
+ */
+ if(attrs.onhide) {
+ eForm.$onhide = angular.bind(eForm, $parse(attrs.onhide), scope);
+ }
+
+ /**
+ * Called when form is cancelled.
+ *
+ * @var {method|attribute} oncancel
+ * @memberOf editable-form
+ */
+ if(attrs.oncancel) {
+ eForm.$oncancel = angular.bind(eForm, $parse(attrs.oncancel), scope);
+ }
+
+ /**
+ * Whether form initially rendered in shown state.
+ *
+ * @var {bool|attribute} shown
+ * @memberOf editable-form
+ */
+ if(attrs.shown && $parse(attrs.shown)(scope)) {
+ eForm.$show();
+ }
+
+ /**
+ * Action when form losses focus. Values: `cancel|submit|ignore`.
+ * Default is `ignore`.
+ *
+ * @var {string|attribute} blur
+ * @memberOf editable-form
+ */
+ eForm._blur = attrs.blur || editableOptions.blurForm;
+
+ // onbeforesave, onaftersave
+ if(!attrs.ngSubmit && !attrs.submit) {
+ /**
+ * Called after all children `onbeforesave` callbacks but before saving form values
+ * to model.
+ * If at least one children callback returns `non-string` - it will not not be called.
+ * See [editable-form demo](#editable-form) for details.
+ *
+ * @var {method|attribute} onbeforesave
+ * @memberOf editable-form
+ *
+ */
+ if(attrs.onbeforesave) {
+ eForm.$onbeforesave = function() {
+ return $parse(attrs.onbeforesave)(scope, {$data: eForm.$data});
+ };
+ }
+
+ /**
+ * Called when form values are saved to model.
+ * See [editable-form demo](#editable-form) for details.
+ *
+ * @var {method|attribute} onaftersave
+ * @memberOf editable-form
+ *
+ */
+ if(attrs.onaftersave) {
+ eForm.$onaftersave = function() {
+ return $parse(attrs.onaftersave)(scope, {$data: eForm.$data});
+ };
+ }
+
+ elem.bind('submit', function(event) {
+ event.preventDefault();
+ scope.$apply(function() {
+ eForm.$submit();
+ });
+ });
+ }
+
+
+ // click - mark form as clicked to exclude in document click handler
+ elem.bind('click', function(e) {
+ // ignore right/middle button click
+ if (e.which && e.which !== 1) {
+ return;
+ }
+
+ if (eForm.$visible) {
+ eForm._clicked = true;
+ }
+ });
+
+ }
+ };
+ }
+ };
+}]);
+/**
+ * editablePromiseCollection
+ *
+ * Collect results of function calls. Shows waiting if there are promises.
+ * Finally, applies callbacks if:
+ * - onTrue(): all results are true and all promises resolved to true
+ * - onFalse(): at least one result is false or promise resolved to false
+ * - onString(): at least one result is string or promise rejected or promise resolved to string
+ */
+
+angular.module('xeditable').factory('editablePromiseCollection', ['$q', function($q) {
+
+ function promiseCollection() {
+ return {
+ promises: [],
+ hasFalse: false,
+ hasString: false,
+ when: function(result, noPromise) {
+ if (result === false) {
+ this.hasFalse = true;
+ } else if (!noPromise && angular.isObject(result)) {
+ this.promises.push($q.when(result));
+ } else if (angular.isString(result)){
+ this.hasString = true;
+ } else { //result === true || result === undefined || result === null
+ return;
+ }
+ },
+ //callbacks: onTrue, onFalse, onString
+ then: function(callbacks) {
+ callbacks = callbacks || {};
+ var onTrue = callbacks.onTrue || angular.noop;
+ var onFalse = callbacks.onFalse || angular.noop;
+ var onString = callbacks.onString || angular.noop;
+ var onWait = callbacks.onWait || angular.noop;
+
+ var self = this;
+
+ if (this.promises.length) {
+ onWait(true);
+ $q.all(this.promises).then(
+ //all resolved
+ function(results) {
+ onWait(false);
+ //check all results via same `when` method (without checking promises)
+ angular.forEach(results, function(result) {
+ self.when(result, true);
+ });
+ applyCallback();
+ },
+ //some rejected
+ function(error) {
+ onWait(false);
+ onString();
+ }
+ );
+ } else {
+ applyCallback();
+ }
+
+ function applyCallback() {
+ if (!self.hasString && !self.hasFalse) {
+ onTrue();
+ } else if (!self.hasString && self.hasFalse) {
+ onFalse();
+ } else {
+ onString();
+ }
+ }
+
+ }
+ };
+ }
+
+ return promiseCollection;
+
+}]);
+
+/**
+ * editableUtils
+ */
+ angular.module('xeditable').factory('editableUtils', [function() {
+ return {
+ indexOf: function (array, obj) {
+ if (array.indexOf) return array.indexOf(obj);
+
+ for ( var i = 0; i < array.length; i++) {
+ if (obj === array[i]) return i;
+ }
+ return -1;
+ },
+
+ arrayRemove: function (array, value) {
+ var index = this.indexOf(array, value);
+ if (index >= 0) {
+ array.splice(index, 1);
+ }
+ return value;
+ },
+
+ // copy from https://github.com/angular/angular.js/blob/master/src/Angular.js
+ camelToDash: function(str) {
+ var SNAKE_CASE_REGEXP = /[A-Z]/g;
+ return str.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
+ return (pos ? '-' : '') + letter.toLowerCase();
+ });
+ },
+
+ dashToCamel: function(str) {
+ var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
+ var MOZ_HACK_REGEXP = /^moz([A-Z])/;
+ return str.
+ replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
+ return offset ? letter.toUpperCase() : letter;
+ }).
+ replace(MOZ_HACK_REGEXP, 'Moz$1');
+ },
+
+ rename: function (tag, el) {
+ if (el[0] && el[0].attributes) {
+ var newEl = angular.element('<' + tag + '/>');
+ newEl.html(el.html());
+ var attrs = el[0].attributes;
+ for (var i = 0; i < attrs.length; ++i) {
+ newEl.attr(attrs.item(i).nodeName, attrs.item(i).value);
+ }
+ return newEl;
+ }
+ }
+ };
+}]);
+
+/**
+ * editableNgOptionsParser
+ *
+ * see: https://github.com/angular/angular.js/blob/master/src/ng/directive/select.js#L131
+ */
+ angular.module('xeditable').factory('editableNgOptionsParser', [function() {
+ //0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888
+ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;
+
+ function parser(optionsExp) {
+ var match;
+
+ if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
+ throw 'ng-options parse error';
+ }
+
+ var
+ displayFn = match[2] || match[1],
+ valueName = match[4] || match[6],
+ keyName = match[5],
+ groupByFn = match[3] || '',
+ valueFn = match[2] ? match[1] : valueName,
+ valuesFn = match[7],
+ track = match[8],
+ trackFn = track ? match[8] : null;
+
+ var ngRepeat;
+ if (keyName === undefined) { // array
+ ngRepeat = valueName + ' in ' + valuesFn;
+ if (track !== undefined) {
+ ngRepeat += ' track by '+trackFn;
+ }
+ } else { // object
+ ngRepeat = '('+keyName+', '+valueName+') in '+valuesFn;
+ }
+
+ // group not supported yet
+ return {
+ ngRepeat: ngRepeat,
+ locals: {
+ valueName: valueName,
+ keyName: keyName,
+ valueFn: valueFn,
+ displayFn: displayFn
+ }
+ };
+ }
+
+ return parser;
+}]);
+
+/**
+ * editableCombodate
+ *
+ * angular version of https://github.com/vitalets/combodate
+ */
+angular.module('xeditable').factory('editableCombodate', [function() {
+ function Combodate(element, options) {
+ this.$element = angular.element(element);
+
+ if(this.$element[0].nodeName != 'INPUT') {
+ throw 'Combodate should be applied to INPUT element';
+ }
+
+ var currentYear = new Date().getFullYear();
+ this.defaults = {
+ //in this format value stored in original input
+ format: 'YYYY-MM-DD HH:mm',
+ //in this format items in dropdowns are displayed
+ template: 'D / MMM / YYYY H : mm',
+ //initial value, can be `new Date()`
+ value: null,
+ minYear: 1970,
+ maxYear: currentYear,
+ yearDescending: true,
+ minuteStep: 5,
+ secondStep: 1,
+ firstItem: 'empty', //'name', 'empty', 'none'
+ errorClass: null,
+ customClass: '',
+ roundTime: true, // whether to round minutes and seconds if step > 1
+ smartDays: true // whether days in combo depend on selected month: 31, 30, 28
+ };
+
+ this.options = angular.extend({}, this.defaults, options);
+ this.init();
+ }
+
+ Combodate.prototype = {
+ constructor: Combodate,
+ init: function () {
+ this.map = {
+ //key regexp moment.method
+ day: ['D', 'date'],
+ month: ['M', 'month'],
+ year: ['Y', 'year'],
+ hour: ['[Hh]', 'hours'],
+ minute: ['m', 'minutes'],
+ second: ['s', 'seconds'],
+ ampm: ['[Aa]', '']
+ };
+
+ this.$widget = angular.element('<span class="combodate"></span>').html(this.getTemplate());
+
+ this.initCombos();
+
+ if (this.options.smartDays) {
+ var combo = this;
+ this.$widget.find('select').bind('change', function(e) {
+ // update days count if month or year changes
+ if (angular.element(e.target).hasClass('month') || angular.element(e.target).hasClass('year')) {
+ combo.fillCombo('day');
+ }
+ });
+ }
+
+ this.$widget.find('select').css('width', 'auto');
+
+ // hide original input and insert widget
+ this.$element.css('display', 'none').after(this.$widget);
+
+ // set initial value
+ this.setValue(this.$element.val() || this.options.value);
+ },
+
+ /*
+ Replace tokens in template with <select> elements
+ */
+ getTemplate: function() {
+ var tpl = this.options.template;
+ var customClass = this.options.customClass;
+
+ //first pass
+ angular.forEach(this.map, function(v, k) {
+ v = v[0];
+ var r = new RegExp(v+'+');
+ var token = v.length > 1 ? v.substring(1, 2) : v;
+
+ tpl = tpl.replace(r, '{'+token+'}');
+ });
+
+ //replace spaces with &nbsp;
+ tpl = tpl.replace(/ /g, '&nbsp;');
+
+ //second pass
+ angular.forEach(this.map, function(v, k) {
+ v = v[0];
+ var token = v.length > 1 ? v.substring(1, 2) : v;
+
+ tpl = tpl.replace('{'+token+'}', '<select class="'+k+' '+customClass+'"></select>');
+ });
+
+ return tpl;
+ },
+
+ /*
+ Initialize combos that presents in template
+ */
+ initCombos: function() {
+ for (var k in this.map) {
+ var c = this.$widget[0].querySelectorAll('.'+k);
+ // set properties like this.$day, this.$month etc.
+ this['$'+k] = c.length ? angular.element(c) : null;
+ // fill with items
+ this.fillCombo(k);
+ }
+ },
+
+ /*
+ Fill combo with items
+ */
+ fillCombo: function(k) {
+ var $combo = this['$'+k];
+ if (!$combo) {
+ return;
+ }
+
+ // define method name to fill items, e.g `fillDays`
+ var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1);
+ var items = this[f]();
+ var value = $combo.val();
+
+ $combo.html('');
+ for(var i=0; i<items.length; i++) {
+ $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
+ }
+
+ $combo.val(value);
+ },
+
+ /*
+ Initialize items of combos. Handles `firstItem` option
+ */
+ fillCommon: function(key) {
+ var values = [], relTime;
+
+ if(this.options.firstItem === 'name') {
+ //need both to support moment ver < 2 and >= 2
+ relTime = moment.relativeTime || moment.langData()._relativeTime;
+ var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
+ //take last entry (see momentjs lang files structure)
+ header = header.split(' ').reverse()[0];
+ values.push(['', header]);
+ } else if(this.options.firstItem === 'empty') {
+ values.push(['', '']);
+ }
+ return values;
+ },
+
+
+ /*
+ fill day
+ */
+ fillDay: function() {
+ var items = this.fillCommon('d'), name, i,
+ twoDigit = this.options.template.indexOf('DD') !== -1,
+ daysCount = 31;
+
+ // detect days count (depends on month and year)
+ // originally https://github.com/vitalets/combodate/pull/7
+ if (this.options.smartDays && this.$month && this.$year) {
+ var month = parseInt(this.$month.val(), 10);
+ var year = parseInt(this.$year.val(), 10);
+
+ if (!isNaN(month) && !isNaN(year)) {
+ daysCount = moment([year, month]).daysInMonth();
+ }
+ }
+
+ for (i = 1; i <= daysCount; i++) {
+ name = twoDigit ? this.leadZero(i) : i;
+ items.push([i, name]);
+ }
+ return items;
+ },
+
+ /*
+ fill month
+ */
+ fillMonth: function() {
+ var items = this.fillCommon('M'), name, i,
+ longNames = this.options.template.indexOf('MMMM') !== -1,
+ shortNames = this.options.template.indexOf('MMM') !== -1,
+ twoDigit = this.options.template.indexOf('MM') !== -1;
+
+ for(i=0; i<=11; i++) {
+ if(longNames) {
+ //see https://github.com/timrwood/momentjs.com/pull/36
+ name = moment().date(1).month(i).format('MMMM');
+ } else if(shortNames) {
+ name = moment().date(1).month(i).format('MMM');
+ } else if(twoDigit) {
+ name = this.leadZero(i+1);
+ } else {
+ name = i+1;
+ }
+ items.push([i, name]);
+ }
+ return items;
+ },
+
+ /*
+ fill year
+ */
+ fillYear: function() {
+ var items = [], name, i,
+ longNames = this.options.template.indexOf('YYYY') !== -1;
+
+ for(i=this.options.maxYear; i>=this.options.minYear; i--) {
+ name = longNames ? i : (i+'').substring(2);
+ items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
+ }
+
+ items = this.fillCommon('y').concat(items);
+
+ return items;
+ },
+
+ /*
+ fill hour
+ */
+ fillHour: function() {
+ var items = this.fillCommon('h'), name, i,
+ h12 = this.options.template.indexOf('h') !== -1,
+ h24 = this.options.template.indexOf('H') !== -1,
+ twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
+ min = h12 ? 1 : 0,
+ max = h12 ? 12 : 23;
+
+ for(i=min; i<=max; i++) {
+ name = twoDigit ? this.leadZero(i) : i;
+ items.push([i, name]);
+ }
+ return items;
+ },
+
+ /*
+ fill minute
+ */
+ fillMinute: function() {
+ var items = this.fillCommon('m'), name, i,
+ twoDigit = this.options.template.indexOf('mm') !== -1;
+
+ for(i=0; i<=59; i+= this.options.minuteStep) {
+ name = twoDigit ? this.leadZero(i) : i;
+ items.push([i, name]);
+ }
+ return items;
+ },
+
+ /*
+ fill second
+ */
+ fillSecond: function() {
+ var items = this.fillCommon('s'), name, i,
+ twoDigit = this.options.template.indexOf('ss') !== -1;
+
+ for(i=0; i<=59; i+= this.options.secondStep) {
+ name = twoDigit ? this.leadZero(i) : i;
+ items.push([i, name]);
+ }
+ return items;
+ },
+
+ /*
+ fill ampm
+ */
+ fillAmpm: function() {
+ var ampmL = this.options.template.indexOf('a') !== -1,
+ ampmU = this.options.template.indexOf('A') !== -1,
+ items = [
+ ['am', ampmL ? 'am' : 'AM'],
+ ['pm', ampmL ? 'pm' : 'PM']
+ ];
+ return items;
+ },
+
+ /*
+ Returns current date value from combos.
+ If format not specified - `options.format` used.
+ If format = `null` - Moment object returned.
+ */
+ getValue: function(format) {
+ var dt, values = {},
+ that = this,
+ notSelected = false;
+
+ //getting selected values
+ angular.forEach(this.map, function(v, k) {
+ if(k === 'ampm') {
+ return;
+ }
+ var def = k === 'day' ? 1 : 0;
+
+ values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;
+
+ if(isNaN(values[k])) {
+ notSelected = true;
+ return false;
+ }
+ });
+
+ //if at least one visible combo not selected - return empty string
+ if(notSelected) {
+ return '';
+ }
+
+ //convert hours 12h --> 24h
+ if(this.$ampm) {
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
+ if(values.hour === 12) {
+ values.hour = this.$ampm.val() === 'am' ? 0 : 12;
+ } else {
+ values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
+ }
+ }
+
+ dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
+
+ //highlight invalid date
+ this.highlight(dt);
+
+ format = format === undefined ? this.options.format : format;
+ if(format === null) {
+ return dt.isValid() ? dt : null;
+ } else {
+ return dt.isValid() ? dt.format(format) : '';
+ }
+ },
+
+ setValue: function(value) {
+ if(!value) {
+ return;
+ }
+
+ // parse in strict mode (third param `true`)
+ var dt = typeof value === 'string' ? moment(value, this.options.format, true) : moment(value),
+ that = this,
+ values = {};
+
+ //function to find nearest value in select options
+ function getNearest($select, value) {
+ var delta = {};
+ angular.forEach($select.children('option'), function(opt, i){
+ var optValue = angular.element(opt).attr('value');
+
+ if(optValue === '') return;
+ var distance = Math.abs(optValue - value);
+ if(typeof delta.distance === 'undefined' || distance < delta.distance) {
+ delta = {value: optValue, distance: distance};
+ }
+ });
+ return delta.value;
+ }
+
+ if(dt.isValid()) {
+ //read values from date object
+ angular.forEach(this.map, function(v, k) {
+ if(k === 'ampm') {
+ return;
+ }
+ values[k] = dt[v[1]]();
+ });
+
+ if(this.$ampm) {
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
+ if(values.hour >= 12) {
+ values.ampm = 'pm';
+ if(values.hour > 12) {
+ values.hour -= 12;
+ }
+ } else {
+ values.ampm = 'am';
+ if(values.hour === 0) {
+ values.hour = 12;
+ }
+ }
+ }
+
+ angular.forEach(values, function(v, k) {
+ //call val() for each existing combo, e.g. this.$hour.val()
+ if(that['$'+k]) {
+
+ if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
+ v = getNearest(that['$'+k], v);
+ }
+
+ if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
+ v = getNearest(that['$'+k], v);
+ }
+
+ that['$'+k].val(v);
+ }
+ });
+
+ // update days count
+ if (this.options.smartDays) {
+ this.fillCombo('day');
+ }
+
+ this.$element.val(dt.format(this.options.format)).triggerHandler('change');
+ }
+ },
+
+ /*
+ highlight combos if date is invalid
+ */
+ highlight: function(dt) {
+ if(!dt.isValid()) {
+ if(this.options.errorClass) {
+ this.$widget.addClass(this.options.errorClass);
+ } else {
+ //store original border color
+ if(!this.borderColor) {
+ this.borderColor = this.$widget.find('select').css('border-color');
+ }
+ this.$widget.find('select').css('border-color', 'red');
+ }
+ } else {
+ if(this.options.errorClass) {
+ this.$widget.removeClass(this.options.errorClass);
+ } else {
+ this.$widget.find('select').css('border-color', this.borderColor);
+ }
+ }
+ },
+
+ leadZero: function(v) {
+ return v <= 9 ? '0' + v : v;
+ },
+
+ destroy: function() {
+ this.$widget.remove();
+ this.$element.removeData('combodate').show();
+ }
+
+ };
+
+ return {
+ getInstance: function(element, options) {
+ return new Combodate(element, options);
+ }
+ };
+}]);
+
+/*
+Editable icons:
+- default
+- font-awesome
+
+*/
+angular.module('xeditable').factory('editableIcons', function() {
+
+ var icons = {
+ //Icon-set to use, defaults to bootstrap icons
+ default: {
+ 'bs2': {
+ ok: 'icon-ok icon-white',
+ cancel: 'icon-remove',
+ clear: 'icon-trash'
+ },
+ 'bs3': {
+ ok: 'glyphicon glyphicon-ok',
+ cancel: 'glyphicon glyphicon-remove',
+ clear: 'glyphicon glyphicon-trash'
+ }
+ },
+ external: {
+ 'font-awesome': {
+ ok: 'fa fa-check',
+ cancel: 'fa fa-times',
+ clear: 'fa fa-trash'
+ }
+ }
+ };
+
+ return icons;
+});
+
+/* jshint -W086 */
+/*
+Editable themes:
+- default
+- bootstrap 2
+- bootstrap 3
+- semantic-ui
+
+Note: in postrender() `this` is instance of editableController
+*/
+angular.module('xeditable').factory('editableThemes', function() {
+ var themes = {
+ //default
+ 'default': {
+ formTpl: '<form class="editable-wrap"></form>',
+ noformTpl: '<span class="editable-wrap"></span>',
+ controlsTpl: '<span class="editable-controls"></span>',
+ inputTpl: '',
+ errorTpl: '<div class="editable-error" data-ng-if="$error" data-ng-bind-html="$error"></div>',
+ buttonsTpl: '<span class="editable-buttons"></span>',
+ submitTpl: '<button type="submit">save</button>',
+ cancelTpl: '<button type="button" ng-click="$form.$cancel()">cancel</button>',
+ resetTpl: '<button type="reset">clear</button>'
+ },
+
+ //bs2
+ 'bs2': {
+ formTpl: '<form class="form-inline editable-wrap" role="form"></form>',
+ noformTpl: '<span class="editable-wrap"></span>',
+ controlsTpl: '<div class="editable-controls controls control-group" ng-class="{\'error\': $error}"></div>',
+ inputTpl: '',
+ errorTpl: '<div class="editable-error help-block" data-ng-if="$error" data-ng-bind-html="$error"></div>',
+ buttonsTpl: '<span class="editable-buttons"></span>',
+ submitTpl: '<button type="submit" class="btn btn-primary"><span></span></button>',
+ cancelTpl: '<button type="button" class="btn" ng-click="$form.$cancel()">'+
+ '<span></span>'+
+ '</button>',
+ resetTpl: '<button type="reset" class="btn btn-danger">clear</button>'
+
+ },
+
+ //bs3
+ 'bs3': {
+ formTpl: '<form class="form-inline editable-wrap" role="form"></form>',
+ noformTpl: '<span class="editable-wrap"></span>',
+ controlsTpl: '<div class="editable-controls form-group" ng-class="{\'has-error\': $error}"></div>',
+ inputTpl: '',
+ errorTpl: '<div class="editable-error help-block" data-ng-if="$error" data-ng-bind-html="$error"></div>',
+ buttonsTpl: '<span class="editable-buttons"></span>',
+ submitTpl: '<button type="submit" class="btn btn-primary"><span></span></button>',
+ cancelTpl: '<button type="button" class="btn btn-default" ng-click="$form.$cancel()">'+
+ '<span></span>'+
+ '</button>',
+ resetTpl: '<button type="reset" class="btn btn-danger">clear</button>',
+
+ //bs3 specific prop to change buttons class: btn-sm, btn-lg
+ buttonsClass: '',
+ //bs3 specific prop to change standard inputs class: input-sm, input-lg
+ inputClass: '',
+ postrender: function() {
+ //apply `form-control` class to std inputs
+ switch(this.directiveName) {
+ case 'editableText':
+ case 'editableSelect':
+ case 'editableTextarea':
+ case 'editableEmail':
+ case 'editableTel':
+ case 'editableNumber':
+ case 'editableUrl':
+ case 'editableSearch':
+ case 'editableDate':
+ case 'editableDatetime':
+ case 'editableBsdate':
+ case 'editableTime':
+ case 'editableMonth':
+ case 'editableWeek':
+ case 'editablePassword':
+ case 'editableDatetimeLocal':
+ this.inputEl.addClass('form-control');
+ if(this.theme.inputClass) {
+ // don`t apply `input-sm` and `input-lg` to select multiple
+ // should be fixed in bs itself!
+ if(this.inputEl.attr('multiple') &&
+ (this.theme.inputClass === 'input-sm' || this.theme.inputClass === 'input-lg')) {
+ break;
+ }
+ this.inputEl.addClass(this.theme.inputClass);
+ }
+ break;
+ case 'editableCheckbox':
+ this.editorEl.addClass('checkbox');
+ }
+
+ //apply buttonsClass (bs3 specific!)
+ if(this.buttonsEl && this.theme.buttonsClass) {
+ this.buttonsEl.find('button').addClass(this.theme.buttonsClass);
+ }
+ }
+ },
+
+ //semantic-ui
+ 'semantic': {
+ formTpl: '<form class="editable-wrap ui form" ng-class="{\'error\': $error}" role="form"></form>',
+ noformTpl: '<span class="editable-wrap"></span>',
+ controlsTpl: '<div class="editable-controls ui fluid input" ng-class="{\'error\': $error}"></div>',
+ inputTpl: '',
+ errorTpl: '<div class="editable-error ui error message" data-ng-if="$error" data-ng-bind-html="$error"></div>',
+ buttonsTpl: '<span class="mini ui buttons"></span>',
+ submitTpl: '<button type="submit" class="ui primary button"><i class="ui check icon"></i></button>',
+ cancelTpl: '<button type="button" class="ui button" ng-click="$form.$cancel()">'+
+ '<i class="ui cancel icon"></i>'+
+ '</button>',
+ resetTpl: '<button type="reset" class="ui button">clear</button>'
+ }
+ };
+
+ return themes;
+});
diff --git a/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.min.js b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.min.js
new file mode 100755
index 0000000..1c06f95
--- /dev/null
+++ b/3rd_party/static/testapi-ui/assets/lib/angular-xeditable-0.8.0/js/xeditable.min.js
@@ -0,0 +1,7 @@
+/*!
+angular-xeditable - 0.8.0
+Edit-in-place for angular.js
+Build date: 2017-06-06
+*/
+angular.module("xeditable",[]).value("editableOptions",{theme:"default",icon_set:"default",buttons:"right",blurElem:"cancel",blurForm:"ignore",activate:"focus",isDisabled:!1,activationEvent:"click",submitButtonTitle:"Submit",submitButtonAriaLabel:"Submit",cancelButtonTitle:"Cancel",cancelButtonAriaLabel:"Cancel",clearButtonTitle:"Clear",clearButtonAriaLabel:"Clear",displayClearButton:!1}),angular.module("xeditable").directive("editableBsdate",["editableDirectiveFactory","$injector","$parse",function(a,b,c){uibDatepickerConfig=b.get("uibDatepickerConfig"),uibDatepickerPopupConfig=b.get("uibDatepickerPopupConfig");var d=[["eIsOpen","is-open"],["eDateDisabled","date-disabled"],["eDatepickerPopup","uib-datepicker-popup"],["eShowButtonBar","show-button-bar"],["eCurrentText","current-text"],["eClearText","clear-text"],["eCloseText","close-text"],["eCloseOnDateSelection","close-on-date-selection"],["eDatepickerAppendToBody","datepicker-append-to-body"],["eOnOpenFocus","on-open-focus"],["eName","name"],["eDateDisabled","date-disabled"],["eAltInputFormats","alt-input-formats"]],e=[["eFormatDay","formatDay"],["eFormatMonth","formatMonth"],["eFormatYear","formatYear"],["eFormatDayHeader","formatDayHeader"],["eFormatDayTitle","formatDayTitle"],["eFormatMonthTitle","formatMonthTitle"],["eMaxMode","maxMode"],["eMinMode","minMode"],["eDatepickerMode","datepickerMode"]];return a({directiveName:"editableBsdate",inputTpl:"<div></div>",render:function(){this.parent.render.call(this);var a=this.attrs,b=this.scope,f=angular.element('<input type="text" class="form-control" ng-model="$parent.$data"/>');f.attr("uib-datepicker-popup",a.eDatepickerPopupXEditable||uibDatepickerPopupConfig.datepickerPopup),f.attr("year-range",a.eYearRange||20),f.attr("ng-readonly",a.eReadonly||!1);for(var g=d.length-1;g>=0;g--){var h=a[d[g][0]];"undefined"!=typeof h&&f.attr(d[g][1],h)}a.eNgChange&&(f.attr("ng-change",a.eNgChange),this.inputEl.removeAttr("ng-change")),a.eStyle&&(f.attr("style",a.eStyle),this.inputEl.removeAttr("style"));var i={maxDate:b.$eval(a.eMaxDate)||uibDatepickerConfig.maxDate,minDate:b.$eval(a.eMinDate)||uibDatepickerConfig.minDate,showWeeks:a.eShowWeeks?"true"===a.eShowWeeks.toLowerCase():uibDatepickerConfig.showWeeks,startingDay:a.eStartingDay||0,initDate:b.$eval(a.eInitDate)||new Date};if(a.eDatepickerOptions){var j=c(a.eDatepickerOptions)(b);angular.extend(i,j)}for(var k=e.length-1;k>=0;k--){var l=a[e[k][0]];"undefined"!=typeof l&&(i[e[k][1]]=l)}b.dateOptions=i;var m=angular.isDefined(a.eShowCalendarButton)?a.eShowCalendarButton:"true";if("true"===m){var n=angular.element('<button type="button" class="btn btn-default"><i class="glyphicon glyphicon-calendar"></i></button>'),o=angular.element('<span class="input-group-btn"></span>');n.attr("ng-click",a.eNgClick),o.append(n),this.inputEl.append(o)}else f.attr("ng-click",a.eNgClick);f.attr("datepicker-options","dateOptions"),this.inputEl.prepend(f),this.inputEl.removeAttr("class"),this.inputEl.removeAttr("ng-click"),this.inputEl.removeAttr("is-open"),this.inputEl.removeAttr("init-date"),this.inputEl.removeAttr("datepicker-popup"),this.inputEl.removeAttr("required"),this.inputEl.removeAttr("ng-model"),this.inputEl.removeAttr("date-picker-append-to-body"),this.inputEl.removeAttr("name"),this.inputEl.attr("class","input-group")},autosubmit:function(){var a=this;a.inputEl.bind("change",function(){setTimeout(function(){a.scope.$apply(function(){a.scope.$form.$submit()})},500)}),a.inputEl.bind("keydown",function(b){9===b.keyCode&&"submit"===a.editorEl.attr("blur")&&a.scope.$apply(function(){a.scope.$form.$submit()})})}})}]),angular.module("xeditable").directive("editableBstime",["editableDirectiveFactory",function(a){return a({directiveName:"editableBstime",inputTpl:"<div uib-timepicker></div>",render:function(){this.parent.render.call(this);var a=angular.element('<div class="well well-small" style="display:inline-block;"></div>');a.attr("ng-model",this.inputEl.attr("ng-model")),this.inputEl.removeAttr("ng-model"),this.attrs.eNgChange&&(a.attr("ng-change",this.inputEl.attr("ng-change")),this.inputEl.removeAttr("ng-change")),this.inputEl.wrap(a)}})}]),angular.module("xeditable").directive("editableCheckbox",["editableDirectiveFactory",function(a){return a({directiveName:"editableCheckbox",inputTpl:'<input type="checkbox">',render:function(){this.parent.render.call(this),this.inputEl.wrap("<label></label>"),this.attrs.eTitle&&this.inputEl.parent().append("<span>"+this.attrs.eTitle+"</span>")},autosubmit:function(){var a=this;a.inputEl.bind("change",function(){setTimeout(function(){a.scope.$apply(function(){a.scope.$form.$submit()})},500)})}})}]),angular.module("xeditable").directive("editableChecklist",["editableDirectiveFactory","editableNgOptionsParser",function(a,b){return a({directiveName:"editableChecklist",inputTpl:"<span></span>",useCopy:!0,render:function(){this.parent.render.call(this);var a=b(this.attrs.eNgOptions),c="",d="";this.attrs.eNgChange&&(c=' ng-change="'+this.attrs.eNgChange+'"'),this.attrs.eChecklistComparator&&(d=' checklist-comparator="'+this.attrs.eChecklistComparator+'"');var e='<label ng-repeat="'+a.ngRepeat+'"><input type="checkbox" checklist-model="$parent.$parent.$data" checklist-value="'+a.locals.valueFn+'"'+c+d+'><span ng-bind="'+a.locals.displayFn+'"></span></label>';this.inputEl.removeAttr("ng-model"),this.inputEl.removeAttr("ng-options"),this.inputEl.removeAttr("ng-change"),this.inputEl.removeAttr("checklist-comparator"),this.inputEl.html(e)}})}]),angular.module("xeditable").directive("editableCombodate",["editableDirectiveFactory","editableCombodate",function(a,b){return a({directiveName:"editableCombodate",inputTpl:'<input type="text">',render:function(){this.parent.render.call(this);var a={value:new Date(this.scope.$data)},c=this;angular.forEach(["format","template","minYear","maxYear","yearDescending","minuteStep","secondStep","firstItem","errorClass","customClass","roundTime","smartDays"],function(b){var d="e"+b.charAt(0).toUpperCase()+b.slice(1);d in c.attrs&&("minYear"==b||"maxYear"==b||"minuteStep"==b||"secondStep"==b?a[b]=parseInt(c.attrs[d],10):a[b]=c.attrs[d])});var d=b.getInstance(this.inputEl,a);d.$widget.find("select").bind("change",function(a){c.scope.$data=d.getValue()?new Date(d.getValue().replace(/-/g,"/")).toISOString():null})}})}]),function(){var a=function(a){return a.toLowerCase().replace(/-(.)/g,function(a,b){return b.toUpperCase()})},b="text|password|email|tel|number|url|search|color|date|datetime|datetime-local|time|month|week|file".split("|");angular.forEach(b,function(b){var c=a("editable-"+b);angular.module("xeditable").directive(c,["editableDirectiveFactory",function(a){return a({directiveName:c,inputTpl:'<input type="'+b+'">',render:function(){if(this.parent.render.call(this),this.attrs.eInputgroupleft||this.attrs.eInputgroupright){if(this.inputEl.wrap('<div class="input-group"></div>'),this.attrs.eInputgroupleft){var a=angular.element('<span class="input-group-addon">'+this.attrs.eInputgroupleft+"</span>");this.inputEl.parent().prepend(a)}if(this.attrs.eInputgroupright){var b=angular.element('<span class="input-group-addon">'+this.attrs.eInputgroupright+"</span>");this.inputEl.parent().append(b)}}if(this.attrs.eLabel){var c=angular.element("<label>"+this.attrs.eLabel+"</label>");this.attrs.eInputgroupleft||this.attrs.eInputgroupright?this.inputEl.parent().parent().prepend(c):this.inputEl.parent().prepend(c)}this.attrs.eFormclass&&this.editorEl.addClass(this.attrs.eFormclass)},autosubmit:function(){var a=this;a.inputEl.bind("keydown",function(b){9===b.keyCode&&"submit"===a.editorEl.attr("blur")&&a.scope.$apply(function(){a.scope.$form.$submit()})})}})}])}),angular.module("xeditable").directive("editableRange",["editableDirectiveFactory","$interpolate",function(a,b){return a({directiveName:"editableRange",inputTpl:'<input type="range" id="range" name="range">',render:function(){this.parent.render.call(this),this.inputEl.after("<output>"+b.startSymbol()+"$data"+b.endSymbol()+"</output>")}})}])}(),angular.module("xeditable").directive("editableTagsInput",["editableDirectiveFactory","editableUtils",function(a,b){var c=a({directiveName:"editableTagsInput",inputTpl:"<tags-input></tags-input>",useCopy:!0,render:function(){this.parent.render.call(this),this.inputEl.append(b.rename("auto-complete",this.attrs.$autoCompleteElement)),this.inputEl.removeAttr("ng-model"),this.inputEl.attr("ng-model","$parent.$data")}}),d=c.link;return c.link=function(a,b,c,e){var f=b.find("editable-tags-input-auto-complete");return c.$autoCompleteElement=f.clone(),f.remove(),d(a,b,c,e)},c}]),angular.module("xeditable").directive("editableRadiolist",["editableDirectiveFactory","editableNgOptionsParser","$interpolate",function(a,b,c){return a({directiveName:"editableRadiolist",inputTpl:"<span></span>",render:function(){this.parent.render.call(this);var a=b(this.attrs.eNgOptions),d="",e="";this.attrs.eNgChange&&(d=' ng-change="'+this.attrs.eNgChange+'"'),this.attrs.eName&&(e=' name="'+this.attrs.eName+'"');var f='<label data-ng-repeat="'+a.ngRepeat+'"><input type="radio" data-ng-disabled="::'+this.attrs.eNgDisabled+'" data-ng-model="$parent.$parent.$data" data-ng-value="'+c.startSymbol()+"::"+a.locals.valueFn+c.endSymbol()+'"'+d+e+'><span data-ng-bind="::'+a.locals.displayFn+'"></span></label>';this.inputEl.removeAttr("ng-model"),this.inputEl.removeAttr("ng-options"),this.inputEl.removeAttr("ng-change"),this.inputEl.html(f)},autosubmit:function(){var a=this;a.inputEl.bind("change",function(){setTimeout(function(){a.scope.$apply(function(){a.scope.$form.$submit()})},500)})}})}]),angular.module("xeditable").directive("editableSelect",["editableDirectiveFactory",function(a){return a({directiveName:"editableSelect",inputTpl:"<select></select>",render:function(){if(this.parent.render.call(this),this.attrs.ePlaceholder){var a=angular.element('<option value="">'+this.attrs.ePlaceholder+"</option>");this.inputEl.append(a)}},autosubmit:function(){var a=this;a.attrs.hasOwnProperty("eMultiple")||a.inputEl.bind("change",function(){a.scope.$apply(function(){a.scope.$form.$submit()})})}})}]),angular.module("xeditable").directive("editableTextarea",["editableDirectiveFactory",function(a){return a({directiveName:"editableTextarea",inputTpl:"<textarea></textarea>",addListeners:function(){var a=this;a.parent.addListeners.call(a),a.single&&"no"!==a.buttons&&a.autosubmit()},autosubmit:function(){var a=this;a.inputEl.bind("keydown",function(b){a.attrs.submitOnEnter?13!==b.keyCode||b.shiftKey||a.scope.$apply(function(){a.scope.$form.$submit()}):((b.ctrlKey||b.metaKey)&&13===b.keyCode||9===b.keyCode&&"submit"===a.editorEl.attr("blur"))&&a.scope.$apply(function(){a.scope.$form.$submit()})})}})}]),angular.module("xeditable").directive("editableUidate",["editableDirectiveFactory",function(a){return a({directiveName:"editableUidate",inputTpl:'<input class="form-control" />',render:function(){this.parent.render.call(this),this.inputEl.attr("ui-date",this.attrs.eUiDate),this.inputEl.attr("placeholder",this.attrs.ePlaceholder)}})}]),angular.module("xeditable").directive("editableUiSelect",["editableDirectiveFactory","editableUtils",function(a,b){var c=a({directiveName:"editableUiSelect",inputTpl:"<ui-select></ui-select>",render:function(){this.parent.render.call(this),this.inputEl.append(b.rename("ui-select-match",this.attrs.$matchElement)),this.inputEl.append(b.rename("ui-select-choices",this.attrs.$choicesElement)),this.inputEl.removeAttr("ng-model"),this.inputEl.attr("ng-model","$parent.$parent.$data")},autosubmit:function(){var a=this;a.inputEl.bind("change",function(){setTimeout(function(){a.scope.$apply(function(){a.scope.$form.$submit()})},500)}),a.inputEl.bind("keydown",function(b){9===b.keyCode&&"submit"===a.editorEl.attr("blur")&&a.scope.$apply(function(){a.scope.$form.$submit()})})}}),d=c.link;return c.link=function(a,b,c,e){var f=b.find("editable-ui-select-match"),g=b.find("editable-ui-select-choices");return c.$matchElement=f.clone(),c.$choicesElement=g.clone(),f.remove(),g.remove(),d(a,b,c,e)},c}]),angular.module("xeditable").factory("editableController",["$q","editableUtils",function(a,b){function c(a,c,d,e,f,g,h,i,j,k,l,m){var n,o,p=this;p.scope=a,p.elem=d,p.attrs=c,p.inputEl=null,p.editorEl=null,p.single=!0,p.error="",p.theme=f[c.editableTheme]||f[h.theme]||f["default"],p.parent={};var q=c.editableTheme||h.theme||"default",r=c.editableIconSet||h.icon_set;p.icon_set="default"===r?g["default"][q]:g.external[r],p.inputTpl="",p.directiveName="",p.useCopy=!1,p.single=null,p.buttons="right",p.popover=!1,p.init=function(b){if(p.single=b,p.name=c.eName||c[p.directiveName],!c[p.directiveName])throw"You should provide value for `"+p.directiveName+"` in editable element!";n=e(c[p.directiveName]),p.single?p.buttons=p.attrs.buttons||h.buttons:p.buttons="no",c.eName&&p.scope.$watch("$data",function(a){p.scope.$form.$data[c.eName]=a}),c.onshow&&(p.onshow=function(){return p.catchError(e(c.onshow)(a))}),c.onhide&&(p.onhide=function(){return e(c.onhide)(a)}),c.oncancel&&(p.oncancel=function(){return e(c.oncancel)(a)}),c.onbeforesave&&(p.onbeforesave=function(){return p.catchError(e(c.onbeforesave)(a))}),c.onaftersave&&(p.onaftersave=function(){return p.catchError(e(c.onaftersave)(a))}),c.popover&&(p.popover=p.attrs.popover),a.$parent.$watch(c[p.directiveName],function(a,b){p.setLocalValue(),p.handleEmpty()})},p.render=function(){var a=p.theme;p.inputEl=angular.element(p.inputTpl),p.controlsEl=angular.element(a.controlsTpl),p.controlsEl.append(p.inputEl),"no"!==p.buttons&&(p.buttonsEl=angular.element(a.buttonsTpl),p.submitEl=angular.element(a.submitTpl),p.resetEl=angular.element(a.resetTpl),p.cancelEl=angular.element(a.cancelTpl),p.submitEl.attr("title",h.submitButtonTitle),p.submitEl.attr("aria-label",h.submitButtonAriaLabel),p.cancelEl.attr("title",h.cancelButtonTitle),p.cancelEl.attr("aria-label",h.cancelButtonAriaLabel),p.resetEl.attr("title",h.clearButtonTitle),p.resetEl.attr("aria-label",h.clearButtonAriaLabel),p.icon_set&&(p.submitEl.find("span").addClass(p.icon_set.ok),p.cancelEl.find("span").addClass(p.icon_set.cancel),p.resetEl.find("span").addClass(p.icon_set.clear)),p.buttonsEl.append(p.submitEl).append(p.cancelEl),h.displayClearButton&&p.buttonsEl.append(p.resetEl),p.controlsEl.append(p.buttonsEl),p.inputEl.addClass("editable-has-buttons")),p.errorEl=angular.element(a.errorTpl),p.controlsEl.append(p.errorEl),p.editorEl=angular.element(p.single?a.formTpl:a.noformTpl),p.editorEl.append(p.controlsEl);for(var d in c.$attr)if(!(d.length<=1)){var e=!1,f=d.substring(1,2);if("e"===d.substring(0,1)&&f===f.toUpperCase()&&(e=d.substring(1),"Form"!==e&&"NgSubmit"!==e)){var g=e.substring(0,1),i=e.substring(1,2);e=i===i.toUpperCase()&&g===g.toUpperCase()?g.toLowerCase()+"-"+b.camelToDash(e.substring(1)):g.toLowerCase()+b.camelToDash(e.substring(1));var j="value"!==e&&""===c[d]?e:c[d];p.inputEl.attr(e,j)}}if(p.inputEl.addClass("editable-input"),p.inputEl.attr("ng-model","$parent.$data"),p.editorEl.addClass(b.camelToDash(p.directiveName)),p.single&&(p.editorEl.attr("editable-form","$form"),p.editorEl.attr("blur",p.attrs.blur||h.blurElem)),p.popover){var k=angular.element("<div></div>");k.append(p.editorEl),p.editorEl=k,m.put("popover.html",p.editorEl[0].outerHTML)}angular.isFunction(a.postrender)&&a.postrender.call(p)},p.setLocalValue=function(){p.scope.$data=p.useCopy?angular.copy(n(a.$parent)):n(a.$parent)};var s=null;p.show=function(){return p.setLocalValue(),p.render(),d.after(p.editorEl),s=a.$new(),j(p.editorEl)(s),p.addListeners(),d.addClass("editable-hide"),p.onshow()},p.hide=function(){return s.$destroy(),p.controlsEl.remove(),p.editorEl.remove(),d.removeClass("editable-hide"),p.popover&&m.remove("popover.html"),p.onhide()},p.cancel=function(){p.oncancel()},p.addListeners=function(){p.inputEl.bind("keyup",function(a){if(p.single)switch(a.keyCode){case 27:p.scope.$apply(function(){p.scope.$form.$cancel()})}}),p.single&&"no"===p.buttons&&p.autosubmit(),p.editorEl.bind("click",function(a){a.which&&1!==a.which||p.scope.$form.$visible&&(p.scope.$form._clicked=!0)})},p.setWaiting=function(a){a?(o=!p.inputEl.attr("disabled")&&!p.inputEl.attr("ng-disabled")&&!p.inputEl.attr("ng-enabled"),o&&(p.inputEl.attr("disabled","disabled"),p.buttonsEl&&p.buttonsEl.find("button").attr("disabled","disabled"))):o&&(p.inputEl.removeAttr("disabled"),p.buttonsEl&&p.buttonsEl.find("button").removeAttr("disabled"))},p.activate=function(a,b){setTimeout(function(){var c=p.inputEl[0];"focus"===h.activate&&c.focus?(void 0!==a&&""!==a&&c.setSelectionRange&&(b=b||a,c.onfocus=function(){setTimeout(function(){try{this.setSelectionRange(a,b)}catch(c){}}.bind(this))}),"editableRadiolist"==p.directiveName||"editableChecklist"==p.directiveName||"editableBsdate"==p.directiveName||"editableTagsInput"==p.directiveName?c.querySelector(".ng-pristine").focus():c.focus()):"select"===h.activate&&(c.select?c.select():c.focus&&c.focus())},0)},p.setError=function(b){angular.isObject(b)||(a.$error=l.trustAsHtml(b),p.error=b)},p.catchError=function(a,b){return angular.isObject(a)&&b!==!0?k.when(a).then(angular.bind(this,function(a){this.catchError(a,!0)}),angular.bind(this,function(a){this.catchError(a,!0)})):b&&angular.isObject(a)&&a.status&&200!==a.status&&a.data&&angular.isString(a.data)?(this.setError(a.data),a=a.data):angular.isString(a)&&this.setError(a),a},p.save=function(){n.assign(a.$parent,p.useCopy?angular.copy(p.scope.$data):p.scope.$data)},p.handleEmpty=function(){var b=n(a.$parent),c=null===b||void 0===b||""===b||angular.isArray(b)&&0===b.length;d.toggleClass("editable-empty",c)},p.autosubmit=angular.noop,p.onshow=angular.noop,p.onhide=angular.noop,p.oncancel=angular.noop,p.onbeforesave=angular.noop,p.onaftersave=angular.noop}return c.$inject=["$scope","$attrs","$element","$parse","editableThemes","editableIcons","editableOptions","$rootScope","$compile","$q","$sce","$templateCache"],c}]),angular.module("xeditable").factory("editableDirectiveFactory",["$parse","$compile","editableThemes","$rootScope","$document","editableController","editableFormController","editableOptions",function(a,b,c,d,e,f,g,h){return function(b){return{restrict:"A",scope:!0,require:[b.directiveName,"?^form"],controller:f,link:function(c,f,i,j){var k,l=j[0],m=!1;if(j[1])k=j[1],m=void 0===i.eSingle;else if(i.eForm){var n=a(i.eForm)(c);if(n)k=n,m=!0;else if(f&&"function"==typeof f.parents&&f.parents().last().find("form[name="+i.eForm+"]").length)k=null,m=!0;else for(var o=0;o<e[0].forms.length;o++)if(e[0].forms[o].name===i.eForm){k=null,m=!0;break}}angular.forEach(b,function(a,b){void 0!==l[b]&&(l.parent[b]=l[b])}),angular.extend(l,b);var p=function(){return angular.isDefined(i.editDisabled)?c.$eval(i.editDisabled):h.isDisabled};if(l.init(!m),c.$editable=l,f.addClass("editable"),m)if(k){if(c.$form=k,!c.$form.$addEditable)throw"Form with editable elements should have `editable-form` attribute.";c.$form.$addEditable(l)}else d.$$editableBuffer=d.$$editableBuffer||{},d.$$editableBuffer[i.eForm]=d.$$editableBuffer[i.eForm]||[],d.$$editableBuffer[i.eForm].push(l),c.$form=null;else c.$form=g(),c.$form.$addEditable(l),i.eForm&&(a(i.eForm).assign||angular.noop)(c.$parent,c.$form),(!i.eForm||i.eClickable)&&(f.addClass("editable-click"),f.bind(h.activationEvent,function(a){a.preventDefault(),a.editable=l,p()||c.$apply(function(){c.$form.$show()})}))}}}}]),angular.module("xeditable").factory("editableFormController",["$parse","$document","$rootScope","editablePromiseCollection","editableUtils",function(a,b,c,d,e){var f=[],g=function(a,b){if(b==a)return!0;for(var c=b.parentNode;null!==c;){if(c==a)return!0;c=c.parentNode}return!1},h=function(a,b){var c=!0,d=a.$editables;return angular.forEach(d,function(a){var d=a.editorEl[0];g(d,b.target)&&(c=!1)}),c};b.bind("click",function(a){if(!a.which||1===a.which){for(var b=[],d=[],e=0;e<f.length;e++)f[e]._clicked?f[e]._clicked=!1:f[e].$waiting||("cancel"===f[e]._blur&&h(f[e],a)&&b.push(f[e]),"submit"===f[e]._blur&&h(f[e],a)&&d.push(f[e]));(b.length||d.length)&&c.$apply(function(){angular.forEach(b,function(a){a.$cancel()}),angular.forEach(d,function(a){a.$submit()})})}}),c.$on("closeEdit",function(){for(var a=0;a<f.length;a++)f[a].$hide()});var i={$addEditable:function(a){this.$editables.push(a),a.elem.bind("$destroy",angular.bind(this,this.$removeEditable,a)),a.scope.$form||(a.scope.$form=this),this.$visible&&a.catchError(a.show()),a.catchError(a.setWaiting(this.$waiting))},$removeEditable:function(a){for(var b=0;b<this.$editables.length;b++)if(this.$editables[b]===a)return void this.$editables.splice(b,1)},$show:function(){if(!this.$visible){this.$visible=!0;var a=d();a.when(this.$onshow()),this.$setError(null,""),angular.forEach(this.$editables,function(b){a.when(b.show())}),a.then({onWait:angular.bind(this,this.$setWaiting),onTrue:angular.bind(this,this.$activate),onFalse:angular.bind(this,this.$activate),onString:angular.bind(this,this.$activate)}),setTimeout(angular.bind(this,function(){this._clicked=!1,-1===e.indexOf(f,this)&&f.push(this)}),0)}},$activate:function(a){var b,c,d;if(this.$editables.length){if(angular.isString(a))for(b=0;b<this.$editables.length;b++)if(this.$editables[b].name===a)return void this.$editables[b].activate();for(b=0;b<this.$editables.length;b++)if(this.$editables[b].error)return void this.$editables[b].activate();c=this.$editables[0].elem[0].selectionStart?this.$editables[0].elem[0].selectionStart:this.$editables[0].elem[0].text?this.$editables[0].elem[0].text.length:this.$editables[0].elem[0].innerHTML?this.$editables[0].elem[0].innerHTML.length:0,d=this.$editables[0].elem[0].selectionEnd?this.$editables[0].elem[0].selectionEnd:this.$editables[0].elem[0].text?this.$editables[0].elem[0].text.length:this.$editables[0].elem[0].innerHTML?this.$editables[0].elem[0].innerHTML.length:0,this.$editables[0].activate(c,d)}},$hide:function(){this.$visible&&(this.$visible=!1,this.$onhide(),angular.forEach(this.$editables,function(a){a.hide()}),e.arrayRemove(f,this))},$cancel:function(){this.$visible&&(this.$oncancel(),angular.forEach(this.$editables,function(a){a.cancel()}),this.$hide())},$setWaiting:function(a){this.$waiting=!!a,angular.forEach(this.$editables,function(b){b.setWaiting(!!a)})},$setError:function(a,b){angular.forEach(this.$editables,function(c){a&&c.name!==a||c.setError(b)})},$submit:function(){function a(a){var b=d();b.when(this.$onbeforesave()),b.then({onWait:angular.bind(this,this.$setWaiting),onTrue:a?angular.bind(this,this.$save):angular.bind(this,this.$hide),onFalse:angular.bind(this,this.$hide),onString:angular.bind(this,this.$activate)})}if(!this.$waiting){this.$setError(null,"");var b=d();angular.forEach(this.$editables,function(a){b.when(a.onbeforesave())}),b.then({onWait:angular.bind(this,this.$setWaiting),onTrue:angular.bind(this,a,!0),onFalse:angular.bind(this,a,!1),onString:angular.bind(this,this.$activate)})}},$save:function(){angular.forEach(this.$editables,function(a){a.save()});var a=d();a.when(this.$onaftersave()),angular.forEach(this.$editables,function(b){a.when(b.onaftersave())}),a.then({onWait:angular.bind(this,this.$setWaiting),onTrue:angular.bind(this,this.$hide),onFalse:angular.bind(this,this.$hide),onString:angular.bind(this,this.$activate)})},$onshow:angular.noop,$oncancel:angular.noop,$onhide:angular.noop,$onbeforesave:angular.noop,$onaftersave:angular.noop};return function(){return angular.extend({$editables:[],$visible:!1,$waiting:!1,$data:{},_clicked:!1,_blur:null},i)}}]),angular.module("xeditable").directive("editableForm",["$rootScope","$parse","editableFormController","editableOptions",function(a,b,c,d){return{restrict:"A",require:["form"],compile:function(){return{pre:function(b,d,e,f){var g,h=f[0];e.editableForm?b[e.editableForm]&&b[e.editableForm].$show?(g=b[e.editableForm],angular.extend(h,g)):(g=c(),b[e.editableForm]=g,angular.extend(g,h)):(g=c(),angular.extend(h,g));var i=a.$$editableBuffer,j=h.$name;j&&i&&i[j]&&(angular.forEach(i[j],function(a){g.$addEditable(a)}),delete i[j])},post:function(a,c,e,f){var g;g=e.editableForm&&a[e.editableForm]&&a[e.editableForm].$show?a[e.editableForm]:f[0],e.onshow&&(g.$onshow=angular.bind(g,b(e.onshow),a)),e.onhide&&(g.$onhide=angular.bind(g,b(e.onhide),a)),e.oncancel&&(g.$oncancel=angular.bind(g,b(e.oncancel),a)),e.shown&&b(e.shown)(a)&&g.$show(),g._blur=e.blur||d.blurForm,e.ngSubmit||e.submit||(e.onbeforesave&&(g.$onbeforesave=function(){return b(e.onbeforesave)(a,{$data:g.$data})}),e.onaftersave&&(g.$onaftersave=function(){return b(e.onaftersave)(a,{$data:g.$data})}),c.bind("submit",function(b){b.preventDefault(),a.$apply(function(){g.$submit()})})),c.bind("click",function(a){a.which&&1!==a.which||g.$visible&&(g._clicked=!0)})}}}}}]),angular.module("xeditable").factory("editablePromiseCollection",["$q",function(a){function b(){return{promises:[],hasFalse:!1,hasString:!1,when:function(b,c){if(b===!1)this.hasFalse=!0;else if(!c&&angular.isObject(b))this.promises.push(a.when(b));else{if(!angular.isString(b))return;this.hasString=!0}},then:function(b){function c(){h.hasString||h.hasFalse?!h.hasString&&h.hasFalse?e():f():d()}b=b||{};var d=b.onTrue||angular.noop,e=b.onFalse||angular.noop,f=b.onString||angular.noop,g=b.onWait||angular.noop,h=this;this.promises.length?(g(!0),a.all(this.promises).then(function(a){g(!1),angular.forEach(a,function(a){h.when(a,!0)}),c()},function(a){g(!1),f()})):c()}}}return b}]),angular.module("xeditable").factory("editableUtils",[function(){return{indexOf:function(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0;c<a.length;c++)if(b===a[c])return c;return-1},arrayRemove:function(a,b){var c=this.indexOf(a,b);return c>=0&&a.splice(c,1),b},camelToDash:function(a){var b=/[A-Z]/g;return a.replace(b,function(a,b){return(b?"-":"")+a.toLowerCase()})},dashToCamel:function(a){var b=/([\:\-\_]+(.))/g,c=/^moz([A-Z])/;return a.replace(b,function(a,b,c,d){return d?c.toUpperCase():c}).replace(c,"Moz$1")},rename:function(a,b){if(b[0]&&b[0].attributes){var c=angular.element("<"+a+"/>");c.html(b.html());for(var d=b[0].attributes,e=0;e<d.length;++e)c.attr(d.item(e).nodeName,d.item(e).value);return c}}}}]),angular.module("xeditable").factory("editableNgOptionsParser",[function(){function a(a){var c;if(!(c=a.match(b)))throw"ng-options parse error";var d,e=c[2]||c[1],f=c[4]||c[6],g=c[5],h=(c[3]||"",c[2]?c[1]:f),i=c[7],j=c[8],k=j?c[8]:null;return void 0===g?(d=f+" in "+i,void 0!==j&&(d+=" track by "+k)):d="("+g+", "+f+") in "+i,{ngRepeat:d,locals:{valueName:f,keyName:g,valueFn:h,displayFn:e}}}var b=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/;return a}]),angular.module("xeditable").factory("editableCombodate",[function(){function a(a,b){if(this.$element=angular.element(a),"INPUT"!=this.$element[0].nodeName)throw"Combodate should be applied to INPUT element";var c=(new Date).getFullYear();this.defaults={format:"YYYY-MM-DD HH:mm",template:"D / MMM / YYYY H : mm",value:null,minYear:1970,maxYear:c,yearDescending:!0,minuteStep:5,secondStep:1,firstItem:"empty",errorClass:null,customClass:"",roundTime:!0,smartDays:!0},this.options=angular.extend({},this.defaults,b),this.init()}return a.prototype={constructor:a,init:function(){if(this.map={day:["D","date"],month:["M","month"],year:["Y","year"],hour:["[Hh]","hours"],minute:["m","minutes"],second:["s","seconds"],ampm:["[Aa]",""]},this.$widget=angular.element('<span class="combodate"></span>').html(this.getTemplate()),this.initCombos(),this.options.smartDays){var a=this;this.$widget.find("select").bind("change",function(b){(angular.element(b.target).hasClass("month")||angular.element(b.target).hasClass("year"))&&a.fillCombo("day")})}this.$widget.find("select").css("width","auto"),this.$element.css("display","none").after(this.$widget),this.setValue(this.$element.val()||this.options.value)},getTemplate:function(){var a=this.options.template,b=this.options.customClass;return angular.forEach(this.map,function(b,c){b=b[0];var d=new RegExp(b+"+"),e=b.length>1?b.substring(1,2):b;a=a.replace(d,"{"+e+"}")}),a=a.replace(/ /g,"&nbsp;"),angular.forEach(this.map,function(c,d){c=c[0];var e=c.length>1?c.substring(1,2):c;a=a.replace("{"+e+"}",'<select class="'+d+" "+b+'"></select>')}),a},initCombos:function(){for(var a in this.map){var b=this.$widget[0].querySelectorAll("."+a);this["$"+a]=b.length?angular.element(b):null,this.fillCombo(a)}},fillCombo:function(a){var b=this["$"+a];if(b){var c="fill"+a.charAt(0).toUpperCase()+a.slice(1),d=this[c](),e=b.val();b.html("");for(var f=0;f<d.length;f++)b.append('<option value="'+d[f][0]+'">'+d[f][1]+"</option>");b.val(e)}},fillCommon:function(a){var b,c=[];if("name"===this.options.firstItem){b=moment.relativeTime||moment.langData()._relativeTime;var d="function"==typeof b[a]?b[a](1,!0,a,!1):b[a];d=d.split(" ").reverse()[0],c.push(["",d])}else"empty"===this.options.firstItem&&c.push(["",""]);return c},fillDay:function(){var a,b,c=this.fillCommon("d"),d=-1!==this.options.template.indexOf("DD"),e=31;if(this.options.smartDays&&this.$month&&this.$year){var f=parseInt(this.$month.val(),10),g=parseInt(this.$year.val(),10);isNaN(f)||isNaN(g)||(e=moment([g,f]).daysInMonth())}for(b=1;e>=b;b++)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillMonth:function(){var a,b,c=this.fillCommon("M"),d=-1!==this.options.template.indexOf("MMMM"),e=-1!==this.options.template.indexOf("MMM"),f=-1!==this.options.template.indexOf("MM");for(b=0;11>=b;b++)a=d?moment().date(1).month(b).format("MMMM"):e?moment().date(1).month(b).format("MMM"):f?this.leadZero(b+1):b+1,c.push([b,a]);return c},fillYear:function(){var a,b,c=[],d=-1!==this.options.template.indexOf("YYYY");for(b=this.options.maxYear;b>=this.options.minYear;b--)a=d?b:(b+"").substring(2),c[this.options.yearDescending?"push":"unshift"]([b,a]);return c=this.fillCommon("y").concat(c)},fillHour:function(){var a,b,c=this.fillCommon("h"),d=-1!==this.options.template.indexOf("h"),e=(-1!==this.options.template.indexOf("H"),-1!==this.options.template.toLowerCase().indexOf("hh")),f=d?1:0,g=d?12:23;for(b=f;g>=b;b++)a=e?this.leadZero(b):b,c.push([b,a]);return c},fillMinute:function(){var a,b,c=this.fillCommon("m"),d=-1!==this.options.template.indexOf("mm");for(b=0;59>=b;b+=this.options.minuteStep)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillSecond:function(){var a,b,c=this.fillCommon("s"),d=-1!==this.options.template.indexOf("ss");for(b=0;59>=b;b+=this.options.secondStep)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillAmpm:function(){var a=-1!==this.options.template.indexOf("a"),b=(-1!==this.options.template.indexOf("A"),[["am",a?"am":"AM"],["pm",a?"pm":"PM"]]);return b},getValue:function(a){var b,c={},d=this,e=!1;return angular.forEach(this.map,function(a,b){if("ampm"!==b){var f="day"===b?1:0;return c[b]=d["$"+b]?parseInt(d["$"+b].val(),10):f,isNaN(c[b])?(e=!0,!1):void 0}}),e?"":(this.$ampm&&(12===c.hour?c.hour="am"===this.$ampm.val()?0:12:c.hour="am"===this.$ampm.val()?c.hour:c.hour+12),b=moment([c.year,c.month,c.day,c.hour,c.minute,c.second]),this.highlight(b),a=void 0===a?this.options.format:a,null===a?b.isValid()?b:null:b.isValid()?b.format(a):"")},setValue:function(a){function b(a,b){var c={};return angular.forEach(a.children("option"),function(a,d){var e=angular.element(a).attr("value");if(""!==e){var f=Math.abs(e-b);("undefined"==typeof c.distance||f<c.distance)&&(c={value:e,distance:f})}}),c.value}if(a){var c="string"==typeof a?moment(a,this.options.format,!0):moment(a),d=this,e={};c.isValid()&&(angular.forEach(this.map,function(a,b){"ampm"!==b&&(e[b]=c[a[1]]())}),this.$ampm&&(e.hour>=12?(e.ampm="pm",e.hour>12&&(e.hour-=12)):(e.ampm="am",0===e.hour&&(e.hour=12))),angular.forEach(e,function(a,c){d["$"+c]&&("minute"===c&&d.options.minuteStep>1&&d.options.roundTime&&(a=b(d["$"+c],a)),"second"===c&&d.options.secondStep>1&&d.options.roundTime&&(a=b(d["$"+c],a)),d["$"+c].val(a))}),this.options.smartDays&&this.fillCombo("day"),this.$element.val(c.format(this.options.format)).triggerHandler("change"));
+}},highlight:function(a){a.isValid()?this.options.errorClass?this.$widget.removeClass(this.options.errorClass):this.$widget.find("select").css("border-color",this.borderColor):this.options.errorClass?this.$widget.addClass(this.options.errorClass):(this.borderColor||(this.borderColor=this.$widget.find("select").css("border-color")),this.$widget.find("select").css("border-color","red"))},leadZero:function(a){return 9>=a?"0"+a:a},destroy:function(){this.$widget.remove(),this.$element.removeData("combodate").show()}},{getInstance:function(b,c){return new a(b,c)}}}]),angular.module("xeditable").factory("editableIcons",function(){var a={"default":{bs2:{ok:"icon-ok icon-white",cancel:"icon-remove",clear:"icon-trash"},bs3:{ok:"glyphicon glyphicon-ok",cancel:"glyphicon glyphicon-remove",clear:"glyphicon glyphicon-trash"}},external:{"font-awesome":{ok:"fa fa-check",cancel:"fa fa-times",clear:"fa fa-trash"}}};return a}),angular.module("xeditable").factory("editableThemes",function(){var a={"default":{formTpl:'<form class="editable-wrap"></form>',noformTpl:'<span class="editable-wrap"></span>',controlsTpl:'<span class="editable-controls"></span>',inputTpl:"",errorTpl:'<div class="editable-error" data-ng-if="$error" data-ng-bind-html="$error"></div>',buttonsTpl:'<span class="editable-buttons"></span>',submitTpl:'<button type="submit">save</button>',cancelTpl:'<button type="button" ng-click="$form.$cancel()">cancel</button>',resetTpl:'<button type="reset">clear</button>'},bs2:{formTpl:'<form class="form-inline editable-wrap" role="form"></form>',noformTpl:'<span class="editable-wrap"></span>',controlsTpl:'<div class="editable-controls controls control-group" ng-class="{\'error\': $error}"></div>',inputTpl:"",errorTpl:'<div class="editable-error help-block" data-ng-if="$error" data-ng-bind-html="$error"></div>',buttonsTpl:'<span class="editable-buttons"></span>',submitTpl:'<button type="submit" class="btn btn-primary"><span></span></button>',cancelTpl:'<button type="button" class="btn" ng-click="$form.$cancel()"><span></span></button>',resetTpl:'<button type="reset" class="btn btn-danger">clear</button>'},bs3:{formTpl:'<form class="form-inline editable-wrap" role="form"></form>',noformTpl:'<span class="editable-wrap"></span>',controlsTpl:'<div class="editable-controls form-group" ng-class="{\'has-error\': $error}"></div>',inputTpl:"",errorTpl:'<div class="editable-error help-block" data-ng-if="$error" data-ng-bind-html="$error"></div>',buttonsTpl:'<span class="editable-buttons"></span>',submitTpl:'<button type="submit" class="btn btn-primary"><span></span></button>',cancelTpl:'<button type="button" class="btn btn-default" ng-click="$form.$cancel()"><span></span></button>',resetTpl:'<button type="reset" class="btn btn-danger">clear</button>',buttonsClass:"",inputClass:"",postrender:function(){switch(this.directiveName){case"editableText":case"editableSelect":case"editableTextarea":case"editableEmail":case"editableTel":case"editableNumber":case"editableUrl":case"editableSearch":case"editableDate":case"editableDatetime":case"editableBsdate":case"editableTime":case"editableMonth":case"editableWeek":case"editablePassword":case"editableDatetimeLocal":if(this.inputEl.addClass("form-control"),this.theme.inputClass){if(this.inputEl.attr("multiple")&&("input-sm"===this.theme.inputClass||"input-lg"===this.theme.inputClass))break;this.inputEl.addClass(this.theme.inputClass)}break;case"editableCheckbox":this.editorEl.addClass("checkbox")}this.buttonsEl&&this.theme.buttonsClass&&this.buttonsEl.find("button").addClass(this.theme.buttonsClass)}},semantic:{formTpl:'<form class="editable-wrap ui form" ng-class="{\'error\': $error}" role="form"></form>',noformTpl:'<span class="editable-wrap"></span>',controlsTpl:'<div class="editable-controls ui fluid input" ng-class="{\'error\': $error}"></div>',inputTpl:"",errorTpl:'<div class="editable-error ui error message" data-ng-if="$error" data-ng-bind-html="$error"></div>',buttonsTpl:'<span class="mini ui buttons"></span>',submitTpl:'<button type="submit" class="ui primary button"><i class="ui check icon"></i></button>',cancelTpl:'<button type="button" class="ui button" ng-click="$form.$cancel()"><i class="ui cancel icon"></i></button>',resetTpl:'<button type="reset" class="ui button">clear</button>'}};return a}); \ No newline at end of file