forms-angular 0.12.0-beta.17 → 0.12.0-beta.170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,12 +9,12 @@ var fng;
9
9
  restrict: 'AE',
10
10
  replace: true,
11
11
  template: '<li ng-show="items.length > 0" class="mcdd" uib-dropdown>' +
12
- ' <a uib-dropdown-toggle>' +
12
+ ' <a href="#" uib-dropdown-toggle>' +
13
13
  ' {{contextMenu}} <b class="caret"></b>' +
14
14
  ' </a>' +
15
15
  ' <ul class="uib-dropdown-menu dropdown-menu">' +
16
16
  ' <li ng-repeat="choice in items" ng-hide="isHidden($index)" ng-class="dropdownClass($index)">' +
17
- ' <a ng-show="choice.text" class="dropdown-option" ng-href="{{choice.url}}" ng-click="doClick($index, $event)">' +
17
+ ' <a ng-show="choice.text" class="dropdown-option" ng-href="{{choice.url || choice.urlFunc()}}" ng-click="doClick($index, $event)">' +
18
18
  ' {{choice.text}}' +
19
19
  ' </a>' +
20
20
  ' </li>' +
@@ -31,16 +31,11 @@ var fng;
31
31
  var directives;
32
32
  (function (directives) {
33
33
  /*@ngInject*/
34
- function errorDisplay() {
34
+ errorDisplay.$inject = ["cssFrameworkService"];
35
+ function errorDisplay(cssFrameworkService) {
35
36
  return {
36
37
  restrict: 'E',
37
- template: '<div id="display-error" ng-show="errorMessage" ng-class="css(\'rowFluid\')">' +
38
- ' <div class="alert alert-error col-lg-offset-3 offset3 col-lg-6 col-xs-12 span6 alert-warning alert-dismissable">' +
39
- ' <button type="button" class="close" ng-click="dismissError()">×</button>' +
40
- ' <h4>{{alertTitle}}</h4>' +
41
- ' <div ng-bind-html="errorMessage"></div>' +
42
- ' </div>' +
43
- '</div>'
38
+ templateUrl: 'error-display-' + cssFrameworkService.framework() + '.html'
44
39
  };
45
40
  }
46
41
  directives.errorDisplay = errorDisplay;
@@ -48,6 +43,44 @@ var fng;
48
43
  })(fng || (fng = {}));
49
44
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
50
45
  var fng;
46
+ (function (fng) {
47
+ var controllers;
48
+ (function (controllers) {
49
+ /*@ngInject*/
50
+ LinkCtrl.$inject = ["$scope"];
51
+ function LinkCtrl($scope) {
52
+ /**
53
+ * In the event that the link is part of a form that belongs to a (ui-bootstrap) modal,
54
+ * close the modal
55
+ */
56
+ $scope.checkNotModal = function () {
57
+ var elm = $scope.element[0];
58
+ var parentNode;
59
+ var finished = false;
60
+ var fakeEvt = {
61
+ preventDefault: angular.noop,
62
+ stopPropagation: angular.noop,
63
+ target: 1,
64
+ currentTarget: 1
65
+ };
66
+ do {
67
+ parentNode = elm.parentNode;
68
+ if (!parentNode) {
69
+ finished = true;
70
+ }
71
+ else if (typeof parentNode.getAttribute === "function" && parentNode.getAttribute('uib-modal-window')) {
72
+ angular.element(elm).scope().close(fakeEvt);
73
+ finished = true;
74
+ }
75
+ else {
76
+ elm = parentNode;
77
+ }
78
+ } while (!finished);
79
+ };
80
+ }
81
+ controllers.LinkCtrl = LinkCtrl;
82
+ })(controllers = fng.controllers || (fng.controllers = {}));
83
+ })(fng || (fng = {}));
51
84
  (function (fng) {
52
85
  var directives;
53
86
  (function (directives) {
@@ -58,38 +91,119 @@ var fng;
58
91
  restrict: 'E',
59
92
  scope: { dataSrc: '&model' },
60
93
  link: function (scope, element, attrs) {
61
- var ref = JSON.parse(attrs['ref']);
94
+ var ref = attrs['ref'];
95
+ var isLabel = attrs['text'] && (unescape(attrs['text']) !== attrs['text']);
62
96
  var form = attrs['form'];
97
+ var linktab = attrs['linktab'];
63
98
  scope['readonly'] = attrs['readonly'];
99
+ scope['element'] = element;
64
100
  form = form ? form + '/' : '';
65
- if (attrs['text'] && attrs['text'].length > 0) {
66
- scope['text'] = attrs['text'];
67
- }
68
- var index = scope['$parent']['$index'];
69
- scope.$watch('dataSrc()', function (newVal) {
70
- if (newVal) {
71
- if (typeof index !== 'undefined' && angular.isArray(newVal)) {
72
- newVal = newVal[index];
73
- }
74
- scope['link'] = routingService.buildUrl(ref.collection + '/' + form + newVal + '/edit');
75
- if (!scope['text']) {
76
- SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
77
- var data = response.data;
78
- if (data.success === false) {
79
- scope['text'] = data.err;
101
+ linktab = linktab ? '/' + linktab : '';
102
+ if (isLabel) {
103
+ var workScope = scope;
104
+ var workString = '';
105
+ while (typeof workScope['baseSchema'] !== "function" && workScope.$parent) {
106
+ if (typeof workScope['$index'] !== "undefined") {
107
+ throw new Error('No support for $index at this level - ' + workString);
108
+ }
109
+ workScope = workScope.$parent;
110
+ workString = workString + '$parent.';
111
+ }
112
+ var attrib = attrs['fld'];
113
+ var watchExpression;
114
+ if (scope.$parent.subDoc && scope.$parent.subDoc[attrib]) {
115
+ // Support for use in directives in arrays
116
+ watchExpression = workString + 'subDoc.' + attrib;
117
+ }
118
+ else {
119
+ if (typeof workScope['$index'] !== "undefined") {
120
+ var splitAttrib = attrib.split('.');
121
+ attrib = splitAttrib.pop();
122
+ attrib = splitAttrib.join('.') + '[' + workScope['$index'] + '].' + attrib;
123
+ }
124
+ else {
125
+ attrib = '.' + attrib;
126
+ }
127
+ watchExpression = workString + 'record' + attrib;
128
+ }
129
+ scope.$watch(watchExpression, function (newVal) {
130
+ if (newVal) {
131
+ if (/^[a-f0-9]{24}/.test(newVal.toString())) {
132
+ newVal = newVal.slice(0, 24);
133
+ }
134
+ else if (newVal.id && /^[a-f0-9]{24}/.test(newVal.id)) {
135
+ newVal = newVal.id.slice(0, 24);
136
+ }
137
+ else if (scope.$parent["f_" + attrs['fld'] + "Options"]) {
138
+ // extract from lookups
139
+ var i = scope.$parent["f_" + attrs['fld'] + "Options"].indexOf(newVal);
140
+ if (i > -1) {
141
+ newVal = scope.$parent["f_" + attrs['fld'] + "_ids"][i];
80
142
  }
81
143
  else {
82
- scope['text'] = data.list;
144
+ newVal = undefined;
83
145
  }
84
- }, function (response) {
85
- scope['text'] = 'Error ' + response.status + ': ' + response.data;
86
- });
146
+ }
147
+ else {
148
+ newVal = undefined;
149
+ }
150
+ if (newVal) {
151
+ scope['link'] = routingService.buildUrl(ref + '/' + form + (newVal.id || newVal) + '/edit' + linktab);
152
+ }
153
+ else {
154
+ scope['link'] = undefined;
155
+ }
87
156
  }
88
- }
89
- }, true);
157
+ else {
158
+ scope['link'] = undefined;
159
+ }
160
+ }, true);
161
+ }
162
+ else {
163
+ if (attrs['text'] && attrs['text'].length > 0) {
164
+ scope['text'] = attrs['text'];
165
+ }
166
+ var index = scope['$parent']['$index'];
167
+ scope.$watch('dataSrc()', function (newVal) {
168
+ if (newVal) {
169
+ if (typeof index !== 'undefined' && angular.isArray(newVal)) {
170
+ newVal = newVal[index];
171
+ }
172
+ scope['link'] = routingService.buildUrl(ref + '/' + form + newVal + '/edit' + linktab);
173
+ if (!scope['text']) {
174
+ SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
175
+ var data = response.data;
176
+ if (data.success === false) {
177
+ scope['text'] = data.err;
178
+ }
179
+ else {
180
+ scope['text'] = data.list;
181
+ }
182
+ }, function (response) {
183
+ scope['text'] = 'Error ' + response.status + ': ' + response.data;
184
+ });
185
+ }
186
+ }
187
+ }, true);
188
+ }
90
189
  },
190
+ controller: "LinkCtrl",
91
191
  template: function (element, attrs) {
92
- return attrs.readonly ? '<span class="fng-link">{{text}}</span>' : '<a href="{{ link }}" class="fng-link">{{text}}</a>';
192
+ function handleAnchor(contents) {
193
+ return "<a ng-click=\"checkNotModal()\" ng-href=\"{{ link || '#' }}\" class=\"fng-link\">" + contents + "</a>";
194
+ }
195
+ var retVal;
196
+ if (attrs.readonly) {
197
+ retVal = '<span class="fng-link">{{text}}</span>';
198
+ }
199
+ else if (attrs['text'] && unescape(attrs['text']) !== attrs['text']) {
200
+ retVal = handleAnchor(unescape(attrs['text']));
201
+ // retVal = '<a href="{{ link }}" class="fng-link">{{text}}</a>';
202
+ }
203
+ else {
204
+ retVal = handleAnchor('{{text}}');
205
+ }
206
+ return retVal;
93
207
  }
94
208
  };
95
209
  }
@@ -146,7 +260,7 @@ var fng;
146
260
  // <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
147
261
  // </div>
148
262
  //
149
- // Inline
263
+ // Inline or stacked
150
264
  // <div class="form-group">
151
265
  // <label class="sr-only" for="exampleInputEmail2">Email address</label>
152
266
  // <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
@@ -165,7 +279,7 @@ var fng;
165
279
  // <input type="text" placeholder="Type something…">
166
280
  // <span class="help-block">Example block-level help text here.</span>
167
281
  //
168
- // Inline
282
+ // Inline or Stacked
169
283
  // <input type="text" class="input-small" placeholder="Email">
170
284
  var subkeys = [];
171
285
  var tabsSetup = tabsSetupState.N;
@@ -206,7 +320,7 @@ var fng;
206
320
  var lastPart = compoundName.slice(root.length + 1);
207
321
  if (options.index) {
208
322
  modelString += root + '[' + options.index + '].' + lastPart;
209
- idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|\]\.)/g, '-');
323
+ idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|]\.)/g, '-');
210
324
  }
211
325
  else {
212
326
  modelString += root;
@@ -228,18 +342,33 @@ var fng;
228
342
  var allInputsVars = formMarkupHelper.allInputsVars(scope, fieldInfo, options, modelString, idString, nameString);
229
343
  var common = allInputsVars.common;
230
344
  var value;
231
- var requiredStr = (isRequired || fieldInfo.required) ? ' required' : '';
232
345
  isRequired = isRequired || fieldInfo.required;
233
- var requiredStr = isRequired ? ' required' : '';
346
+ var requiredStr = isRequired ? ' required ' : '';
234
347
  var enumInstruction;
348
+ function handleReadOnlyDisabled(readonly) {
349
+ var retVal = '';
350
+ if (readonly) {
351
+ // despite the option being "readonly", we should use disabled and ng-disabled rather than their readonly
352
+ // equivalents (which give controls the appearance of being read-only, but don't actually prevent user
353
+ // interaction)
354
+ if (typeof readonly === "boolean") {
355
+ retVal = " disabled ";
356
+ }
357
+ else {
358
+ retVal = " ng-disabled=\"" + readonly + "\" ";
359
+ }
360
+ }
361
+ return retVal;
362
+ }
235
363
  switch (fieldInfo.type) {
236
364
  case 'select':
237
365
  if (fieldInfo.select2) {
238
366
  value = '<input placeholder="fng-select2 has been removed" readonly>';
239
367
  }
240
368
  else {
241
- common += (fieldInfo.readonly ? 'disabled ' : '');
369
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
242
370
  common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
371
+ common += " aria-label=\"" + (fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name) + "\" ";
243
372
  value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
244
373
  if (!isRequired) {
245
374
  value += '<option></option>';
@@ -268,25 +397,31 @@ var fng;
268
397
  }
269
398
  break;
270
399
  case 'link':
271
- if (typeof fieldInfo.ref === 'string') {
272
- fieldInfo.ref = { type: 'lookup', collection: fieldInfo.ref };
273
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(fieldInfo.ref));
274
- }
275
- value = '<fng-link model="' + modelString + '" ref="' + JSON.stringify(fieldInfo.ref).replace(/"/g, '&quot;') + '"';
400
+ value = '<fng-link model="' + modelString + '" ref="' + fieldInfo.ref + '"';
276
401
  if (fieldInfo.form) {
277
402
  value += ' form="' + fieldInfo.form + '"';
278
403
  }
279
- if (fieldInfo.linkText) {
280
- value += ' text="' + fieldInfo.linkText + '"';
404
+ if (fieldInfo.linktab) {
405
+ value += ' linktab="' + fieldInfo.linktab + '"';
406
+ }
407
+ if (fieldInfo.linktext) {
408
+ value += ' text="' + fieldInfo.linktext + '"';
281
409
  }
282
410
  if (fieldInfo.readonly) {
283
- value += ' readonly="true"';
411
+ if (typeof fieldInfo.readonly === "boolean") {
412
+ value += " readonly=\"true\"";
413
+ }
414
+ else {
415
+ value += " ng-readonly=\"" + fieldInfo.readonly + "\"";
416
+ }
284
417
  }
285
418
  value += '></fng-link>';
286
419
  break;
287
420
  case 'radio':
288
421
  value = '';
289
- common += requiredStr + (fieldInfo.readonly ? ' disabled ' : ' ');
422
+ common += requiredStr;
423
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
424
+ common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
290
425
  var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
291
426
  if (angular.isArray(fieldInfo.options)) {
292
427
  if (options.subschema) {
@@ -296,8 +431,7 @@ var fng;
296
431
  var thisCommon_1;
297
432
  angular.forEach(fieldInfo.options, function (optValue, idx) {
298
433
  thisCommon_1 = common.replace('id="', 'id="' + idx + '-');
299
- value += '<input ' + thisCommon_1 + 'type="radio"';
300
- value += ' value="' + optValue + '">' + optValue;
434
+ value += "<input " + thisCommon_1 + " type=\"radio\" aria-label=\"" + optValue + "\" value=\"" + optValue + "\">" + optValue;
301
435
  if (separateLines) {
302
436
  value += '<br />';
303
437
  }
@@ -312,18 +446,16 @@ var fng;
312
446
  }
313
447
  enumInstruction = generateEnumInstructions();
314
448
  value += '<' + tagType + ' ng-repeat="option in ' + enumInstruction.repeat + '">';
315
- value += '<input ' + common.replace('id="', 'id="{{$index}}-') + ' type="radio" value="{{' + enumInstruction.value + '}}"> {{';
316
- value += enumInstruction.label || enumInstruction.value;
317
- value += ' }} </' + tagType + '> ';
449
+ value += "<input " + common.replace('id="', 'id="{{$index}}-') + " type=\"radio\" aria-label=\"" + enumInstruction.value + "\" value=\"{{ " + enumInstruction.value + " }}\"> {{ " + (enumInstruction.label || enumInstruction.value) + " }} </" + tagType + "> ";
318
450
  }
319
451
  break;
320
452
  case 'checkbox':
321
- common += requiredStr + (fieldInfo.readonly ? ' disabled ' : ' ');
453
+ common += requiredStr;
454
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
455
+ common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
456
+ value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
322
457
  if (cssFrameworkService.framework() === 'bs3') {
323
- value = '<div class="checkbox"><input ' + common + 'type="checkbox"></div>';
324
- }
325
- else {
326
- value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
458
+ value = '<div class="checkbox">' + value + '</div>';
327
459
  }
328
460
  break;
329
461
  default:
@@ -338,12 +470,13 @@ var fng;
338
470
  }
339
471
  }
340
472
  if (fieldInfo.editor === 'ckEditor') {
473
+ console.log('Deprecation Warning: "editor" property deprecated - use "add"');
341
474
  common += 'ckeditor = "" ';
342
475
  if (cssFrameworkService.framework() === 'bs3') {
343
476
  allInputsVars.sizeClassBS3 = 'col-xs-12';
344
477
  }
345
478
  }
346
- value = '<textarea ' + common + ' />';
479
+ value = '<textarea ' + common + '></textarea>';
347
480
  }
348
481
  else {
349
482
  value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
@@ -363,6 +496,9 @@ var fng;
363
496
  case 'inline':
364
497
  result = 'form-inline';
365
498
  break;
499
+ case 'stacked':
500
+ result = 'form-stacked';
501
+ break;
366
502
  case 'horizontalCompact':
367
503
  result = 'form-horizontal compact';
368
504
  break;
@@ -390,7 +526,7 @@ var fng;
390
526
  if (tabNo >= 0) {
391
527
  // TODO Figure out tab history updates (check for other tab-history-todos)
392
528
  // result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
393
- result.before = '<uib-tab select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"';
529
+ result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"';
394
530
  if (tabNo > 0) {
395
531
  result.before += 'active="tabs[' + tabNo + '].active"';
396
532
  }
@@ -451,6 +587,41 @@ var fng;
451
587
  }
452
588
  return result;
453
589
  };
590
+ var generateInlineHeaders = function (instructionsArray, options, model, evenWhenEmpty) {
591
+ // "column" headers for nested schemas that use formStyle: "inline" will only line up with their respective
592
+ // controls when widths are applied to both the cg_f_xxxx and col_label_xxxx element using css.
593
+ // Likely, the widths will need to be the same, so consider using the following:
594
+ // div[id$="_f_<collection>_<field>"] {
595
+ // width: 100px;
596
+ // }
597
+ // one column can grow to the remaining available width thus:
598
+ // div[id$="_f_<collection>_<field>"] {
599
+ // flex-grow: 1;
600
+ // }
601
+ var hideWhenEmpty = evenWhenEmpty ? "" : "ng-hide=\"!" + model + " || " + model + ".length === 0\"";
602
+ var res = "<div class=\"inline-col-headers\" style=\"display:flex\" " + hideWhenEmpty + ">";
603
+ for (var _i = 0, instructionsArray_1 = instructionsArray; _i < instructionsArray_1.length; _i++) {
604
+ var info = instructionsArray_1[_i];
605
+ // need to call this now to ensure the id is set. will probably be (harmlessly) called again later.
606
+ inferMissingProperties(info, options);
607
+ res += '<div ';
608
+ info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
609
+ if (info.showWhen) {
610
+ if (typeof info.showWhen === 'string') {
611
+ res += 'ng-show="' + info.showWhen + '"';
612
+ }
613
+ else {
614
+ res += 'ng-show="' + formMarkupHelper.generateNgShow(info.showWhen, model) + '"';
615
+ }
616
+ }
617
+ if (info.id && typeof info.id.replace === "function") {
618
+ res += ' id="col_label_' + info.id.replace(/\./g, '-') + '"';
619
+ }
620
+ res += " class=\"inline-col-header\"><label for=\"" + info.id + "\" class=\"control-label\">" + info.label + "</label></div>";
621
+ }
622
+ res += "</div>";
623
+ return res;
624
+ };
454
625
  var handleField = function (info, options) {
455
626
  var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
456
627
  var template = fieldChrome.template;
@@ -473,7 +644,8 @@ var fng;
473
644
  formstyle: options.formstyle,
474
645
  subkey: schemaDefName + '_subkey',
475
646
  subkeyno: arraySel,
476
- subschemaroot: info.name
647
+ subschemaroot: info.name,
648
+ suppressNestingWarning: info.suppressNestingWarning
477
649
  });
478
650
  template += topAndTail.after;
479
651
  }
@@ -481,7 +653,9 @@ var fng;
481
653
  }
482
654
  else {
483
655
  if (options.subschema) {
484
- console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
656
+ if (!options.suppressNestingWarning) {
657
+ console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
658
+ }
485
659
  }
486
660
  else {
487
661
  var model = (options.model || 'record') + '.' + info.name;
@@ -503,14 +677,18 @@ var fng;
503
677
  }
504
678
  }
505
679
  /* Array body */
680
+ if (info.formStyle === "inline" && info.inlineHeaders) {
681
+ template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
682
+ }
506
683
  template += '<ol class="sub-doc"' + (info.sortable ? " ui-sortable=\"sortableOptions\" ng-model=\"" + model + "\"" : '') + '>';
507
684
  template += '<li ng-form class="' + (cssFrameworkService.framework() === 'bs2' ? 'row-fluid ' : '') +
508
- convertFormStyleToClass(info.formStyle) + '" name="form_' + niceName + '{{$index}}" class="sub-doc well" id="' + info.id + 'List_{{$index}}" ' +
685
+ (info.inlineHeaders ? 'width-controlled ' : '') +
686
+ convertFormStyleToClass(info.formStyle) + ' ' + (info.ngClass ? "ng-class:" + info.ngClass : "") + '" name="form_' + niceName + '{{$index}}" class="sub-doc well" id="' + info.id + 'List_{{$index}}" ' +
509
687
  ' ng-repeat="subDoc in ' + model + ' track by $index">';
510
688
  if (cssFrameworkService.framework() === 'bs2') {
511
689
  template += '<div class="row-fluid sub-doc">';
512
690
  }
513
- if (!info.noRemove || info.customSubDoc) {
691
+ if (info.noRemove !== true || info.customSubDoc) {
514
692
  template += ' <div class="sub-doc-btns">';
515
693
  if (typeof info.customSubDoc == 'string') {
516
694
  template += info.customSubDoc;
@@ -533,31 +711,64 @@ var fng;
533
711
  }
534
712
  template += '</div> ';
535
713
  }
714
+ var parts = void 0;
715
+ if (info.subDocContainerType) {
716
+ var containerType = scope[info.subDocContainerType] || info.subDocContainerType;
717
+ var containerProps = Object.assign({ containerType: containerType }, info.subDocContainerProps);
718
+ parts = containerInstructions(containerProps);
719
+ }
720
+ if (parts === null || parts === void 0 ? void 0 : parts.before) {
721
+ template += parts.before;
722
+ }
536
723
  template += processInstructions(info.schema, false, {
537
724
  subschema: 'true',
538
725
  formstyle: info.formStyle,
539
726
  model: options.model,
540
- subschemaroot: info.name
727
+ subschemaroot: info.name,
728
+ suppressNestingWarning: info.suppressNestingWarning
541
729
  });
730
+ if (parts === null || parts === void 0 ? void 0 : parts.after) {
731
+ template += parts.after;
732
+ }
542
733
  if (cssFrameworkService.framework() === 'bs2') {
543
734
  template += ' </div>';
544
735
  }
545
736
  template += '</li>';
546
737
  template += '</ol>';
547
738
  /* Array footer */
548
- if (info.noAdd !== true || typeof info.customFooter == 'string') {
739
+ if (info.noAdd !== true || typeof info.customFooter == 'string' || info.noneIndicator) {
549
740
  var footer = '';
550
741
  if (typeof info.customFooter == 'string') {
551
742
  footer = info.customFooter;
552
743
  }
553
- if (info.noAdd !== true) {
554
- footer += "<button " + (info.noAdd ? 'ng-hide="' + info.noAdd + '"' : '') + " id=\"add_" + info.id + "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('" + info.name + "',$event)\">\n <i class=\"' + formMarkupHelper.glyphClass() + '-plus\"></i> \n Add\n </button>";
555
- }
556
- if (cssFrameworkService.framework() === 'bs3') {
557
- template += '<div class="row schema-foot"><div class="col-sm-offset-3">' + footer + '</div></div>';
744
+ var hideCond = '';
745
+ var indicatorShowCond = model + ".length == 0";
746
+ if (info.noAdd === true) {
747
+ indicatorShowCond = "ng-show=\"" + indicatorShowCond + "\"";
558
748
  }
559
749
  else {
560
- template += '<div class = "schema-foot ">' + footer + '</div>';
750
+ hideCond = info.noAdd ? "ng-hide=\"" + info.noAdd + "\"" : '';
751
+ indicatorShowCond = info.noAdd ? "ng-show=\"" + info.noAdd + " && " + indicatorShowCond + "\"" : '';
752
+ footer += "<button " + hideCond + " id=\"add_" + info.id + "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('" + info.name + "',$event)\">\n <i class=\"' + formMarkupHelper.glyphClass() + '-plus\"></i> \n Add\n </button>";
753
+ }
754
+ if (info.noneIndicator) {
755
+ footer += "<span " + indicatorShowCond + " class=\"none_indicator\" id=\"no_" + info.id + "_indicator\">None</span>";
756
+ // hideCond for the schema-foot is if there's no add button and no indicator
757
+ hideCond = model + ".length > 0";
758
+ if (info.noAdd === true) {
759
+ hideCond = "ng-hide=\"" + hideCond + "\"";
760
+ }
761
+ else {
762
+ hideCond = info.noAdd ? "ng-hide=\"" + info.noAdd + " && " + hideCond + "\"" : '';
763
+ }
764
+ }
765
+ if (footer !== '') {
766
+ if (cssFrameworkService.framework() === 'bs3') {
767
+ template += "<div " + hideCond + " class=\"row schema-foot\"><div class=\"col-sm-offset-3\">" + footer + "</div></div>";
768
+ }
769
+ else {
770
+ template += "<div " + hideCond + " class = \"schema-foot \">" + footer + "</div>";
771
+ }
561
772
  }
562
773
  }
563
774
  }
@@ -569,8 +780,8 @@ var fng;
569
780
  var controlDivClasses = formMarkupHelper.controlDivClasses(options);
570
781
  if (info.array) {
571
782
  controlDivClasses.push('fng-array');
572
- if (options.formstyle === 'inline') {
573
- throw new Error('Cannot use arrays in an inline form');
783
+ if (options.formstyle === 'inline' || options.formstyle === 'stacked') {
784
+ throw new Error('Cannot use arrays in an inline or stacked form');
574
785
  }
575
786
  template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
576
787
  template += formMarkupHelper.handleArrayInputAndControlDiv(generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, info.id + '_{{$index}}', options), controlDivClasses, info, options);
@@ -578,25 +789,27 @@ var fng;
578
789
  else {
579
790
  // Single fields here
580
791
  template += formMarkupHelper.label(scope, info, null, options);
581
- if (options.required) {
582
- console.log("********* Options required - found it ********");
583
- }
584
792
  template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, info.id, options), controlDivClasses);
585
793
  }
586
794
  }
587
795
  template += fieldChrome.closeTag;
588
796
  return template;
589
797
  };
590
- var inferMissingProperties = function (info) {
798
+ var inferMissingProperties = function (info, options) {
591
799
  // infer missing values
592
800
  info.type = info.type || 'text';
593
801
  if (info.id) {
594
- if (typeof info.id === 'number' || (info.id[0] >= 0 && info.id <= '9')) {
802
+ if (typeof info.id === 'number' || info.id.match(/^[0-9]/)) {
595
803
  info.id = '_' + info.id;
596
804
  }
597
805
  }
598
806
  else {
599
- info.id = 'f_' + info.name.replace(/\./g, '_');
807
+ if (options && options.noid) {
808
+ info.id = null;
809
+ }
810
+ else {
811
+ info.id = 'f_' + info.name.replace(/\./g, '_');
812
+ }
600
813
  }
601
814
  info.label = (info.label !== undefined) ? (info.label === null ? '' : info.label) : $filter('titleCase')(info.name.split('.').slice(-1)[0]);
602
815
  };
@@ -607,7 +820,11 @@ var fng;
607
820
  if (instructionsArray) {
608
821
  for (var anInstruction = 0; anInstruction < instructionsArray.length; anInstruction++) {
609
822
  var info = instructionsArray[anInstruction];
610
- if (anInstruction === 0 && topLevel && !options.schema.match(/$_schema_/) && typeof info.add !== 'object') {
823
+ if (options.viewform) {
824
+ info = angular.copy(info);
825
+ info.readonly = true;
826
+ }
827
+ if (anInstruction === 0 && topLevel && !options.schema.match(/\$_schema_/) && typeof info.add !== 'object') {
611
828
  info.add = info.add ? ' ' + info.add + ' ' : '';
612
829
  if (info.add.indexOf('ui-date') === -1 && !options.noautofocus && !info.containerType) {
613
830
  info.add = info.add + 'autofocus ';
@@ -616,9 +833,10 @@ var fng;
616
833
  var callHandleField = true;
617
834
  if (info.directive) {
618
835
  var directiveName = info.directive;
619
- var newElement = '<' + directiveName + ' model="' + (options.model || 'record') + '"';
836
+ var newElement = info.customHeader || "";
837
+ newElement += '<' + directiveName + ' model="' + (options.model || 'record') + '"';
620
838
  var thisElement = element[0];
621
- inferMissingProperties(info);
839
+ inferMissingProperties(info, options);
622
840
  for (var i = 0; i < thisElement.attributes.length; i++) {
623
841
  var thisAttr = thisElement.attributes[i];
624
842
  switch (thisAttr.nodeName) {
@@ -654,7 +872,9 @@ var fng;
654
872
  break;
655
873
  case 'object':
656
874
  for (var subAdd in info.add) {
657
- newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '&quot;') + '"';
875
+ if (info.add.hasOwnProperty(subAdd)) {
876
+ newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '&quot;') + '"';
877
+ }
658
878
  }
659
879
  break;
660
880
  default:
@@ -663,13 +883,21 @@ var fng;
663
883
  break;
664
884
  case directiveCamel:
665
885
  for (var subProp in info[prop]) {
666
- newElement += info.directive + '-' + subProp + '="' + info[prop][subProp] + '"';
886
+ if (info[prop].hasOwnProperty(subProp)) {
887
+ newElement += " " + info.directive + "-" + subProp + "=\"";
888
+ if (typeof info[prop][subProp] === 'string') {
889
+ newElement += info[prop][subProp].replace(/"/g, '&quot;') + "\"";
890
+ }
891
+ else {
892
+ newElement += JSON.stringify(info[prop][subProp]).replace(/"/g, '&quot;') + "\"";
893
+ }
894
+ }
667
895
  }
668
896
  break;
669
897
  default:
670
898
  if (info[prop]) {
671
899
  if (typeof info[prop] === 'string') {
672
- newElement += ' fng-fld-' + prop + '="' + info[prop].toString().replace(/"/g, '&quot;') + '"';
900
+ newElement += ' fng-fld-' + prop + '="' + info[prop].replace(/"/g, '&quot;') + '"';
673
901
  }
674
902
  else {
675
903
  newElement += ' fng-fld-' + prop + '="' + JSON.stringify(info[prop]).replace(/"/g, '&quot;') + '"';
@@ -685,6 +913,7 @@ var fng;
685
913
  }
686
914
  }
687
915
  newElement += 'ng-model="' + info.name + '"></' + directiveName + '>';
916
+ newElement += (info.customFooter || "");
688
917
  result += newElement;
689
918
  callHandleField = false;
690
919
  }
@@ -721,9 +950,7 @@ var fng;
721
950
  else if (options.subkey) {
722
951
  // Don't display fields that form part of the subkey, as they should not be edited (because in these circumstances they form some kind of key)
723
952
  var objectToSearch = angular.isArray(scope[options.subkey]) ? scope[options.subkey][0].keyList : scope[options.subkey].keyList;
724
- if (_.find(objectToSearch, function (value, key) {
725
- return scope[options.subkey].path + '.' + key === info.name;
726
- })) {
953
+ if (_.find(objectToSearch, function (value, key) { return scope[options.subkey].path + '.' + key === info.name; })) {
727
954
  callHandleField = false;
728
955
  }
729
956
  }
@@ -731,7 +958,7 @@ var fng;
731
958
  // if (groupId) {
732
959
  // scope['showHide' + groupId] = true;
733
960
  // }
734
- inferMissingProperties(info);
961
+ inferMissingProperties(info, options);
735
962
  result += handleField(info, options);
736
963
  }
737
964
  }
@@ -745,8 +972,9 @@ var fng;
745
972
  var unwatch = scope.$watch(attrs.schema, function (newValue) {
746
973
  if (newValue) {
747
974
  var newArrayValue = angular.isArray(newValue) ? newValue : [newValue]; // otherwise some old tests stop working for no real reason
748
- if (newArrayValue.length > 0) {
975
+ if (newArrayValue.length > 0 && typeof unwatch === "function") {
749
976
  unwatch();
977
+ unwatch = null;
750
978
  var elementHtml = '';
751
979
  var recordAttribute = attrs.model || 'record'; // By default data comes from scope.record
752
980
  var theRecord = scope[recordAttribute];
@@ -760,12 +988,13 @@ var fng;
760
988
  var customAttrs = '';
761
989
  for (var thisAttr in attrs) {
762
990
  if (attrs.hasOwnProperty(thisAttr)) {
763
- if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model'].indexOf(thisAttr) === -1) {
991
+ if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model', 'viewform'].indexOf(thisAttr) === -1) {
764
992
  customAttrs += ' ' + attrs.$attr[thisAttr] + '="' + attrs[thisAttr] + '"';
765
993
  }
766
994
  }
767
995
  }
768
- elementHtml = '<form name="' + scope.topLevelFormName + '" class="' + convertFormStyleToClass(attrs.formstyle) + ' novalidate"' + customAttrs + '>';
996
+ var tag = attrs.forceform ? 'ng-form' : 'form';
997
+ elementHtml = "<" + tag + " name=\"" + scope.topLevelFormName + "\" class=\"" + convertFormStyleToClass(attrs.formstyle) + "\" novalidate " + customAttrs + ">";
769
998
  }
770
999
  if (theRecord === scope.topLevelFormName) {
771
1000
  throw new Error('Model and Name must be distinct - they are both ' + theRecord);
@@ -781,10 +1010,12 @@ var fng;
781
1010
  // If we have modelControllers we need to let them know when we have form + data
782
1011
  var sharedData = scope[attrs.shared || 'sharedData'];
783
1012
  var modelControllers_1 = sharedData ? sharedData.modelControllers : [];
784
- if (subkeys.length > 0 || modelControllers_1.length > 0) {
1013
+ if ((subkeys.length > 0 || modelControllers_1.length > 0) && !scope.phaseWatcher) {
785
1014
  var unwatch2 = scope.$watch('phase', function (newValue) {
786
- if (newValue === 'ready') {
1015
+ scope.phaseWatcher = true;
1016
+ if (newValue === 'ready' && typeof unwatch2 === "function") {
787
1017
  unwatch2();
1018
+ unwatch2 = null;
788
1019
  // Tell the 'model controllers' that the form and data are there
789
1020
  for (var i = 0; i < modelControllers_1.length; i++) {
790
1021
  if (modelControllers_1[i].onAllReady) {
@@ -813,7 +1044,7 @@ var fng;
813
1044
  arrayOffset = scope[arrayToProcess[thisOffset].selectFunc](theRecord, info);
814
1045
  }
815
1046
  else if (arrayToProcess[thisOffset].keyList) {
816
- // We are chosing the array element by matching one or more keys
1047
+ // We are choosing the array element by matching one or more keys
817
1048
  var thisSubkeyList = arrayToProcess[thisOffset].keyList;
818
1049
  for (arrayOffset = 0; arrayOffset < dataVal.length; arrayOffset++) {
819
1050
  matching = true;
@@ -867,7 +1098,7 @@ var fng;
867
1098
  }
868
1099
  });
869
1100
  }
870
- $rootScope.$broadcast('formInputDone');
1101
+ $rootScope.$broadcast('formInputDone', attrs.name);
871
1102
  if (formGenerator.updateDataDependentDisplay && theRecord && Object.keys(theRecord).length > 0) {
872
1103
  // If this is not a test force the data dependent updates to the DOM
873
1104
  formGenerator.updateDataDependentDisplay(theRecord, null, true, scope);
@@ -916,7 +1147,7 @@ var fng;
916
1147
  /*@ngInject*/
917
1148
  SearchCtrl.$inject = ["$scope", "$http", "$location", "routingService"];
918
1149
  function SearchCtrl($scope, $http, $location, routingService) {
919
- var currentRequest = '';
1150
+ var lastSearchSent;
920
1151
  var _isNotMobile;
921
1152
  _isNotMobile = (function () {
922
1153
  var check = false;
@@ -971,9 +1202,15 @@ var fng;
971
1202
  };
972
1203
  $scope.selectResult = function (resultNo) {
973
1204
  var result = $scope.results[resultNo];
974
- var newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
975
- if (result.resourceTab) {
976
- newURL += '/' + result.resourceTab;
1205
+ var newURL;
1206
+ if (result.url) {
1207
+ newURL = result.url.replace('|id|', result.id);
1208
+ }
1209
+ else {
1210
+ newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
1211
+ if (result.resourceTab) {
1212
+ newURL += '/' + result.resourceTab;
1213
+ }
977
1214
  }
978
1215
  $location.url(newURL);
979
1216
  };
@@ -992,12 +1229,12 @@ var fng;
992
1229
  };
993
1230
  $scope.$watch('searchTarget', function (newValue) {
994
1231
  if (newValue && newValue.length > 0) {
995
- currentRequest = newValue;
996
- $http.get('/api/search?q=' + newValue).then(function (response) {
1232
+ lastSearchSent = $scope.testTime || new Date().valueOf();
1233
+ $http.get('/api/search?q=' + newValue + '&sentAt=' + lastSearchSent).then(function (response) {
997
1234
  var data = response.data;
998
1235
  // Check that we haven't fired off a subsequent request, in which
999
1236
  // case we are no longer interested in these results
1000
- if (currentRequest === newValue) {
1237
+ if (!data.timestamps || !data.timestamps.sentAt || Number.parseInt(data.timestamps.sentAt) === lastSearchSent) {
1001
1238
  if ($scope.searchTarget.length > 0) {
1002
1239
  $scope.results = data.results;
1003
1240
  $scope.moreCount = data.moreCount;
@@ -1064,6 +1301,21 @@ var fng;
1064
1301
  })(fng || (fng = {}));
1065
1302
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
1066
1303
  var fng;
1304
+ (function (fng) {
1305
+ var filters;
1306
+ (function (filters) {
1307
+ /*@ngInject*/
1308
+ function extractTimestampFromMongoID() {
1309
+ return function (id) {
1310
+ var timestamp = id.substring(0, 8);
1311
+ return new Date(parseInt(timestamp, 16) * 1000);
1312
+ };
1313
+ }
1314
+ filters.extractTimestampFromMongoID = extractTimestampFromMongoID;
1315
+ })(filters = fng.filters || (fng.filters = {}));
1316
+ })(fng || (fng = {}));
1317
+ /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
1318
+ var fng;
1067
1319
  (function (fng) {
1068
1320
  var filters;
1069
1321
  (function (filters) {
@@ -1244,6 +1496,7 @@ var fng;
1244
1496
  controllerName += 'Ctrl';
1245
1497
  locals.$scope = sharedData.modelControllers[level] = localScope;
1246
1498
  var parentScope = localScope.$parent;
1499
+ parentScope.items = parentScope.items || [];
1247
1500
  var addMenuOptions = function (array) {
1248
1501
  angular.forEach(array, function (value) {
1249
1502
  if (value.divider) {
@@ -1275,6 +1528,11 @@ var fng;
1275
1528
  locals.$scope.contextMenuPromise.then(function (array) { return addMenuOptions(array); });
1276
1529
  }
1277
1530
  }
1531
+ if (sharedData.modelNameDisplayPromise) {
1532
+ sharedData.modelNameDisplayPromise.then(function (value) {
1533
+ parentScope.modelNameDisplay = value;
1534
+ });
1535
+ }
1278
1536
  }
1279
1537
  catch (error) {
1280
1538
  // Check to see if error is no such controller - don't care
@@ -1304,7 +1562,7 @@ var fng;
1304
1562
  routing: 'ngroute',
1305
1563
  prefix: '' // How do we want to prefix our routes? If not empty string then first character must be slash (which is added if not)
1306
1564
  };
1307
- var postActions = ['edit'];
1565
+ var postActions = ['edit', 'view'];
1308
1566
  var builtInRoutes = [
1309
1567
  {
1310
1568
  route: '/analyse/:model/:reportSchemaName',
@@ -1314,12 +1572,19 @@ var fng;
1314
1572
  { route: '/analyse/:model', state: 'analyse::model', templateUrl: 'base-analysis.html' },
1315
1573
  { route: '/:model/:id/edit', state: 'model::edit', templateUrl: 'base-edit.html' },
1316
1574
  { route: '/:model/:id/edit/:tab', state: 'model::edit::tab', templateUrl: 'base-edit.html' },
1575
+ { route: '/:model/:id/view', state: 'model::edit', templateUrl: 'base-view.html' },
1576
+ { route: '/:model/:id/view/:tab', state: 'model::view::tab', templateUrl: 'base-view.html' },
1317
1577
  { route: '/:model/new', state: 'model::new', templateUrl: 'base-edit.html' },
1318
1578
  { route: '/:model', state: 'model::list', templateUrl: 'base-list.html' },
1579
+ { route: '/:model/viewonly', state: 'model::view', templateUrl: 'base-list-view.html' },
1580
+ // Non default form (subset of fields etc)
1319
1581
  { route: '/:model/:form/:id/edit', state: 'model::form::edit', templateUrl: 'base-edit.html' },
1320
1582
  { route: '/:model/:form/:id/edit/:tab', state: 'model::form::edit::tab', templateUrl: 'base-edit.html' },
1583
+ { route: '/:model/:form/:id/view', state: 'model::form::view', templateUrl: 'base-view.html' },
1584
+ { route: '/:model/:form/:id/view/:tab', state: 'model::form::view::tab', templateUrl: 'base-view.html' },
1321
1585
  { route: '/:model/:form/new', state: 'model::form::new', templateUrl: 'base-edit.html' },
1322
- { route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' } // list page with links to non default form
1586
+ { route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' },
1587
+ { route: '/:model/:form/viewonly', state: 'model::form::list::view', templateUrl: 'base-list-view.html' } // list page with edit links to non default form
1323
1588
  ];
1324
1589
  var _routeProvider, _stateProvider;
1325
1590
  var lastRoute = null;
@@ -1373,6 +1638,12 @@ var fng;
1373
1638
  case 'edit':
1374
1639
  urlStr = modelString + formString + '/' + id + '/edit' + tabString;
1375
1640
  break;
1641
+ case 'view':
1642
+ urlStr = modelString + formString + '/' + id + '/view' + tabString;
1643
+ break;
1644
+ case 'read':
1645
+ urlStr = modelString + formString + '/' + id + '/read' + tabString;
1646
+ break;
1376
1647
  case 'new':
1377
1648
  urlStr = modelString + formString + '/new' + tabString;
1378
1649
  break;
@@ -1455,8 +1726,9 @@ var fng;
1455
1726
  lastObject.modelName = locationSplit[1];
1456
1727
  var lastParts_1 = [locationSplit[locationParts - 1], locationSplit[locationParts - 2]];
1457
1728
  var newPos = lastParts_1.indexOf('new');
1729
+ var viewonlyPos = lastParts_1.indexOf('viewonly');
1458
1730
  var actionPos = void 0;
1459
- if (newPos === -1) {
1731
+ if (newPos === -1 && viewonlyPos === -1) {
1460
1732
  actionPos = postActions.reduce(function (previousValue, currentValue) {
1461
1733
  var pos = lastParts_1.indexOf(currentValue);
1462
1734
  return pos > -1 ? pos : previousValue;
@@ -1466,10 +1738,13 @@ var fng;
1466
1738
  lastObject.id = locationSplit[locationParts];
1467
1739
  }
1468
1740
  }
1469
- else {
1741
+ else if (newPos !== -1) {
1470
1742
  lastObject.newRecord = true;
1471
1743
  locationParts -= (1 + newPos);
1472
1744
  }
1745
+ else {
1746
+ locationParts -= (1 + viewonlyPos);
1747
+ }
1473
1748
  if (actionPos === 1 || newPos === 1) {
1474
1749
  lastObject.tab = lastParts_1[0];
1475
1750
  }
@@ -1480,34 +1755,6 @@ var fng;
1480
1755
  }
1481
1756
  return lastObject;
1482
1757
  };
1483
- ///**
1484
- // * DominicBoettger wrote:
1485
- // *
1486
- // * Parser for the states provided by ui.router
1487
- // */
1488
- //'use strict';
1489
- //formsAngular.factory('$stateParse', [function () {
1490
- //
1491
- // var lastObject = {};
1492
- //
1493
- // return function (state) {
1494
- // if (state.current && state.current.name) {
1495
- // lastObject = {newRecord: false};
1496
- // lastObject.modelName = state.params.model;
1497
- // if (state.current.name === 'model::list') {
1498
- // lastObject = {index: true};
1499
- // lastObject.modelName = state.params.model;
1500
- // } else if (state.current.name === 'model::edit') {
1501
- // lastObject.id = state.params.id;
1502
- // } else if (state.current.name === 'model::new') {
1503
- // lastObject.newRecord = true;
1504
- // } else if (state.current.name === 'model::analyse') {
1505
- // lastObject.analyse = true;
1506
- // }
1507
- // }
1508
- // return lastObject;
1509
- // };
1510
- //}]);
1511
1758
  },
1512
1759
  buildUrl: function (path) {
1513
1760
  var url = config.html5Mode ? '' : '#';
@@ -1524,46 +1771,25 @@ var fng;
1524
1771
  },
1525
1772
  redirectTo: function () {
1526
1773
  return function (operation, scope, location, id, tab) {
1527
- // switch (config.routing) {
1528
- // case 'ngroute' :
1529
- if (location.search()) {
1530
- location.url(location.path());
1774
+ location.search({}); // Lose any search parameters
1775
+ var urlStr;
1776
+ if (operation === 'onDelete') {
1777
+ if (config.onDelete) {
1778
+ if (config.onDelete === 'new') {
1779
+ urlStr = _buildOperationUrl(config.prefix, 'new', scope.modelName, scope.formName, id, tab);
1780
+ }
1781
+ else {
1782
+ urlStr = config.onDelete;
1783
+ }
1784
+ }
1785
+ else {
1786
+ urlStr = _buildOperationUrl(config.prefix, 'list', scope.modelName, scope.formName, id, tab);
1787
+ }
1788
+ }
1789
+ else {
1790
+ urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
1531
1791
  }
1532
- var urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
1533
1792
  location.path(urlStr);
1534
- // break;
1535
- // case 'uirouter' :
1536
- // var formString = scope.formName ? ('/' + scope.formName) : '';
1537
- // var modelString = config.prefix + '/' + scope.modelName;
1538
- // console.log('form schemas not supported with ui-router');
1539
- // switch (operation) {
1540
- // case 'list' :
1541
- // location.path(modelString + formString);
1542
- // break;
1543
- // case 'edit' :
1544
- // location.path(modelString + formString + '/' + id + '/edit');
1545
- // break;
1546
- // case 'new' :
1547
- // location.path(modelString + formString + '/new');
1548
- // break;
1549
- // }
1550
- // switch (operation) {
1551
- // case 'list' :
1552
- // $state.go('model::list', { model: model });
1553
- // break;
1554
- // case 'edit' :
1555
- // location.path('/' + scope.modelName + formString + '/' + id + '/edit');
1556
- // break;
1557
- // case 'new' :
1558
- // location.path('/' + scope.modelName + formString + '/new');
1559
- // break;
1560
- // }
1561
- // break;
1562
- //
1563
- //
1564
- // // edit: $state.go('model::edit', {id: data._id, model: $scope.modelName });
1565
- // // new: $state.go('model::new', {model: $scope.modelName});
1566
- // break;
1567
1793
  };
1568
1794
  }
1569
1795
  };
@@ -1585,7 +1811,7 @@ var fng;
1585
1811
  * All methods should be state-less
1586
1812
  *
1587
1813
  */
1588
- function formGenerator($location, $timeout, $filter, SubmissionsService, routingService, recordHandler) {
1814
+ function formGenerator($location, $timeout, $filter, routingService, recordHandler) {
1589
1815
  function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
1590
1816
  function handletabInfo(tabName, thisInst) {
1591
1817
  var tabTitle = angular.copy(tabName);
@@ -1611,50 +1837,55 @@ var fng;
1611
1837
  }
1612
1838
  tab.content.push(thisInst);
1613
1839
  }
1840
+ if (typeof $scope.onSchemaFetch === "function") {
1841
+ $scope.onSchemaFetch(description, source);
1842
+ }
1614
1843
  for (var field in source) {
1615
- if (field === '_id') {
1616
- if (destList && source['_id'].options && source['_id'].options.list) {
1617
- handleListInfo(destList, source['_id'].options.list, field);
1618
- }
1619
- }
1620
- else if (source.hasOwnProperty(field)) {
1621
- var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
1622
- var formData = mongooseOptions.form || {};
1623
- if (mongooseType.schema && !formData.hidden) {
1624
- if (doRecursion && destForm) {
1625
- var schemaSchema = [];
1626
- handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
1627
- var sectionInstructions = basicInstructions(field, formData, prefix);
1628
- sectionInstructions.schema = schemaSchema;
1629
- if (formData.tab) {
1630
- handletabInfo(formData.tab, sectionInstructions);
1631
- }
1632
- if (formData.order !== undefined) {
1633
- destForm.splice(formData.order, 0, sectionInstructions);
1634
- }
1635
- else {
1636
- destForm.push(sectionInstructions);
1637
- }
1844
+ if (source.hasOwnProperty(field)) {
1845
+ if (field === '_id') {
1846
+ if (destList && source['_id'].options && source['_id'].options.list) {
1847
+ handleListInfo(destList, source['_id'].options.list, field);
1638
1848
  }
1639
1849
  }
1640
- else {
1641
- if (destForm && !formData.hidden) {
1642
- var formInstructions = basicInstructions(field, formData, prefix);
1643
- if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
1644
- var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
1645
- if (formInst.tab) {
1646
- handletabInfo(formInst.tab, formInst);
1850
+ else if (source.hasOwnProperty(field)) {
1851
+ var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
1852
+ var formData = mongooseOptions.form || {};
1853
+ if (mongooseType.schema && !formData.hidden) {
1854
+ if (doRecursion && destForm) {
1855
+ var schemaSchema = [];
1856
+ handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
1857
+ var sectionInstructions = basicInstructions(field, formData, prefix);
1858
+ sectionInstructions.schema = schemaSchema;
1859
+ if (formData.tab) {
1860
+ handletabInfo(formData.tab, sectionInstructions);
1647
1861
  }
1648
1862
  if (formData.order !== undefined) {
1649
- destForm.splice(formData.order, 0, formInst);
1863
+ destForm.splice(formData.order, 0, sectionInstructions);
1650
1864
  }
1651
1865
  else {
1652
- destForm.push(formInst);
1866
+ destForm.push(sectionInstructions);
1653
1867
  }
1654
1868
  }
1655
1869
  }
1656
- if (destList && mongooseOptions.list) {
1657
- handleListInfo(destList, mongooseOptions.list, field);
1870
+ else {
1871
+ if (destForm && !formData.hidden) {
1872
+ var formInstructions = basicInstructions(field, formData, prefix);
1873
+ if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
1874
+ var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
1875
+ if (formInst.tab) {
1876
+ handletabInfo(formInst.tab, formInst);
1877
+ }
1878
+ if (formData.order !== undefined) {
1879
+ destForm.splice(formData.order, 0, formInst);
1880
+ }
1881
+ else {
1882
+ destForm.push(formInst);
1883
+ }
1884
+ }
1885
+ }
1886
+ if (destList && mongooseOptions.list) {
1887
+ handleListInfo(destList, mongooseOptions.list, field);
1888
+ }
1658
1889
  }
1659
1890
  }
1660
1891
  }
@@ -1678,6 +1909,9 @@ var fng;
1678
1909
  // console.log($scope.tabs[0]['title'])
1679
1910
  // $location.hash($scope.tabs[0]['title']);
1680
1911
  // }
1912
+ if (typeof $scope.onSchemaProcessed === "function") {
1913
+ $scope.onSchemaProcessed(description, description.slice(0, 5) === 'Main ' ? $scope.baseSchema() : destForm);
1914
+ }
1681
1915
  if (destList && destList.length === 0) {
1682
1916
  handleEmptyList(description, destList, destForm, source);
1683
1917
  }
@@ -1686,23 +1920,23 @@ var fng;
1686
1920
  function performLookupSelect() {
1687
1921
  formInstructions.options = recordHandler.suffixCleanId(formInstructions, 'Options');
1688
1922
  formInstructions.ids = recordHandler.suffixCleanId(formInstructions, '_ids');
1689
- if (!formInstructions.hidden && mongooseOptions.ref) {
1690
- if (typeof mongooseOptions.ref === 'string') {
1691
- mongooseOptions.ref = { type: 'lookup', collection: mongooseOptions.ref };
1692
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(mongooseOptions.ref));
1693
- }
1694
- switch (mongooseOptions.ref.type) {
1695
- case 'lookup':
1696
- recordHandler.setUpLookupOptions(mongooseOptions.ref.collection, formInstructions, $scope, ctrlState, handleSchema);
1697
- break;
1698
- case 'lookupList':
1699
- recordHandler.setUpLookupListOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState);
1700
- break;
1701
- case 'internal':
1702
- recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.ref);
1703
- break;
1704
- default:
1705
- throw new Error("Unsupported ref type " + mongooseOptions.ref.type + " found in " + formInstructions.name);
1923
+ if (!formInstructions.hidden) {
1924
+ if (mongooseOptions.ref) {
1925
+ recordHandler.setUpLookupOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState, handleSchema);
1926
+ }
1927
+ else if (mongooseOptions.lookupListRef) {
1928
+ recordHandler.setUpLookupListOptions(mongooseOptions.lookupListRef, formInstructions, $scope, ctrlState);
1929
+ formInstructions.lookupListRef = mongooseOptions.lookupListRef;
1930
+ }
1931
+ else if (mongooseOptions.internalRef) {
1932
+ recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.internalRef);
1933
+ formInstructions.internalRef = mongooseOptions.internalRef;
1934
+ }
1935
+ else if (mongooseOptions.customLookupOptions) {
1936
+ // nothing to do - call setUpCustomLookupOptions() when ready to provide id and option arrays
1937
+ }
1938
+ else {
1939
+ throw new Error("No supported select lookup type found in " + formInstructions.name);
1706
1940
  }
1707
1941
  }
1708
1942
  }
@@ -1736,13 +1970,22 @@ var fng;
1736
1970
  }
1737
1971
  else if (mongooseType.instance === 'ObjectID') {
1738
1972
  formInstructions.ref = mongooseOptions.ref;
1739
- if (formInstructions.link && formInstructions.link.linkOnly) {
1740
- formInstructions.type = 'link';
1741
- formInstructions.linkText = formInstructions.link.text;
1973
+ if (formInstructions.link) {
1974
+ if (formInstructions.link.linkOnly) {
1975
+ formInstructions.type = 'link';
1976
+ formInstructions.linktext = formInstructions.link.text;
1977
+ }
1978
+ else if (formInstructions.link.label) {
1979
+ formInstructions.linklabel = true;
1980
+ }
1981
+ else {
1982
+ console.log('Unsupported link setup');
1983
+ }
1742
1984
  formInstructions.form = formInstructions.link.form;
1985
+ formInstructions.linktab = formInstructions.link.linktab;
1743
1986
  delete formInstructions.link;
1744
1987
  }
1745
- else {
1988
+ if (formInstructions.type !== 'link') {
1746
1989
  formInstructions.type = 'select';
1747
1990
  if (formInstructions.select2 || (mongooseOptions.form && mongooseOptions.form.select2)) {
1748
1991
  console.log('support for fng-select2 has been removed in 0.8.3 - please convert to fng-ui-select');
@@ -1754,16 +1997,18 @@ var fng;
1754
1997
  }
1755
1998
  else if (mongooseType.instance === 'Date') {
1756
1999
  if (!formInstructions.type) {
2000
+ formInstructions.intType = 'date';
1757
2001
  if (formInstructions.readonly) {
1758
2002
  formInstructions.type = 'text';
1759
2003
  }
1760
2004
  else if (formInstructions.directive) {
1761
- formInstructions.type = 'text'; // Think they all use date
2005
+ formInstructions.type = 'text';
1762
2006
  }
1763
2007
  else {
1764
2008
  try {
1765
2009
  formInstructions.add = formInstructions.add || '';
1766
- var testDatePickerInstalled = angular.module('ui.date').requires;
2010
+ // Check whether DatePicker is installed
2011
+ angular.module('ui.date').requires;
1767
2012
  formInstructions.type = 'text';
1768
2013
  formInstructions.add += ' ui-date ui-date-format ';
1769
2014
  // formInstructions.add += ' ui-date ui-date-format datepicker-popup-fix ';
@@ -1776,10 +2021,10 @@ var fng;
1776
2021
  }
1777
2022
  }
1778
2023
  else if (mongooseType.instance.toLowerCase() === 'boolean') {
1779
- formInstructions.type = 'checkbox';
2024
+ formInstructions.type = formInstructions.type || 'checkbox';
1780
2025
  }
1781
2026
  else if (mongooseType.instance === 'Number') {
1782
- formInstructions.type = 'number';
2027
+ formInstructions.type = formInstructions.type || 'number';
1783
2028
  if (mongooseOptions.min !== undefined) {
1784
2029
  formInstructions.add = 'min="' + mongooseOptions.min + '" ' + (formInstructions.add || '');
1785
2030
  }
@@ -1799,11 +2044,17 @@ var fng;
1799
2044
  if (mongooseOptions.readonly) {
1800
2045
  formInstructions['readonly'] = true;
1801
2046
  }
2047
+ if (mongooseType.defaultValue !== undefined) {
2048
+ formInstructions.defaultValue = mongooseType.defaultValue;
2049
+ }
2050
+ else if (mongooseType.options && mongooseType.options.default !== undefined) {
2051
+ console.log('No support for default with no value, yet');
2052
+ }
1802
2053
  return formInstructions;
1803
2054
  }
1804
- function getArrayFieldToExtend(fieldName, $scope) {
2055
+ function getArrayFieldToExtend(fieldName, $scope, modelOverride) {
1805
2056
  var fieldParts = fieldName.split('.');
1806
- var arrayField = $scope.record;
2057
+ var arrayField = modelOverride || $scope.record;
1807
2058
  for (var i = 0, l = fieldParts.length; i < l; i++) {
1808
2059
  if (!arrayField[fieldParts[i]]) {
1809
2060
  if (i === l - 1) {
@@ -1920,6 +2171,9 @@ var fng;
1920
2171
  generateEditUrl: function generateEditUrl(obj, $scope) {
1921
2172
  return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/edit');
1922
2173
  },
2174
+ generateViewUrl: function generateViewUrl(obj, $scope) {
2175
+ return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/view');
2176
+ },
1923
2177
  generateNewUrl: function generateNewUrl($scope) {
1924
2178
  return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + 'new');
1925
2179
  },
@@ -2004,25 +2258,44 @@ var fng;
2004
2258
  }
2005
2259
  return forceNextTime;
2006
2260
  },
2007
- add: function add(fieldName, $event, $scope) {
2008
- var arrayField = getArrayFieldToExtend(fieldName, $scope);
2009
- arrayField.push({});
2010
- $scope.setFormDirty($event);
2261
+ add: function add(fieldName, $event, $scope, modelOverride) {
2262
+ var _a;
2263
+ // check that target element is visible. May not be reliable - see https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
2264
+ if ($event.target.offsetParent) {
2265
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2266
+ var schemaElement = $scope.formSchema.find(function (f) { return f.name === fieldName; }); // In case someone is using the formSchema directly
2267
+ var subSchema = schemaElement ? schemaElement.schema : null;
2268
+ var obj = subSchema ? $scope.setDefaults(subSchema, fieldName + '.') : {};
2269
+ if (typeof ((_a = $scope.dataEventFunctions) === null || _a === void 0 ? void 0 : _a.onInitialiseNewSubDoc) === "function") {
2270
+ $scope.dataEventFunctions.onInitialiseNewSubDoc(fieldName, subSchema, obj);
2271
+ }
2272
+ arrayField.push(obj);
2273
+ $scope.setFormDirty($event);
2274
+ }
2011
2275
  },
2012
- unshift: function unshift(fieldName, $event, $scope) {
2013
- var arrayField = getArrayFieldToExtend(fieldName, $scope);
2276
+ unshift: function unshift(fieldName, $event, $scope, modelOverride) {
2277
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2014
2278
  arrayField.unshift({});
2015
2279
  $scope.setFormDirty($event);
2016
2280
  },
2017
- remove: function remove(fieldName, value, $event, $scope) {
2281
+ remove: function remove(fieldName, value, $event, $scope, modelOverride) {
2018
2282
  // Remove an element from an array
2019
- var fieldParts = fieldName.split('.');
2020
- var arrayField = $scope.record;
2021
- for (var i = 0, l = fieldParts.length; i < l; i++) {
2022
- arrayField = arrayField[fieldParts[i]];
2283
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2284
+ var err;
2285
+ if (typeof $scope.dataEventFunctions.onDeleteSubDoc === "function") {
2286
+ var schemaElement = $scope.formSchema.find(function (f) {
2287
+ return f.name === fieldName;
2288
+ });
2289
+ var subSchema = schemaElement ? schemaElement.schema : null;
2290
+ err = $scope.dataEventFunctions.onDeleteSubDoc(fieldName, subSchema, arrayField, value);
2291
+ }
2292
+ if (err) {
2293
+ $scope.showError(err);
2294
+ }
2295
+ else {
2296
+ arrayField.splice(value, 1);
2297
+ $scope.setFormDirty($event);
2023
2298
  }
2024
- arrayField.splice(value, 1);
2025
- $scope.setFormDirty($event);
2026
2299
  },
2027
2300
  hasError: function hasError(formName, name, index, $scope) {
2028
2301
  var result = false;
@@ -2034,7 +2307,7 @@ var fng;
2034
2307
  // Cannot assume that directives will use the same methods
2035
2308
  if (form) {
2036
2309
  var field_1 = form[name];
2037
- if (field_1 && field_1.$invalid) {
2310
+ if (field_1 && field_1.$invalid && !field_1.$$attr.readonly) {
2038
2311
  if (field_1.$dirty) {
2039
2312
  result = true;
2040
2313
  }
@@ -2074,6 +2347,9 @@ var fng;
2074
2347
  $scope.generateEditUrl = function (obj) {
2075
2348
  return formGeneratorInstance.generateEditUrl(obj, $scope);
2076
2349
  };
2350
+ $scope.generateViewUrl = function (obj) {
2351
+ return formGeneratorInstance.generateViewUrl(obj, $scope);
2352
+ };
2077
2353
  $scope.generateNewUrl = function () {
2078
2354
  return formGeneratorInstance.generateNewUrl($scope);
2079
2355
  };
@@ -2081,7 +2357,7 @@ var fng;
2081
2357
  return recordHandlerInstance.scrollTheList($scope);
2082
2358
  };
2083
2359
  $scope.getListData = function (record, fieldName) {
2084
- return recordHandlerInstance.getListData($scope, record, fieldName, $scope.listSchema);
2360
+ return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
2085
2361
  };
2086
2362
  $scope.setPristine = function (clearErrors) {
2087
2363
  if (clearErrors) {
@@ -2103,17 +2379,17 @@ var fng;
2103
2379
  console.log('setFormDirty called without an event (fine in a unit test)');
2104
2380
  }
2105
2381
  };
2106
- $scope.add = function (fieldName, $event) {
2107
- return formGeneratorInstance.add(fieldName, $event, $scope);
2382
+ $scope.add = function (fieldName, $event, modelOverride) {
2383
+ return formGeneratorInstance.add(fieldName, $event, $scope, modelOverride);
2108
2384
  };
2109
2385
  $scope.hasError = function (form, name, index) {
2110
2386
  return formGeneratorInstance.hasError(form, name, index, $scope);
2111
2387
  };
2112
- $scope.unshift = function (fieldName, $event) {
2113
- return formGeneratorInstance.unshift(fieldName, $event, $scope);
2388
+ $scope.unshift = function (fieldName, $event, modelOverride) {
2389
+ return formGeneratorInstance.unshift(fieldName, $event, $scope, modelOverride);
2114
2390
  };
2115
- $scope.remove = function (fieldName, value, $event) {
2116
- return formGeneratorInstance.remove(fieldName, value, $event, $scope);
2391
+ $scope.remove = function (fieldName, value, $event, modelOverride) {
2392
+ return formGeneratorInstance.remove(fieldName, value, $event, $scope, modelOverride);
2117
2393
  };
2118
2394
  $scope.baseSchema = function () {
2119
2395
  return ($scope.tabs.length ? $scope.tabs : $scope.formSchema);
@@ -2128,7 +2404,7 @@ var fng;
2128
2404
  };
2129
2405
  }
2130
2406
  services.formGenerator = formGenerator;
2131
- formGenerator.$inject = ["$location", "$timeout", "$filter", "SubmissionsService", "routingService", "recordHandler"];
2407
+ formGenerator.$inject = ["$location", "$timeout", "$filter", "routingService", "recordHandler"];
2132
2408
  })(services = fng.services || (fng.services = {}));
2133
2409
  })(fng || (fng = {}));
2134
2410
  /// <reference path="../../index.d.ts" />
@@ -2137,8 +2413,8 @@ var fng;
2137
2413
  var services;
2138
2414
  (function (services) {
2139
2415
  /*@ngInject*/
2140
- formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService"];
2141
- function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService) {
2416
+ formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "$filter"];
2417
+ function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, $filter) {
2142
2418
  function generateNgShow(showWhen, model) {
2143
2419
  function evaluateSide(side) {
2144
2420
  var result = side;
@@ -2166,11 +2442,15 @@ var fng;
2166
2442
  }
2167
2443
  return evaluateSide(showWhen.lhs) + conditionSymbols[conditionPos] + evaluateSide(showWhen.rhs);
2168
2444
  }
2169
- var isHorizontalStyle = function isHorizontalStyle(formStyle) {
2170
- return (!formStyle || formStyle === 'undefined' || ['vertical', 'inline'].indexOf(formStyle) === -1);
2445
+ var isHorizontalStyle = function isHorizontalStyle(formStyle, includeStacked) {
2446
+ var exclude = ['vertical', 'inline'];
2447
+ if (!includeStacked) {
2448
+ exclude.push('stacked');
2449
+ }
2450
+ return (!formStyle || formStyle === 'undefined' || !exclude.includes(formStyle));
2171
2451
  };
2172
2452
  function glyphClass() {
2173
- return (cssFrameworkService.framework() === 'bs2') ? 'icon' : 'glyphicon glyphicon';
2453
+ return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
2174
2454
  }
2175
2455
  return {
2176
2456
  isHorizontalStyle: isHorizontalStyle,
@@ -2188,7 +2468,9 @@ var fng;
2188
2468
  insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
2189
2469
  }
2190
2470
  }
2191
- insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2471
+ if (info.id && typeof info.id.replace === "function") {
2472
+ insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2473
+ }
2192
2474
  if (cssFrameworkService.framework() === 'bs3') {
2193
2475
  classes += ' form-group';
2194
2476
  if (options.formstyle === 'vertical' && info.size !== 'block-level') {
@@ -2213,7 +2495,7 @@ var fng;
2213
2495
  closeTag += '</div>';
2214
2496
  }
2215
2497
  else {
2216
- if (isHorizontalStyle(options.formstyle)) {
2498
+ if (isHorizontalStyle(options.formstyle, true)) {
2217
2499
  template += '<div' + addAllService.addAll(scope, 'Group', 'control-group', options);
2218
2500
  closeTag = '</div>';
2219
2501
  }
@@ -2227,11 +2509,13 @@ var fng;
2227
2509
  },
2228
2510
  label: function label(scope, fieldInfo, addButtonMarkup, options) {
2229
2511
  var labelHTML = '';
2230
- if ((cssFrameworkService.framework() === 'bs3' || (options.formstyle !== 'inline' && fieldInfo.label !== '')) || addButtonMarkup) {
2512
+ if ((cssFrameworkService.framework() === 'bs3' || (!['inline', 'stacked'].includes(options.formstyle) && fieldInfo.label !== '')) || addButtonMarkup) {
2231
2513
  labelHTML = '<label';
2232
2514
  var classes = 'control-label';
2233
- if (isHorizontalStyle(options.formstyle)) {
2234
- labelHTML += ' for="' + fieldInfo.id + '"';
2515
+ if (isHorizontalStyle(options.formstyle, false)) {
2516
+ if (!fieldInfo.linklabel) {
2517
+ labelHTML += ' for="' + fieldInfo.id + '"';
2518
+ }
2235
2519
  if (typeof fieldInfo.labelDefaultClass !== 'undefined') {
2236
2520
  // Override default label class (can be empty)
2237
2521
  classes += ' ' + fieldInfo.labelDefaultClass;
@@ -2240,7 +2524,7 @@ var fng;
2240
2524
  classes += ' col-sm-3';
2241
2525
  }
2242
2526
  }
2243
- else if (options.formstyle === 'inline') {
2527
+ else if (['inline', 'stacked'].includes(options.formstyle)) {
2244
2528
  labelHTML += ' for="' + fieldInfo.id + '"';
2245
2529
  classes += ' sr-only';
2246
2530
  }
@@ -2249,6 +2533,17 @@ var fng;
2249
2533
  labelHTML += ' <i id="add_' + fieldInfo.id + '" ng-click="add(\'' + fieldInfo.name + '\',$event)" class="' + glyphClass() + '-plus-sign"></i>';
2250
2534
  }
2251
2535
  labelHTML += '</label>';
2536
+ if (fieldInfo.linklabel) {
2537
+ var value = '<fng-link fld="' + fieldInfo.name + '" ref="' + fieldInfo.ref + '" text="' + escape(labelHTML) + '"';
2538
+ if (fieldInfo.form) {
2539
+ value += ' form="' + fieldInfo.form + '"';
2540
+ }
2541
+ if (fieldInfo.linktab) {
2542
+ value += ' linktab="' + fieldInfo.linktab + '"';
2543
+ }
2544
+ value += '></fng-link>';
2545
+ labelHTML = value;
2546
+ }
2252
2547
  }
2253
2548
  return labelHTML;
2254
2549
  },
@@ -2268,14 +2563,17 @@ var fng;
2268
2563
  else {
2269
2564
  sizeClassBS2 = (fieldInfo.size ? ' input-' + fieldInfo.size : '');
2270
2565
  }
2271
- if (options.formstyle === 'inline') {
2566
+ if (['inline', 'stacked'].includes(options.formstyle)) {
2272
2567
  placeHolder = placeHolder || fieldInfo.label;
2273
2568
  }
2274
- common = 'ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2569
+ common = 'data-ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2275
2570
  common += (placeHolder ? ('placeholder="' + placeHolder + '" ') : '');
2276
2571
  if (fieldInfo.popup) {
2277
2572
  common += 'title="' + fieldInfo.popup + '" ';
2278
2573
  }
2574
+ if (fieldInfo.ariaLabel) {
2575
+ common += 'aria-label="' + fieldInfo.ariaLabel + '" ';
2576
+ }
2279
2577
  common += addAllService.addAll(scope, 'Field', null, options);
2280
2578
  return {
2281
2579
  common: common,
@@ -2286,28 +2584,36 @@ var fng;
2286
2584
  };
2287
2585
  },
2288
2586
  inputChrome: function inputChrome(value, fieldInfo, options, markupVars) {
2289
- if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle) && fieldInfo.type !== 'checkbox') {
2587
+ if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle, true) && fieldInfo.type !== 'checkbox') {
2290
2588
  value = '<div class="bs3-input ' + markupVars.sizeClassBS3 + '">' + value + '</div>';
2291
2589
  }
2292
2590
  // Hack to cope with inline help in directives
2293
2591
  var inlineHelp = (fieldInfo.helpInline || '') + (fieldInfo.helpinline || '');
2294
2592
  if (inlineHelp.length > 0) {
2295
- value += '<span class="' + (cssFrameworkService.framework() === 'bs2' ? 'help-inline' : 'help-block') + '">' + inlineHelp + '</span>';
2296
- }
2297
- // If we have chosen
2298
- value += '<div ng-if="' + (options.name || 'myForm') + '.' + fieldInfo.id + '.$dirty" class="help-block">' +
2299
- ' <div ng-messages="' + (options.name || 'myForm') + '.' + fieldInfo.id + '.$error">' +
2300
- ' <div ng-messages-include="error-messages.html">' +
2301
- ' </div>' +
2302
- ' </div>' +
2303
- '</div>';
2593
+ var helpMarkup = cssFrameworkService.framework() === 'bs2' ? { el: 'span', cl: 'help-inline' } : { el: 'div', cl: 'help-block' };
2594
+ value += "<" + helpMarkup.el + " class=\"" + helpMarkup.cl + "\">" + inlineHelp + "</" + helpMarkup.el + ">";
2595
+ }
2596
+ if (!options.noid) {
2597
+ value += "<div ng-if=\"" + (options.name || 'myForm') + "['" + fieldInfo.id + "'].$dirty\" class=\"help-block\">" +
2598
+ (" <div ng-messages=\"" + (options.name || 'myForm') + "['" + fieldInfo.id + "'].$error\">") +
2599
+ ' <div ng-messages-include="error-messages.html">' +
2600
+ ' </div>' +
2601
+ ' </div>' +
2602
+ '</div>';
2603
+ }
2304
2604
  if (fieldInfo.help) {
2305
- value += '<span class="help-block">' + fieldInfo.help + '</span>';
2605
+ value += '<div class="help-block">' + fieldInfo.help + '</div>';
2306
2606
  }
2307
2607
  return value;
2308
2608
  },
2309
2609
  generateSimpleInput: function generateSimpleInput(common, fieldInfo, options) {
2310
- var result = '<input ' + common + 'type="' + fieldInfo.type + '"';
2610
+ var result = '<input ' + common + 'type="' + fieldInfo.type + '" ';
2611
+ if (!fieldInfo.label && !fieldInfo.ariaLabel) {
2612
+ result += "aria-label=\"" + fieldInfo.name.replace(/\./g, ' ') + "\" ";
2613
+ }
2614
+ else if (options.subschema) {
2615
+ result += "aria-label=\"" + (fieldInfo.label ? ($filter('titleCase')(options.subschemaroot) + ' ' + fieldInfo.label) : (fieldInfo.popup || fieldInfo.name.replace(/\./g, ' '))) + "\" ";
2616
+ }
2311
2617
  if (options.formstyle === 'inline' && cssFrameworkService.framework() === 'bs2' && !fieldInfo.size) {
2312
2618
  result += 'class="input-small"';
2313
2619
  }
@@ -2316,7 +2622,7 @@ var fng;
2316
2622
  },
2317
2623
  controlDivClasses: function controlDivClasses(options) {
2318
2624
  var result = [];
2319
- if (isHorizontalStyle(options.formstyle)) {
2625
+ if (isHorizontalStyle(options.formstyle, false)) {
2320
2626
  result.push(cssFrameworkService.framework() === 'bs2' ? 'controls' : 'col-sm-9');
2321
2627
  }
2322
2628
  return result;
@@ -2350,7 +2656,13 @@ var fng;
2350
2656
  if (fieldInfo.add) {
2351
2657
  result += ' ' + fieldInfo.add + ' ';
2352
2658
  }
2353
- result += requiredStr + (fieldInfo.readonly ? ' readonly' : '') + ' ';
2659
+ result += requiredStr;
2660
+ if (fieldInfo.readonly) {
2661
+ result += " " + (typeof fieldInfo.readOnly === 'boolean' ? 'readonly' : 'ng-readonly="' + fieldInfo.readonly + '"') + " ";
2662
+ }
2663
+ else {
2664
+ result += ' ';
2665
+ }
2354
2666
  return result;
2355
2667
  }
2356
2668
  };
@@ -2506,23 +2818,29 @@ var fng;
2506
2818
  *
2507
2819
  */
2508
2820
  /*@ngInject*/
2509
- recordHandler.$inject = ["$http", "$location", "$window", "$filter", "$timeout", "routingService", "SubmissionsService", "SchemasService"];
2510
- function recordHandler($http, $location, $window, $filter, $timeout, routingService, SubmissionsService, SchemasService) {
2821
+ recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
2822
+ function recordHandler($location, $window, $filter, $timeout, routingService, cssFrameworkService, SubmissionsService, SchemasService) {
2511
2823
  // TODO: Put this in a service
2512
2824
  var makeMongoId = function (rnd) {
2513
2825
  if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
2514
- return rnd(Date.now() / 1000) + ' '.repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
2826
+ return rnd(Date.now() / 1000) + " ".repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
2515
2827
  };
2828
+ function _handleCancel(resp) {
2829
+ if (["cancel", "backdrop click", "escape key press"].indexOf(resp) === -1) {
2830
+ throw resp;
2831
+ }
2832
+ }
2516
2833
  var suffixCleanId = function suffixCleanId(inst, suffix) {
2517
- return (inst.id || 'f_' + inst.name).replace(/\./g, '_') + suffix;
2834
+ return (inst.id || "f_" + inst.name).replace(/\./g, "_") + suffix;
2518
2835
  };
2519
- var walkTree = function (object, fieldname, element) {
2836
+ var walkTree = function (object, fieldname, element, insertIntermediateObjects) {
2520
2837
  // Walk through subdocs to find the required key
2521
2838
  // for instance walkTree(master,'address.street.number',element)
2522
2839
  // called by getData and setData
2840
+ if (insertIntermediateObjects === void 0) { insertIntermediateObjects = false; }
2523
2841
  // element is used when accessing in the context of a input, as the id (like exams-2-grader)
2524
2842
  // gives us the element of an array (one level down only for now). Leaving element blank returns the whole array
2525
- var parts = fieldname.split('.'), higherLevels = parts.length - 1, workingRec = object;
2843
+ var parts = fieldname.split("."), higherLevels = parts.length - 1, workingRec = object;
2526
2844
  for (var i = 0; i < higherLevels; i++) {
2527
2845
  if (!workingRec) {
2528
2846
  throw new Error("walkTree failed: Object = " + object + ", fieldname = " + fieldname + ", i = " + i);
@@ -2533,18 +2851,21 @@ var fng;
2533
2851
  });
2534
2852
  }
2535
2853
  else {
2854
+ if (insertIntermediateObjects && !workingRec[parts[i]]) {
2855
+ workingRec[parts[i]] = {};
2856
+ }
2536
2857
  workingRec = workingRec[parts[i]];
2537
2858
  }
2538
- if (angular.isArray(workingRec) && typeof element !== 'undefined') {
2539
- if (element.scope && typeof element.scope === 'function') {
2859
+ if (angular.isArray(workingRec) && typeof element !== "undefined") {
2860
+ if (element.scope && typeof element.scope === "function") {
2540
2861
  // If we come across an array we need to find the correct position, if we have an element
2541
2862
  workingRec = workingRec[element.scope().$index];
2542
2863
  }
2543
- else if (typeof element === 'number') {
2864
+ else if (typeof element === "number") {
2544
2865
  workingRec = workingRec[element];
2545
2866
  }
2546
2867
  else {
2547
- throw new Error('Unsupported element type in walkTree ' + fieldname);
2868
+ throw new Error("Unsupported element type in walkTree " + fieldname);
2548
2869
  }
2549
2870
  }
2550
2871
  if (!workingRec) {
@@ -2557,15 +2878,20 @@ var fng;
2557
2878
  };
2558
2879
  };
2559
2880
  var setData = function setData(object, fieldname, element, value) {
2560
- var leafData = walkTree(object, fieldname, element);
2881
+ var leafData = walkTree(object, fieldname, element, !!value);
2561
2882
  if (leafData.lastObject && leafData.key) {
2562
- if (angular.isArray(leafData.lastObject)) {
2563
- for (var i = 0; i < leafData.lastObject.length; i++) {
2564
- leafData.lastObject[i][leafData.key] = value[i];
2883
+ if (value) {
2884
+ if (angular.isArray(leafData.lastObject)) {
2885
+ for (var i = 0; i < leafData.lastObject.length; i++) {
2886
+ leafData.lastObject[i][leafData.key] = value[i];
2887
+ }
2888
+ }
2889
+ else {
2890
+ leafData.lastObject[leafData.key] = value;
2565
2891
  }
2566
2892
  }
2567
2893
  else {
2568
- leafData.lastObject[leafData.key] = value;
2894
+ delete leafData.lastObject[leafData.key];
2569
2895
  }
2570
2896
  }
2571
2897
  };
@@ -2584,11 +2910,17 @@ var fng;
2584
2910
  }
2585
2911
  return retVal;
2586
2912
  };
2587
- var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState) {
2913
+ var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState, ignoreDirty) {
2914
+ if (ignoreDirty === void 0) { ignoreDirty = false; }
2588
2915
  // Update the master and the record with the lookup values, master first
2589
- if (!$scope.topLevelFormName || $scope[$scope.topLevelFormName].$pristine) {
2916
+ if (!$scope.topLevelFormName || ($scope[$scope.topLevelFormName] && (ignoreDirty || $scope[$scope.topLevelFormName].$pristine))) {
2590
2917
  updateObject(schemaElement.name, ctrlState.master, function (value) {
2591
- return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, 'Options')], $scope[suffixCleanId(schemaElement, '_ids')]);
2918
+ if (typeof value == "object" && value.id) {
2919
+ return value;
2920
+ }
2921
+ else {
2922
+ return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, "Options")], $scope[suffixCleanId(schemaElement, "_ids")]);
2923
+ }
2592
2924
  });
2593
2925
  // Then copy the converted keys from master into record
2594
2926
  var newVal = getData(ctrlState.master, schemaElement.name);
@@ -2599,38 +2931,29 @@ var fng;
2599
2931
  };
2600
2932
  // Split a field name into the next level and all following levels
2601
2933
  function splitFieldName(aFieldName) {
2602
- var nesting = aFieldName.split('.'), result = [nesting[0]];
2934
+ var nesting = aFieldName.split("."), result = [nesting[0]];
2603
2935
  if (nesting.length > 1) {
2604
- result.push(nesting.slice(1).join('.'));
2936
+ result.push(nesting.slice(1).join("."));
2605
2937
  }
2606
2938
  return result;
2607
2939
  }
2608
- var getListData = function getListData($scope, record, fieldName, listSchema) {
2940
+ var getListData = function getListData(record, fieldName, listSchema, $scope) {
2609
2941
  if (listSchema === void 0) { listSchema = null; }
2610
- var retVal = record;
2611
- var nests = fieldName.split('.');
2612
- for (var i = 0; i < nests.length; i++) {
2613
- if (retVal !== undefined && retVal !== null && nests && nests[i]) {
2614
- retVal = retVal[nests[i]];
2615
- }
2616
- }
2617
- if (retVal === undefined) {
2618
- retVal = '';
2619
- }
2942
+ var retVal = getData(record, fieldName) || "";
2620
2943
  if (retVal && listSchema) {
2621
2944
  // Convert list fields as per instructions in params (ideally should be the same as what is found in data_form getListFields
2622
- var schemaElm = _.find(listSchema, function (elm) { return (elm['name'] === fieldName); });
2945
+ var schemaElm = _.find(listSchema, function (elm) { return (elm["name"] === fieldName); });
2623
2946
  if (schemaElm) {
2624
- switch (schemaElm['params']) {
2947
+ switch (schemaElm["params"]) {
2625
2948
  case undefined:
2626
2949
  break;
2627
- case 'timestamp':
2950
+ case "timestamp":
2628
2951
  var timestamp = retVal.toString().substring(0, 8);
2629
2952
  var date = new Date(parseInt(timestamp, 16) * 1000);
2630
- retVal = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
2953
+ retVal = date.toLocaleDateString() + " " + date.toLocaleTimeString();
2631
2954
  break;
2632
2955
  default:
2633
- retVal = $scope.dataEventFunctions[schemaElm['params']](record);
2956
+ retVal = $scope.dataEventFunctions[schemaElm["params"]](record);
2634
2957
  }
2635
2958
  }
2636
2959
  }
@@ -2647,7 +2970,7 @@ var fng;
2647
2970
  if (angular.isArray(theValue)) {
2648
2971
  for (var i = theValue.length - 1; i >= 0; i--) {
2649
2972
  var type = typeof theValue[i];
2650
- if (type === 'undefined' || (type === 'object' && Object.keys(theValue[i]).length === 0)) {
2973
+ if (type === "undefined" || (type === "object" && Object.keys(theValue[i]).length === 0)) {
2651
2974
  theValue.splice(i, 1);
2652
2975
  }
2653
2976
  }
@@ -2669,8 +2992,8 @@ var fng;
2669
2992
  }
2670
2993
  // Set up the lookup lists (value and id) on the scope for an internal lookup. Called by convertToAngularModel and $watch
2671
2994
  function setUpInternalLookupLists($scope, options, ids, newVal, valueAttrib) {
2672
- var optionsArray = (typeof options === 'string' ? $scope[options] : options);
2673
- var idsArray = (typeof ids === 'string' ? $scope[ids] : ids);
2995
+ var optionsArray = (typeof options === "string" ? $scope[options] : options);
2996
+ var idsArray = (typeof ids === "string" ? $scope[ids] : ids);
2674
2997
  optionsArray.length = 0;
2675
2998
  idsArray.length = 0;
2676
2999
  if (!!newVal && (newVal.length > 0)) {
@@ -2692,10 +3015,10 @@ var fng;
2692
3015
  result = true;
2693
3016
  }
2694
3017
  else if (!aSchema.directive) {
2695
- if (aSchema.type === 'text') {
3018
+ if (aSchema.type === "text") {
2696
3019
  result = true;
2697
3020
  }
2698
- else if (aSchema.type === 'select' && !aSchema.ids) {
3021
+ else if (aSchema.type === "select" && !aSchema.ids) {
2699
3022
  result = true;
2700
3023
  }
2701
3024
  }
@@ -2705,7 +3028,7 @@ var fng;
2705
3028
  function getConversionObject(scope, entryName, schemaName) {
2706
3029
  var conversions = scope.conversions;
2707
3030
  if (schemaName) {
2708
- conversions = conversions[schemaName] || {};
3031
+ conversions = getData(conversions, schemaName) || {};
2709
3032
  }
2710
3033
  return conversions[entryName];
2711
3034
  }
@@ -2716,37 +3039,58 @@ var fng;
2716
3039
  for (var i = 0; i < schema.length; i++) {
2717
3040
  var schemaEntry = schema[i];
2718
3041
  var fieldName = schemaEntry.name.slice(prefixLength);
3042
+ if (!fieldName.length) {
3043
+ fieldName = schemaEntry.name.split('.').pop();
3044
+ }
2719
3045
  var fieldValue = getData(anObject, fieldName);
3046
+ if (schemaEntry.intType === 'date' && typeof fieldValue === 'string') {
3047
+ setData(anObject, fieldName, null, new Date(fieldValue));
3048
+ }
2720
3049
  if (schemaEntry.schema) {
2721
3050
  if (fieldValue) {
2722
3051
  for (var j = 0; j < fieldValue.length; j++) {
2723
- fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], prefixLength + 1 + fieldName.length, $scope, fieldName, master, j);
3052
+ fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], 1 + fieldName.length, $scope, fieldName, master, j);
2724
3053
  }
2725
3054
  }
2726
3055
  }
2727
3056
  else {
2728
- if (schemaEntry.ref && schemaEntry.ref.type === 'internal') {
2729
- setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.ref.property], schemaEntry.ref.value);
3057
+ if (schemaEntry.internalRef) {
3058
+ setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.internalRef.property], schemaEntry.internalRef.value);
2730
3059
  }
2731
3060
  // Convert {array:['item 1']} to {array:[{x:'item 1'}]}
2732
- var thisField = getListData($scope, anObject, fieldName);
2733
- if (schemaEntry.array && simpleArrayNeedsX(schemaEntry) && thisField) {
3061
+ var thisField = getListData(anObject, fieldName, null, $scope);
3062
+ if (schemaEntry.array &&
3063
+ simpleArrayNeedsX(schemaEntry) &&
3064
+ thisField &&
3065
+ !(thisField.length > 0 && thisField[0].x) // Don't keep on coverting
3066
+ ) {
2734
3067
  for (var k = 0; k < thisField.length; k++) {
2735
3068
  thisField[k] = { x: thisField[k] };
2736
3069
  }
2737
3070
  }
2738
3071
  // Convert {lookup:'012abcde'} to {lookup:'List description for 012abcde'}
2739
- var idList = $scope[suffixCleanId(schemaEntry, '_ids')];
3072
+ var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
2740
3073
  var thisConversion = void 0;
2741
3074
  if (fieldValue && idList && idList.length > 0) {
2742
- if (fieldName.indexOf('.') !== -1) {
2743
- throw new Error('Trying to directly assign to a nested field 332');
3075
+ if (fieldName.indexOf(".") !== -1) {
3076
+ throw new Error("Trying to directly assign to a nested field 332");
2744
3077
  } // Not sure that this can happen, but put in a runtime test
2745
- anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, 'Options')], idList);
3078
+ if (
3079
+ /*
3080
+ Check we are starting with an ObjectId (ie not being called because of $watch on conversion, with a
3081
+ converted value, which would cause an exception)
3082
+ */
3083
+ fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
3084
+ /*
3085
+ We are not suppressing conversions
3086
+ */
3087
+ (!schemaEntry.internalRef || !schemaEntry.internalRef.noConvert)) {
3088
+ anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, "Options")], idList);
3089
+ }
2746
3090
  }
2747
3091
  else if (schemaEntry.select2) {
2748
3092
  // Do nothing with these - handled elsewhere (and deprecated)
2749
- console.log('fng-select2 is deprecated - use fng-ui-select instead');
3093
+ console.log("fng-select2 is deprecated - use fng-ui-select instead");
2750
3094
  void (schemaEntry.select2);
2751
3095
  }
2752
3096
  else if (fieldValue && (thisConversion = getConversionObject($scope, fieldName, schemaName)) &&
@@ -2755,7 +3099,7 @@ var fng;
2755
3099
  thisConversion.fngajax(fieldValue, schemaEntry, function (updateEntry, value) {
2756
3100
  // Update the master and (preserving pristine if appropriate) the record
2757
3101
  setData(master, updateEntry.name, offset, value);
2758
- preservePristine(angular.element('#' + updateEntry.id), function () {
3102
+ preservePristine(angular.element("#" + updateEntry.id), function () {
2759
3103
  setData($scope.record, updateEntry.name, offset, value);
2760
3104
  });
2761
3105
  });
@@ -2814,7 +3158,7 @@ var fng;
2814
3158
  else {
2815
3159
  var index = valuesArray.indexOf(textToConvert);
2816
3160
  if (index === -1) {
2817
- throw new Error('convertListValueToId: Invalid data - value ' + textToConvert + ' not found in ' + valuesArray + ' processing ' + fname);
3161
+ throw new Error("convertListValueToId: Invalid data - value " + textToConvert + " not found in " + valuesArray + " processing " + fname);
2818
3162
  }
2819
3163
  return idsArray[index];
2820
3164
  }
@@ -2822,7 +3166,7 @@ var fng;
2822
3166
  var preservePristine = function preservePristine(element, fn) {
2823
3167
  // stop the form being set to dirty when a fn is called
2824
3168
  // Use when the record (and master) need to be updated by lookup values displayed asynchronously
2825
- var modelController = element.inheritedData('$ngModelController');
3169
+ var modelController = element.inheritedData("$ngModelController");
2826
3170
  var isClean = (modelController && modelController.$pristine);
2827
3171
  if (isClean) {
2828
3172
  // fake it to dirty here and reset after call to fn
@@ -2834,18 +3178,21 @@ var fng;
2834
3178
  }
2835
3179
  };
2836
3180
  var convertIdToListValue = function convertIdToListValue(id, idsArray, valuesArray, fname) {
2837
- if (typeof (id) === 'object') {
3181
+ if (typeof (id) === "object") {
2838
3182
  id = id.id;
2839
3183
  }
2840
3184
  var index = idsArray.indexOf(id);
2841
3185
  if (index === -1) {
2842
- throw new Error('convertIdToListValue: Invalid data - id ' + id + ' not found in ' + idsArray + ' processing ' + fname);
3186
+ index = valuesArray.indexOf(id); // This can get called twice - second time with converted value (not sure how atm) so protect against that...
3187
+ if (index === -1) {
3188
+ throw new Error("convertIdToListValue: Invalid data - id " + id + " not found in " + idsArray + " processing " + fname);
3189
+ }
2843
3190
  }
2844
3191
  return valuesArray[index];
2845
3192
  };
2846
3193
  var processServerData = function processServerData(recordFromServer, $scope, ctrlState) {
2847
3194
  ctrlState.master = convertToAngularModel($scope.formSchema, recordFromServer, 0, $scope);
2848
- $scope.phase = 'ready';
3195
+ $scope.phase = "ready";
2849
3196
  $scope.cancel();
2850
3197
  };
2851
3198
  function convertOldToNew(ref, val, attrib, newVals, oldVals) {
@@ -2864,102 +3211,123 @@ var fng;
2864
3211
  var listOnly = (!$scope.id && !$scope.newRecord);
2865
3212
  // passing null for formSchema parameter prevents all the work being done when we are just after the list data,
2866
3213
  // but should be removed when/if formschemas are cached
2867
- formGeneratorInstance.handleSchema('Main ' + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, '', true, $scope, ctrlState);
3214
+ formGeneratorInstance.handleSchema("Main " + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, "", true, $scope, ctrlState);
3215
+ function processLookupHandlers(newValue, oldValue) {
3216
+ // If we have any internal lookups then update the references
3217
+ $scope.internalLookups.forEach(function (lkp) {
3218
+ var newVal = newValue[lkp.ref.property];
3219
+ var oldVal = oldValue[lkp.ref.property];
3220
+ setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
3221
+ // now change the looked-up values that matched the old to the new
3222
+ if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
3223
+ lkp.handlers.forEach(function (h) {
3224
+ if (h.possibleArray) {
3225
+ var arr = getData($scope.record, h.possibleArray, null);
3226
+ if (arr && arr.length > 0) {
3227
+ arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
3228
+ }
3229
+ }
3230
+ else if (angular.isArray($scope.record[h.lastPart])) {
3231
+ $scope.record[h.lastPart].forEach(function (a) {
3232
+ convertOldToNew(lkp.ref, a, "x", newVal, oldVal);
3233
+ });
3234
+ }
3235
+ else {
3236
+ convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
3237
+ }
3238
+ });
3239
+ }
3240
+ });
3241
+ // If we have any list lookups then update the references
3242
+ $scope.listLookups.forEach(function (lkp) {
3243
+ function extractIdVal(obj, idString) {
3244
+ var retVal = obj[idString];
3245
+ if (retVal && retVal.id) {
3246
+ retVal = retVal.id;
3247
+ }
3248
+ return retVal;
3249
+ }
3250
+ function blankListLookup(inst) {
3251
+ setData($scope.record, inst.name);
3252
+ }
3253
+ var idString = lkp.ref.id.slice(1);
3254
+ if (idString.includes(".")) {
3255
+ throw new Error("No support for nested list lookups yet - " + JSON.stringify(lkp.ref));
3256
+ }
3257
+ var newVal = extractIdVal(newValue, idString);
3258
+ var oldVal = extractIdVal(oldValue, idString);
3259
+ if (newVal !== oldVal) {
3260
+ lkp.handlers.forEach(function (h) {
3261
+ $scope[h.formInstructions.options].length = 0;
3262
+ $scope[h.formInstructions.ids].length = 0;
3263
+ });
3264
+ if (newVal) {
3265
+ SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
3266
+ lkp.handlers.forEach(function (h) {
3267
+ var optionsList = $scope[h.formInstructions.options];
3268
+ var idList = $scope[h.formInstructions.ids];
3269
+ var data = response.data[lkp.ref.property] || [];
3270
+ for (var i = 0; i < data.length; i++) {
3271
+ var option = data[i][lkp.ref.value];
3272
+ var pos = _.sortedIndex(optionsList, option);
3273
+ // handle dupes
3274
+ if (optionsList[pos] === option) {
3275
+ option = option + " (" + data[i]._id + ")";
3276
+ pos = _.sortedIndex(optionsList, option);
3277
+ }
3278
+ optionsList.splice(pos, 0, option);
3279
+ idList.splice(pos, 0, data[i]._id);
3280
+ }
3281
+ if (Object.keys(oldValue).length === 0) {
3282
+ // Not sure how safe this is, but the record is fresh so I think it's OK...
3283
+ updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState, true);
3284
+ }
3285
+ else {
3286
+ // Here we are reacting to a change in the lookup pointer in the record.
3287
+ // We need to blank our lookup field as it will not exist
3288
+ blankListLookup(h.formInstructions);
3289
+ }
3290
+ });
3291
+ });
3292
+ }
3293
+ else {
3294
+ lkp.handlers.forEach(function (h) {
3295
+ blankListLookup(h.formInstructions);
3296
+ });
3297
+ }
3298
+ }
3299
+ });
3300
+ }
3301
+ function notifyReady() {
3302
+ $scope.phase = "ready";
3303
+ $scope.cancel();
3304
+ processLookupHandlers($scope.record, {});
3305
+ }
2868
3306
  if (listOnly) {
2869
3307
  ctrlState.allowLocationChange = true;
2870
3308
  }
2871
3309
  else {
2872
3310
  var force = true;
2873
3311
  if (!$scope.newRecord) {
2874
- $scope.dropConversionWatcher = $scope.$watchCollection('conversions', function (newValue, oldValue) {
3312
+ $scope.dropConversionWatcher = $scope.$watchCollection("conversions", function (newValue, oldValue) {
2875
3313
  if (newValue !== oldValue && $scope.originalData) {
2876
3314
  processServerData($scope.originalData, $scope, ctrlState);
2877
3315
  }
2878
3316
  });
2879
3317
  }
2880
- $scope.$watch('record', function (newValue, oldValue) {
3318
+ $scope.$watch("record", function (newValue, oldValue) {
2881
3319
  if (newValue !== oldValue) {
2882
3320
  if (Object.keys(oldValue).length > 0 && $scope.dropConversionWatcher) {
2883
3321
  $scope.dropConversionWatcher(); // Don't want to convert changed data
2884
3322
  $scope.dropConversionWatcher = null;
2885
3323
  }
2886
3324
  force = formGeneratorInstance.updateDataDependentDisplay(newValue, oldValue, force, $scope);
2887
- // If we have any internal lookups then update the references
2888
- $scope.internalLookups.forEach(function (lkp) {
2889
- var newVal = newValue[lkp.ref.property];
2890
- var oldVal = oldValue[lkp.ref.property];
2891
- setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
2892
- // now change the looked-up values that matched the old to the new
2893
- if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
2894
- lkp.handlers.forEach(function (h) {
2895
- if (h.possibleArray) {
2896
- var arr = getData($scope.record, h.possibleArray, null);
2897
- if (arr && arr.length > 0) {
2898
- arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
2899
- }
2900
- }
2901
- else if (angular.isArray($scope.record[h.lastPart])) {
2902
- $scope.record[h.lastPart].forEach(function (a) {
2903
- convertOldToNew(lkp.ref, a, 'x', newVal, oldVal);
2904
- });
2905
- }
2906
- else {
2907
- convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
2908
- }
2909
- });
2910
- }
2911
- });
2912
- // If we have any list lookups then update the references
2913
- $scope.listLookups.forEach(function (lkp) {
2914
- function extractIdVal(obj, idString) {
2915
- var retVal = obj[idString];
2916
- if (retVal && retVal.id) {
2917
- retVal = retVal.id;
2918
- }
2919
- return retVal;
2920
- }
2921
- var idString = lkp.ref.id.slice(1);
2922
- if (idString.includes('.')) {
2923
- throw new Error("No support for nested list lookups yet - " + JSON.stringify(lkp.ref));
2924
- }
2925
- var newVal = extractIdVal(newValue, idString);
2926
- var oldVal = extractIdVal(oldValue, idString);
2927
- if (newVal !== oldVal) {
2928
- if (newVal) {
2929
- SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
2930
- lkp.handlers.forEach(function (h) {
2931
- var optionsList = $scope[h.formInstructions.options];
2932
- var idList = $scope[h.formInstructions.ids];
2933
- var data = response.data[lkp.ref.property];
2934
- for (var i = 0; i < data.length; i++) {
2935
- var option = data[i][lkp.ref.value];
2936
- var pos = _.sortedIndex(optionsList, option);
2937
- // handle dupes
2938
- if (optionsList[pos] === option) {
2939
- option = option + ' (' + data[i]._id + ')';
2940
- pos = _.sortedIndex(optionsList, option);
2941
- }
2942
- optionsList.splice(pos, 0, option);
2943
- idList.splice(pos, 0, data[i]._id);
2944
- }
2945
- updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState);
2946
- });
2947
- });
2948
- }
2949
- else {
2950
- lkp.handlers.forEach(function (h) {
2951
- $scope[h.formInstructions.options].length = 0;
2952
- $scope[h.formInstructions.ids].length = 0;
2953
- updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState);
2954
- });
2955
- }
2956
- }
2957
- });
3325
+ processLookupHandlers(newValue, oldValue);
2958
3326
  }
2959
3327
  }, true);
2960
3328
  if ($scope.id) {
2961
3329
  // Going to read a record
2962
- if (typeof $scope.dataEventFunctions.onBeforeRead === 'function') {
3330
+ if (typeof $scope.dataEventFunctions.onBeforeRead === "function") {
2963
3331
  $scope.dataEventFunctions.onBeforeRead($scope.id, function (err) {
2964
3332
  if (err) {
2965
3333
  $scope.showError(err);
@@ -2975,99 +3343,125 @@ var fng;
2975
3343
  }
2976
3344
  else {
2977
3345
  // New record
2978
- ctrlState.master = {};
3346
+ ctrlState.allowLocationChange = false;
3347
+ ctrlState.master = $scope.setDefaults($scope.formSchema);
2979
3348
  var passedRecord = $scope.initialiseNewRecord || $location.$$search.r;
2980
3349
  if (passedRecord) {
2981
3350
  try {
2982
- ctrlState.master = JSON.parse(passedRecord);
2983
- // Although this is a new record we are making it dirty from the url so we need to $setDirty
2984
- $scope.$on('fngCancel', function () {
2985
- setTimeout(function () {
2986
- if ($scope[$scope.topLevelFormName]) {
2987
- $scope[$scope.topLevelFormName].$setDirty();
2988
- }
2989
- }, 2); // Has to fire after the setPristime timeout.
2990
- });
3351
+ Object.assign(ctrlState.master, JSON.parse(passedRecord));
3352
+ if (!$scope["newRecordsStartPristine"]) {
3353
+ // Although this is a new record we are making it dirty from the url so we need to $setDirty
3354
+ $scope.$on("fngCancel", function () {
3355
+ $timeout(function () {
3356
+ if ($scope[$scope.topLevelFormName]) {
3357
+ $scope[$scope.topLevelFormName].$setDirty();
3358
+ }
3359
+ }, 1000); // Has to fire after the setPristime timeout.
3360
+ });
3361
+ }
2991
3362
  }
2992
3363
  catch (e) {
2993
- console.log('Error parsing specified record : ' + e.message);
3364
+ console.log("Error parsing specified record : " + e.message);
2994
3365
  }
2995
3366
  }
2996
- if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === 'function') {
3367
+ if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === "function") {
3368
+ console.log("onInitialiseNewRecord is deprecated - use the async version - onNewRecordInit(data,cb)");
2997
3369
  $scope.dataEventFunctions.onInitialiseNewRecord(ctrlState.master);
2998
3370
  }
2999
- $scope.phase = 'ready';
3000
- $scope.cancel();
3371
+ if (typeof $scope.dataEventFunctions.onNewRecordInit === "function") {
3372
+ $scope.dataEventFunctions.onNewRecordInit(ctrlState.master, function (err) {
3373
+ if (err) {
3374
+ $scope.showError(err);
3375
+ }
3376
+ else {
3377
+ notifyReady();
3378
+ }
3379
+ });
3380
+ }
3381
+ else {
3382
+ notifyReady();
3383
+ }
3001
3384
  }
3002
3385
  }
3003
3386
  }
3004
3387
  function handleError($scope) {
3005
3388
  return function (response) {
3006
3389
  if ([200, 400].indexOf(response.status) !== -1) {
3007
- var errorMessage = '';
3390
+ var errorMessage = "";
3008
3391
  for (var errorField in response.data.errors) {
3009
3392
  if (response.data.errors.hasOwnProperty(errorField)) {
3010
- errorMessage += '<li><b>' + $filter('titleCase')(errorField) + ': </b> ';
3393
+ errorMessage += "<li><b>" + $filter("titleCase")(errorField) + ": </b> ";
3011
3394
  switch (response.data.errors[errorField].type) {
3012
- case 'enum':
3013
- errorMessage += 'You need to select from the list of values';
3395
+ case "enum":
3396
+ errorMessage += "You need to select from the list of values";
3014
3397
  break;
3015
3398
  default:
3016
3399
  errorMessage += response.data.errors[errorField].message;
3017
3400
  break;
3018
3401
  }
3019
- errorMessage += '</li>';
3402
+ errorMessage += "</li>";
3020
3403
  }
3021
3404
  }
3022
3405
  if (errorMessage.length > 0) {
3023
- errorMessage = response.data.message + '<br /><ul>' + errorMessage + '</ul>';
3406
+ errorMessage = response.data.message + "<br /><ul>" + errorMessage + "</ul>";
3024
3407
  }
3025
3408
  else {
3026
- errorMessage = response.data.message || 'Error! Sorry - No further details available.';
3409
+ errorMessage = response.data.message || response.data.err || "Error! Sorry - No further details available.";
3027
3410
  }
3028
3411
  $scope.showError(errorMessage);
3029
3412
  }
3030
3413
  else {
3031
- $scope.showError(response.status + ' ' + JSON.stringify(response.data));
3414
+ $scope.showError(response.status + " " + JSON.stringify(response.data));
3032
3415
  }
3033
3416
  };
3034
3417
  }
3035
3418
  function handleIncomingData(data, $scope, ctrlState) {
3036
3419
  ctrlState.allowLocationChange = false;
3037
- $scope.phase = 'reading';
3038
- if (typeof $scope.dataEventFunctions.onAfterRead === 'function') {
3420
+ $scope.phase = "reading";
3421
+ if (typeof $scope.dataEventFunctions.onAfterRead === "function") {
3039
3422
  $scope.dataEventFunctions.onAfterRead(data);
3040
3423
  }
3041
3424
  $scope.originalData = data;
3042
3425
  processServerData(data, $scope, ctrlState);
3043
3426
  }
3427
+ function addArrayLookupToLookupList($scope, formInstructions, ref, lookups) {
3428
+ var nameElements = formInstructions.name.split(".");
3429
+ var refHandler = lookups.find(function (lkp) {
3430
+ return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3431
+ });
3432
+ var thisHandler = {
3433
+ formInstructions: formInstructions,
3434
+ lastPart: nameElements.pop(),
3435
+ possibleArray: nameElements.join(".")
3436
+ };
3437
+ if (!refHandler) {
3438
+ refHandler = {
3439
+ ref: ref,
3440
+ lookupOptions: [],
3441
+ lookupIds: [],
3442
+ handlers: []
3443
+ };
3444
+ lookups.push(refHandler);
3445
+ }
3446
+ refHandler.handlers.push(thisHandler);
3447
+ $scope[formInstructions.options] = refHandler.lookupOptions;
3448
+ $scope[formInstructions.ids] = refHandler.lookupIds;
3449
+ }
3044
3450
  return {
3045
3451
  readRecord: function readRecord($scope, ctrlState) {
3046
- // TODO Consider using $parse for this - http://bahmutov.calepin.co/angularjs-parse-hacks.html
3047
- SubmissionsService.readRecord($scope.modelName, $scope.id)
3452
+ $scope.readingRecord = SubmissionsService.readRecord($scope.modelName, $scope.id);
3453
+ $scope.readingRecord
3048
3454
  .then(function (response) {
3049
- var data = response.data;
3050
- if (data.success === false) {
3051
- $location.path('/404');
3052
- // TODO Figure out tab history updates (check for other tab-history-todos)
3053
- // } else if (response.master) {
3054
- //
3055
- // ctrlState.allowLocationChange = false;
3056
- // $scope.phase = 'ready';
3057
- // $scope.record = angular.copy(response.data);
3058
- // ctrlState.master = angular.copy(response.master);
3059
- // if (response.changed) {
3060
- // $timeout(() => {
3061
- // $scope[$scope.topLevelFormName].$setDirty();
3062
- // });
3063
- // } else {
3064
- // $timeout($scope.setPristine);
3065
- // }
3455
+ var data = angular.copy(response.data);
3456
+ handleIncomingData(data, $scope, ctrlState);
3457
+ }, function (error) {
3458
+ if (error.status === 404) {
3459
+ $location.path("/404");
3066
3460
  }
3067
3461
  else {
3068
- handleIncomingData(data, $scope, ctrlState);
3462
+ $scope.handleHttpError(error);
3069
3463
  }
3070
- }, $scope.handleHttpError);
3464
+ });
3071
3465
  },
3072
3466
  scrollTheList: function scrollTheList($scope) {
3073
3467
  var pagesLoaded = $scope.pagesLoaded;
@@ -3087,31 +3481,38 @@ var fng;
3087
3481
  $scope.recordList = $scope.recordList.concat(data);
3088
3482
  }
3089
3483
  else {
3090
- console.log('DEBUG: infinite scroll component asked for a page twice');
3484
+ console.log("DEBUG: infinite scroll component asked for a page twice - the model was " + $scope.modelName);
3091
3485
  }
3092
3486
  }
3093
3487
  else {
3094
- $scope.showError(data, 'Invalid query');
3488
+ $scope.showError(data, "Invalid query");
3095
3489
  }
3096
3490
  }, $scope.handleHttpError);
3097
3491
  },
3098
- // TODO: Do we need model here? Can we not infer it from scope?
3099
- deleteRecord: function deleteRecord(model, id, $scope, ctrlState) {
3100
- SubmissionsService.deleteRecord(model, id)
3492
+ deleteRecord: function deleteRecord(id, $scope, ctrlState) {
3493
+ SubmissionsService.deleteRecord($scope.modelName, id)
3101
3494
  .then(function () {
3102
- if (typeof $scope.dataEventFunctions.onAfterDelete === 'function') {
3495
+ if (typeof $scope.dataEventFunctions.onAfterDelete === "function") {
3103
3496
  $scope.dataEventFunctions.onAfterDelete(ctrlState.master);
3104
3497
  }
3105
- routingService.redirectTo()('list', $scope, $location);
3498
+ routingService.redirectTo()("onDelete", $scope, $location);
3499
+ }, function (err) {
3500
+ if (err.status === 404) {
3501
+ // Someone already deleted it
3502
+ routingService.redirectTo()("onDelete", $scope, $location);
3503
+ }
3504
+ else {
3505
+ $scope.showError(err.statusText + " (" + err.status + ") while deleting record<br />" + err.data, 'Error deleting record');
3506
+ }
3106
3507
  });
3107
3508
  },
3108
3509
  updateDocument: function updateDocument(dataToSave, options, $scope, ctrlState) {
3109
- $scope.phase = 'updating';
3510
+ $scope.phase = "updating";
3110
3511
  SubmissionsService.updateRecord($scope.modelName, $scope.id, dataToSave)
3111
3512
  .then(function (response) {
3112
3513
  var data = response.data;
3113
3514
  if (data.success !== false) {
3114
- if (typeof $scope.dataEventFunctions.onAfterUpdate === 'function') {
3515
+ if (typeof $scope.dataEventFunctions.onAfterUpdate === "function") {
3115
3516
  $scope.dataEventFunctions.onAfterUpdate(data, ctrlState.master);
3116
3517
  }
3117
3518
  if (options.redirect) {
@@ -3130,19 +3531,20 @@ var fng;
3130
3531
  }
3131
3532
  }, $scope.handleHttpError);
3132
3533
  },
3133
- createNew: function createNew(dataToSave, options, $scope) {
3534
+ createNew: function createNew(dataToSave, options, $scope, ctrlState) {
3134
3535
  SubmissionsService.createRecord($scope.modelName, dataToSave)
3135
3536
  .then(function (response) {
3136
3537
  var data = response.data;
3137
3538
  if (data.success !== false) {
3138
- if (typeof $scope.dataEventFunctions.onAfterCreate === 'function') {
3539
+ ctrlState.allowLocationChange = true;
3540
+ if (typeof $scope.dataEventFunctions.onAfterCreate === "function") {
3139
3541
  $scope.dataEventFunctions.onAfterCreate(data);
3140
3542
  }
3141
3543
  if (options.redirect) {
3142
3544
  $window.location = options.redirect;
3143
3545
  }
3144
3546
  else {
3145
- routingService.redirectTo()('edit', $scope, $location, data._id);
3547
+ routingService.redirectTo()("edit", $scope, $location, data._id);
3146
3548
  }
3147
3549
  }
3148
3550
  else {
@@ -3160,9 +3562,9 @@ var fng;
3160
3562
  .then(function (response) {
3161
3563
  var data = response.data;
3162
3564
  var listInstructions = [];
3163
- handleSchema('Lookup ' + lookupCollection, data, null, listInstructions, '', false, $scope, ctrlState);
3565
+ handleSchema("Lookup " + lookupCollection, data, null, listInstructions, "", false, $scope, ctrlState);
3164
3566
  var dataRequest;
3165
- if (typeof schemaElement.filter !== 'undefined' && schemaElement.filter) {
3567
+ if (typeof schemaElement.filter !== "undefined" && schemaElement.filter) {
3166
3568
  dataRequest = SubmissionsService.getPagedAndFilteredList(lookupCollection, schemaElement.filter);
3167
3569
  }
3168
3570
  else {
@@ -3170,25 +3572,30 @@ var fng;
3170
3572
  }
3171
3573
  dataRequest
3172
3574
  .then(function (response) {
3173
- var data = response.data;
3575
+ var data = angular.copy(response.data);
3174
3576
  if (data) {
3175
3577
  for (var i = 0; i < data.length; i++) {
3176
- var option = '';
3578
+ var option = "";
3177
3579
  for (var j = 0; j < listInstructions.length; j++) {
3178
3580
  var thisVal = data[i][listInstructions[j].name];
3179
- option += thisVal ? thisVal + ' ' : '';
3581
+ option += thisVal ? thisVal + " " : "";
3180
3582
  }
3181
3583
  option = option.trim();
3182
3584
  var pos = _.sortedIndex(optionsList, option);
3183
3585
  // handle dupes (ideally people will use unique indexes to stop them but...)
3184
3586
  if (optionsList[pos] === option) {
3185
- option = option + ' (' + data[i]._id + ')';
3587
+ option = option + " (" + data[i]._id + ")";
3186
3588
  pos = _.sortedIndex(optionsList, option);
3187
3589
  }
3188
3590
  optionsList.splice(pos, 0, option);
3189
3591
  idList.splice(pos, 0, data[i]._id);
3190
3592
  }
3191
- updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
3593
+ if ($scope.readingRecord) {
3594
+ $scope.readingRecord
3595
+ .then(function () {
3596
+ updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
3597
+ });
3598
+ }
3192
3599
  }
3193
3600
  });
3194
3601
  });
@@ -3196,31 +3603,9 @@ var fng;
3196
3603
  setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
3197
3604
  var optionsList = $scope[formInstructions.options] = [];
3198
3605
  var idList = $scope[formInstructions.ids] = [];
3199
- if (ref.id[0] === '$') {
3200
- // id of document we are doing lookup from comes from record, so we need to deal with in $watch
3201
- // by adding it to listLookups
3202
- var nameElements = formInstructions.name.split('.');
3203
- var refHandler = $scope.listLookups.find(function (lkp) {
3204
- return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3205
- });
3206
- var thisHandler = {
3207
- formInstructions: formInstructions,
3208
- lastPart: nameElements.pop(),
3209
- possibleArray: nameElements.join('.')
3210
- };
3211
- if (!refHandler) {
3212
- refHandler = {
3213
- ref: ref,
3214
- lookupOptions: [],
3215
- lookupIds: [],
3216
- handlers: []
3217
- };
3218
- $scope.listLookups.push(refHandler);
3219
- }
3220
- refHandler.handlers.push(thisHandler);
3221
- $scope[formInstructions.options] = refHandler.lookupOptions;
3222
- $scope[formInstructions.ids] = refHandler.lookupIds;
3223
- // TODO DRY this and handleInternalLookup below
3606
+ if (ref.id[0] === "$") {
3607
+ // id of document that contains out lookup list comes from record, so we need to deal with in $watch by adding it to listLookups
3608
+ addArrayLookupToLookupList($scope, formInstructions, ref, $scope.listLookups);
3224
3609
  }
3225
3610
  else {
3226
3611
  // we can do it now
@@ -3231,7 +3616,7 @@ var fng;
3231
3616
  var pos = _.sortedIndex(optionsList, option);
3232
3617
  // handle dupes
3233
3618
  if (optionsList[pos] === option) {
3234
- option = option + ' (' + data[i]._id + ')';
3619
+ option = option + " (" + data[i]._id + ")";
3235
3620
  pos = _.sortedIndex(optionsList, option);
3236
3621
  }
3237
3622
  optionsList.splice(pos, 0, option);
@@ -3242,27 +3627,7 @@ var fng;
3242
3627
  }
3243
3628
  },
3244
3629
  handleInternalLookup: function handleInternalLookup($scope, formInstructions, ref) {
3245
- var nameElements = formInstructions.name.split('.');
3246
- var refHandler = $scope.internalLookups.find(function (lkp) {
3247
- return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3248
- });
3249
- var thisHandler = {
3250
- formInstructions: formInstructions,
3251
- lastPart: nameElements.pop(),
3252
- possibleArray: nameElements.join('.')
3253
- };
3254
- if (!refHandler) {
3255
- refHandler = {
3256
- ref: ref,
3257
- lookupOptions: [],
3258
- lookupIds: [],
3259
- handlers: []
3260
- };
3261
- $scope.internalLookups.push(refHandler);
3262
- }
3263
- refHandler.handlers.push(thisHandler);
3264
- $scope[formInstructions.options] = refHandler.lookupOptions;
3265
- $scope[formInstructions.ids] = refHandler.lookupIds;
3630
+ addArrayLookupToLookupList($scope, formInstructions, ref, $scope.internalLookups);
3266
3631
  },
3267
3632
  preservePristine: preservePristine,
3268
3633
  // Reverse the process of convertToAngularModel
@@ -3279,48 +3644,54 @@ var fng;
3279
3644
  }
3280
3645
  return retVal;
3281
3646
  }
3282
- for (var i = 0; i < schema.length; i++) {
3283
- var fieldname = schema[i].name.slice(prefixLength);
3284
- var thisField = getListData($scope, anObject, fieldname);
3285
- if (schema[i].schema) {
3647
+ var _loop_1 = function () {
3648
+ var schemaI = schema[i];
3649
+ var fieldname = schemaI.name.slice(prefixLength);
3650
+ var thisField = getListData(anObject, fieldname, null, $scope);
3651
+ if (schemaI.schema) {
3286
3652
  if (thisField) {
3287
3653
  for (var j = 0; j < thisField.length; j++) {
3288
- thisField[j] = convertToMongoModel(schema[i].schema, thisField[j], prefixLength + 1 + fieldname.length, $scope, fieldname);
3654
+ thisField[j] = convertToMongoModel(schemaI.schema, thisField[j], 1 + fieldname.length, $scope, fieldname);
3289
3655
  }
3290
3656
  }
3291
3657
  }
3292
3658
  else {
3293
3659
  // Convert {array:[{x:'item 1'}]} to {array:['item 1']}
3294
- if (schema[i].array && simpleArrayNeedsX(schema[i]) && thisField) {
3660
+ if (schemaI.array && simpleArrayNeedsX(schemaI) && thisField) {
3295
3661
  for (var k = 0; k < thisField.length; k++) {
3296
3662
  thisField[k] = thisField[k].x;
3297
3663
  }
3298
3664
  }
3299
3665
  // Convert {lookup:'List description for 012abcde'} to {lookup:'012abcde'}
3300
- var idList = $scope[suffixCleanId(schema[i], '_ids')];
3301
- var thisConversion = void 0;
3302
- if (idList && idList.length > 0) {
3666
+ var idList_1 = $scope[suffixCleanId(schemaI, "_ids")];
3667
+ if (idList_1 && idList_1.length > 0) {
3303
3668
  updateObject(fieldname, anObject, function (value) {
3304
- return convertToForeignKeys(schema[i], value, $scope[suffixCleanId(schema[i], 'Options')], idList);
3669
+ return convertToForeignKeys(schemaI, value, $scope[suffixCleanId(schemaI, "Options")], idList_1);
3305
3670
  });
3306
3671
  }
3307
- else if (thisConversion = getConversionObject($scope, fieldname, schemaName)) {
3308
- var lookup = getData(anObject, fieldname, null);
3309
- var newVal;
3310
- if (schema[i].array) {
3311
- newVal = [];
3312
- if (lookup) {
3313
- for (var n = 0; n < lookup.length; n++) {
3314
- newVal[n] = convertLookup(lookup[n], thisConversion);
3672
+ else {
3673
+ var thisConversion = getConversionObject($scope, fieldname, schemaName);
3674
+ if (thisConversion) {
3675
+ var lookup = getData(anObject, fieldname, null);
3676
+ var newVal = void 0;
3677
+ if (schemaI.array) {
3678
+ newVal = [];
3679
+ if (lookup) {
3680
+ for (var n = 0; n < lookup.length; n++) {
3681
+ newVal[n] = convertLookup(lookup[n], thisConversion);
3682
+ }
3315
3683
  }
3316
3684
  }
3685
+ else {
3686
+ newVal = convertLookup(lookup, thisConversion);
3687
+ }
3688
+ setData(anObject, fieldname, null, newVal);
3317
3689
  }
3318
- else {
3319
- newVal = convertLookup(lookup, thisConversion);
3320
- }
3321
- setData(anObject, fieldname, null, newVal);
3322
3690
  }
3323
3691
  }
3692
+ };
3693
+ for (var i = 0; i < schema.length; i++) {
3694
+ _loop_1();
3324
3695
  }
3325
3696
  return anObject;
3326
3697
  },
@@ -3330,7 +3701,7 @@ var fng;
3330
3701
  $scope.handleHttpError = handleError($scope);
3331
3702
  $scope.cancel = function () {
3332
3703
  angular.copy(ctrlState.master, $scope.record);
3333
- $scope.$broadcast('fngCancel', $scope);
3704
+ $scope.$broadcast("fngCancel", $scope);
3334
3705
  // Let call backs etc resolve in case they dirty form, then clean it
3335
3706
  $timeout($scope.setPristine);
3336
3707
  };
@@ -3339,15 +3710,21 @@ var fng;
3339
3710
  // scope.$emit('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
3340
3711
  // or
3341
3712
  // scope.$broadcast('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
3342
- $scope.$on('showErrorMessage', function (event, args) {
3343
- $scope.showError(args.body, args.title);
3713
+ $scope.$on("showErrorMessage", function (event, args) {
3714
+ if (!event.defaultPrevented) {
3715
+ event.defaultPrevented = true;
3716
+ $scope.showError(args.body, args.title);
3717
+ }
3344
3718
  });
3345
3719
  $scope.showError = function (error, alertTitle) {
3346
- $scope.alertTitle = alertTitle ? alertTitle : 'Error!';
3347
- if (typeof error === 'string') {
3720
+ $scope.alertTitle = alertTitle ? alertTitle : "Error!";
3721
+ if (typeof error === "string") {
3348
3722
  $scope.errorMessage = error;
3349
3723
  }
3350
- else if (error.message && typeof error.message === 'string') {
3724
+ else if (!error) {
3725
+ $scope.errorMessage = "An error occurred - that's all we got. Sorry.";
3726
+ }
3727
+ else if (error.message && typeof error.message === "string") {
3351
3728
  $scope.errorMessage = error.message;
3352
3729
  }
3353
3730
  else if (error.data && error.data.message) {
@@ -3361,16 +3738,35 @@ var fng;
3361
3738
  $scope.errorMessage = error;
3362
3739
  }
3363
3740
  }
3741
+ $scope.errorHideTimer = window.setTimeout(function () {
3742
+ $scope.dismissError();
3743
+ $scope.$digest();
3744
+ }, 3500 + (1000 * ($scope.alertTitle + $scope.errorMessage).length / 50));
3745
+ $scope.errorVisible = true;
3746
+ window.setTimeout(function () {
3747
+ $scope.$digest();
3748
+ });
3749
+ };
3750
+ $scope.clearTimeout = function () {
3751
+ if ($scope.errorHideTimer) {
3752
+ clearTimeout($scope.errorHideTimer);
3753
+ delete $scope.errorHideTimer;
3754
+ }
3364
3755
  };
3365
3756
  $scope.dismissError = function () {
3757
+ $scope.clearTimeout;
3758
+ $scope.errorVisible = false;
3366
3759
  delete $scope.errorMessage;
3367
3760
  delete $scope.alertTitle;
3368
3761
  };
3762
+ $scope.stickError = function () {
3763
+ clearTimeout($scope.errorHideTimer);
3764
+ };
3369
3765
  $scope.prepareForSave = function (cb) {
3370
3766
  //Convert the lookup values into ids
3371
3767
  var dataToSave = recordHandlerInstance.convertToMongoModel($scope.formSchema, angular.copy($scope.record), 0, $scope);
3372
3768
  if ($scope.id) {
3373
- if (typeof $scope.dataEventFunctions.onBeforeUpdate === 'function') {
3769
+ if (typeof $scope.dataEventFunctions.onBeforeUpdate === "function") {
3374
3770
  $scope.dataEventFunctions.onBeforeUpdate(dataToSave, ctrlState.master, function (err) {
3375
3771
  if (err) {
3376
3772
  cb(err);
@@ -3385,7 +3781,7 @@ var fng;
3385
3781
  }
3386
3782
  }
3387
3783
  else {
3388
- if (typeof $scope.dataEventFunctions.onBeforeCreate === 'function') {
3784
+ if (typeof $scope.dataEventFunctions.onBeforeCreate === "function") {
3389
3785
  $scope.dataEventFunctions.onBeforeCreate(dataToSave, function (err) {
3390
3786
  if (err) {
3391
3787
  cb(err);
@@ -3404,22 +3800,24 @@ var fng;
3404
3800
  options = options || {};
3405
3801
  $scope.prepareForSave(function (err, dataToSave) {
3406
3802
  if (err) {
3407
- if (err !== '_update_handled_') {
3408
- $scope.showError(err);
3803
+ if (err !== "_update_handled_") {
3804
+ $timeout(function () {
3805
+ $scope.showError(err);
3806
+ });
3409
3807
  }
3410
3808
  }
3411
3809
  else if ($scope.id) {
3412
3810
  recordHandlerInstance.updateDocument(dataToSave, options, $scope, ctrlState);
3413
3811
  }
3414
3812
  else {
3415
- recordHandlerInstance.createNew(dataToSave, options, $scope);
3813
+ recordHandlerInstance.createNew(dataToSave, options, $scope, ctrlState);
3416
3814
  }
3417
3815
  });
3418
3816
  };
3419
3817
  $scope.newClick = function () {
3420
- routingService.redirectTo()('new', $scope, $location);
3818
+ routingService.redirectTo()("new", $scope, $location);
3421
3819
  };
3422
- $scope.$on('$locationChangeStart', function (event, next) {
3820
+ $scope.$on("$locationChangeStart", function (event, next) {
3423
3821
  // let changed = !$scope.isCancelDisabled();
3424
3822
  // let curPath = window.location.href.split('/');
3425
3823
  // let nextPath = next.split('/');
@@ -3438,21 +3836,12 @@ var fng;
3438
3836
  if (!ctrlState.allowLocationChange && !$scope.isCancelDisabled()) {
3439
3837
  event.preventDefault();
3440
3838
  var modalInstance = $uibModal.open({
3441
- template: '<div class="modal-header">' +
3442
- ' <h3>Record modified</h3>' +
3443
- '</div>' +
3444
- '<div class="modal-body">' +
3445
- ' <p>Would you like to save your changes?</p>' +
3446
- '</div>' +
3447
- '<div class="modal-footer">' +
3448
- ' <button class="btn btn-primary dlg-yes" ng-click="yes()">Yes</button>' +
3449
- ' <button class="btn btn-warning dlg-no" ng-click="no()">No</button>' +
3450
- ' <button class="btn dlg-cancel" ng-click="cancel()">Cancel</button>' +
3451
- '</div>',
3452
- controller: 'SaveChangesModalCtrl',
3453
- backdrop: 'static'
3839
+ template: "<div class=\"modal-header\">\n <h3>Record modified</h3>\n</div>\n<div class=\"modal-body\">\n <p>Would you like to save your changes?</p>\n</div>\n<div class=\"modal-footer\">\n <button class=\"btn btn-primary dlg-yes\" ng-click=\"yes()\">Yes</button>\n <button class=\"btn btn-warning dlg-no\" ng-click=\"no()\">No</button>\n <button class=\"btn dlg-cancel\" ng-click=\"cancel()\">Cancel</button>\n</div>",
3840
+ controller: "SaveChangesModalCtrl",
3841
+ backdrop: "static"
3454
3842
  });
3455
- modalInstance.result.then(function (result) {
3843
+ modalInstance.result
3844
+ .then(function (result) {
3456
3845
  if (result) {
3457
3846
  $scope.save({ redirect: next, allowChange: true }); // save changes
3458
3847
  }
@@ -3460,7 +3849,8 @@ var fng;
3460
3849
  ctrlState.allowLocationChange = true;
3461
3850
  $window.location = next;
3462
3851
  }
3463
- });
3852
+ })
3853
+ .catch(_handleCancel);
3464
3854
  }
3465
3855
  });
3466
3856
  $scope.deleteClick = function () {
@@ -3471,85 +3861,168 @@ var fng;
3471
3861
  }
3472
3862
  else {
3473
3863
  var modalInstance = $uibModal.open({
3474
- template: '<div class="modal-header">' +
3475
- ' <h3>Delete Item</h3>' +
3476
- '</div>' +
3477
- '<div class="modal-body">' +
3478
- ' <p>Are you sure you want to delete this record?</p>' +
3479
- '</div>' +
3480
- '<div class="modal-footer">' +
3481
- ' <button class="btn btn-primary dlg-no" ng-click="cancel()">No</button>' +
3482
- ' <button class="btn btn-warning dlg-yes" ng-click="yes()">Yes</button>' +
3483
- '</div>',
3484
- controller: 'SaveChangesModalCtrl',
3485
- backdrop: 'static'
3864
+ template: "<div class=\"modal-header\">\n <h3>Delete Item</h3>\n</div>\n<div class=\"modal-body\">\n <p>Are you sure you want to delete this record?</p>\n</div>\n<div class=\"modal-footer\">\n <button class=\"btn btn-primary dlg-no\" ng-click=\"cancel()\">No</button>\n <button class=\"btn btn-warning dlg-yes\" ng-click=\"yes()\">Yes</button>\n</div>",
3865
+ controller: "SaveChangesModalCtrl",
3866
+ backdrop: "static"
3486
3867
  });
3487
3868
  confirmDelete = modalInstance.result;
3488
3869
  }
3489
3870
  confirmDelete.then(function (result) {
3871
+ function doTheDeletion() {
3872
+ recordHandlerInstance.deleteRecord($scope.id, $scope, ctrlState);
3873
+ }
3490
3874
  if (result) {
3491
- if (typeof $scope.dataEventFunctions.onBeforeDelete === 'function') {
3875
+ if (typeof $scope.dataEventFunctions.onBeforeDelete === "function") {
3492
3876
  $scope.dataEventFunctions.onBeforeDelete(ctrlState.master, function (err) {
3493
3877
  if (err) {
3494
- if (err !== '_delete_handled_') {
3878
+ if (err !== "_delete_handled_") {
3495
3879
  $scope.showError(err);
3496
3880
  }
3497
3881
  }
3498
3882
  else {
3499
- recordHandlerInstance.deleteRecord($scope.modelName, $scope.id, $scope, ctrlState);
3883
+ doTheDeletion();
3500
3884
  }
3501
3885
  });
3502
3886
  }
3503
3887
  else {
3504
- recordHandlerInstance.deleteRecord($scope.modelName, $scope.id, $scope, ctrlState);
3888
+ doTheDeletion();
3505
3889
  }
3506
3890
  }
3507
- });
3891
+ })
3892
+ .catch(_handleCancel);
3508
3893
  }
3509
3894
  };
3510
3895
  $scope.isCancelDisabled = function () {
3511
- if (typeof $scope.disableFunctions.isCancelDisabled === 'function') {
3896
+ if ($scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine) {
3897
+ return true;
3898
+ }
3899
+ else if (typeof $scope.disableFunctions.isCancelDisabled === "function") {
3512
3900
  return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3513
3901
  }
3514
3902
  else {
3515
- return $scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine;
3903
+ return false;
3516
3904
  }
3517
3905
  };
3518
3906
  $scope.isSaveDisabled = function () {
3519
- if (typeof $scope.disableFunctions.isSaveDisabled === 'function') {
3520
- return $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3907
+ $scope.whyDisabled = undefined;
3908
+ var pristine = false;
3909
+ function generateWhyDisabledMessage(form, subFormName) {
3910
+ form.$$controls.forEach(function (c) {
3911
+ if (c.$invalid) {
3912
+ if (c.$$controls) {
3913
+ // nested form
3914
+ generateWhyDisabledMessage(c, c.$name);
3915
+ }
3916
+ else {
3917
+ $scope.whyDisabled += "<br /><strong>";
3918
+ if (subFormName) {
3919
+ $scope.whyDisabled += subFormName + ' ';
3920
+ }
3921
+ if (cssFrameworkService.framework() === "bs2" &&
3922
+ c.$$element &&
3923
+ c.$$element.parent() &&
3924
+ c.$$element.parent().parent() &&
3925
+ c.$$element.parent().parent().find("label") &&
3926
+ c.$$element.parent().parent().find("label").text()) {
3927
+ $scope.whyDisabled += c.$$element.parent().parent().find("label").text();
3928
+ }
3929
+ else if (cssFrameworkService.framework() === "bs3" &&
3930
+ c.$$element &&
3931
+ c.$$element.parent() &&
3932
+ c.$$element.parent().parent() &&
3933
+ c.$$element.parent().parent().parent() &&
3934
+ c.$$element.parent().parent().parent().find("label") &&
3935
+ c.$$element.parent().parent().parent().find("label").text()) {
3936
+ $scope.whyDisabled += c.$$element.parent().parent().parent().find("label").text();
3937
+ }
3938
+ else {
3939
+ $scope.whyDisabled += c.$name;
3940
+ }
3941
+ $scope.whyDisabled += "</strong>: ";
3942
+ if (c.$error) {
3943
+ for (var type in c.$error) {
3944
+ if (c.$error.hasOwnProperty(type)) {
3945
+ switch (type) {
3946
+ case "required":
3947
+ $scope.whyDisabled += "Field missing required value. ";
3948
+ break;
3949
+ case "pattern":
3950
+ $scope.whyDisabled += "Field does not match required pattern. ";
3951
+ break;
3952
+ default:
3953
+ $scope.whyDisabled += type + ". ";
3954
+ }
3955
+ }
3956
+ }
3957
+ }
3958
+ }
3959
+ }
3960
+ });
3961
+ }
3962
+ if ($scope[$scope.topLevelFormName]) {
3963
+ if ($scope[$scope.topLevelFormName].$invalid) {
3964
+ $scope.whyDisabled = 'The form data is invalid:';
3965
+ generateWhyDisabledMessage($scope[$scope.topLevelFormName]);
3966
+ }
3967
+ else if ($scope[$scope.topLevelFormName].$pristine) {
3968
+ // Don't have disabled message - should be obvious from Cancel being disabled,
3969
+ // and the message comes up when the Save button is clicked.
3970
+ pristine = true;
3971
+ }
3972
+ }
3973
+ else {
3974
+ $scope.whyDisabled = "Top level form name invalid";
3975
+ }
3976
+ if (pristine || !!$scope.whyDisabled) {
3977
+ return true;
3978
+ }
3979
+ else if (typeof $scope.disableFunctions.isSaveDisabled !== "function") {
3980
+ return false;
3521
3981
  }
3522
3982
  else {
3523
- return ($scope[$scope.topLevelFormName] && ($scope[$scope.topLevelFormName].$invalid || $scope[$scope.topLevelFormName].$pristine));
3983
+ var retVal = $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3984
+ if (typeof retVal === "string") {
3985
+ $scope.whyDisabled = retVal;
3986
+ }
3987
+ else {
3988
+ $scope.whyDisabled = "An application level user-specified function is inhibiting saving the record";
3989
+ }
3990
+ return !!retVal;
3524
3991
  }
3525
3992
  };
3526
3993
  $scope.isDeleteDisabled = function () {
3527
- if (typeof $scope.disableFunctions.isDeleteDisabled === 'function') {
3994
+ if (!$scope.id) {
3995
+ return true;
3996
+ }
3997
+ else if (typeof $scope.disableFunctions.isDeleteDisabled === "function") {
3528
3998
  return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3529
3999
  }
3530
4000
  else {
3531
- return (!$scope.id);
4001
+ return false;
3532
4002
  }
3533
4003
  };
3534
4004
  $scope.isNewDisabled = function () {
3535
- if (typeof $scope.disableFunctions.isNewDisabled === 'function') {
4005
+ if (typeof $scope.disableFunctions.isNewDisabled === "function") {
3536
4006
  return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3537
4007
  }
3538
4008
  else {
3539
4009
  return false;
3540
4010
  }
3541
4011
  };
3542
- $scope.disabledText = function (localStyling) {
3543
- var text = '';
3544
- if ($scope.isSaveDisabled) {
3545
- text = 'This button is only enabled when the form is complete and valid. Make sure all required inputs are filled in. ' + localStyling;
3546
- }
3547
- return text;
4012
+ $scope.setDefaults = function (formSchema, base) {
4013
+ if (base === void 0) { base = ''; }
4014
+ var retVal = {};
4015
+ formSchema.forEach(function (s) {
4016
+ if (s.defaultValue !== undefined) {
4017
+ retVal[s.name.replace(base, '')] = s.defaultValue;
4018
+ }
4019
+ });
4020
+ return retVal;
3548
4021
  };
3549
4022
  $scope.getVal = function (expression, index) {
3550
- if (expression.indexOf('$index') === -1 || typeof index !== 'undefined') {
4023
+ if (expression.indexOf("$index") === -1 || typeof index !== "undefined") {
3551
4024
  expression = expression.replace(/\$index/g, index);
3552
- return $scope.$eval('record.' + expression);
4025
+ return $scope.$eval("record." + expression);
3553
4026
  }
3554
4027
  //else {
3555
4028
  // Used to show error here, but angular seems to call before record is populated sometimes
@@ -3563,6 +4036,30 @@ var fng;
3563
4036
  }
3564
4037
  }
3565
4038
  };
4039
+ $scope.setUpCustomLookupOptions = function (schemaElement, ids, options, baseScope) {
4040
+ for (var _i = 0, _a = [$scope, baseScope]; _i < _a.length; _i++) {
4041
+ var scope = _a[_i];
4042
+ if (scope) {
4043
+ // need to be accessible on our scope for generation of the select options, and - for nested schemas -
4044
+ // on baseScope for the conversion back to ids done by prepareForSave
4045
+ scope[schemaElement.ids] = ids;
4046
+ scope[schemaElement.options] = options;
4047
+ }
4048
+ }
4049
+ var data = getData($scope.record, schemaElement.name);
4050
+ if (!data) {
4051
+ return;
4052
+ }
4053
+ if (angular.isArray(data)) {
4054
+ for (var i = 0; i < data.length; i++) {
4055
+ data[i] = convertIdToListValue(data[i], ids, options, schemaElement.name);
4056
+ }
4057
+ }
4058
+ else {
4059
+ data = convertIdToListValue(data, ids, options, schemaElement.name);
4060
+ }
4061
+ setData($scope.record, schemaElement.name, undefined, data);
4062
+ };
3566
4063
  },
3567
4064
  fillFormFromBackendCustomSchema: fillFormFromBackendCustomSchema,
3568
4065
  fillFormWithBackendSchema: function fillFormWithBackendSchema($scope, formGeneratorInstance, recordHandlerInstance, ctrlState) {
@@ -3602,6 +4099,7 @@ var fng;
3602
4099
  /*@ngInject*/
3603
4100
  SubmissionsService.$inject = ["$http", "$cacheFactory"];
3604
4101
  function SubmissionsService($http, $cacheFactory) {
4102
+ var useCacheForGetAll = true;
3605
4103
  /*
3606
4104
  generate a query string for a filtered and paginated query for submissions.
3607
4105
  options consists of the following:
@@ -3659,11 +4157,7 @@ var fng;
3659
4157
  // };
3660
4158
  // },
3661
4159
  getListAttributes: function (ref, id) {
3662
- if (typeof ref === 'string') {
3663
- ref = { type: 'lookup', collection: ref };
3664
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(ref));
3665
- }
3666
- return $http.get('/api/' + ref.collection + '/' + id + '/list');
4160
+ return $http.get('/api/' + ref + '/' + id + '/list');
3667
4161
  },
3668
4162
  readRecord: function (modelName, id) {
3669
4163
  // TODO Figure out tab history updates (check for other tab-history-todos)
@@ -3679,7 +4173,7 @@ var fng;
3679
4173
  },
3680
4174
  getAll: function (modelName, _options) {
3681
4175
  var options = angular.extend({
3682
- cache: true
4176
+ cache: useCacheForGetAll
3683
4177
  }, _options);
3684
4178
  return $http.get('/api/' + modelName, options);
3685
4179
  },
@@ -3696,6 +4190,15 @@ var fng;
3696
4190
  createRecord: function (modelName, dataToSave) {
3697
4191
  $cacheFactory.get('$http').remove('/api/' + modelName);
3698
4192
  return $http.post('/api/' + modelName, dataToSave);
4193
+ },
4194
+ useCache: function (val) {
4195
+ useCacheForGetAll = val;
4196
+ },
4197
+ getCache: function () {
4198
+ return !!$cacheFactory.get('$http');
4199
+ },
4200
+ clearCache: function () {
4201
+ $cacheFactory.get('$http').removeAll();
3699
4202
  }
3700
4203
  };
3701
4204
  }
@@ -3721,6 +4224,7 @@ var fng;
3721
4224
  fngInvalidRequired: 'fng-invalid-required',
3722
4225
  allowLocationChange: true // Set when the data arrives..
3723
4226
  };
4227
+ $scope.errorVisible = false;
3724
4228
  angular.extend($scope, routingService.parsePathFunc()($location.$$path));
3725
4229
  // Load context menu. For /person/client/:id/edit we need
3726
4230
  // to load PersonCtrl and PersonClientCtrl
@@ -3735,17 +4239,33 @@ var fng;
3735
4239
  $rootScope.$broadcast('fngFormLoadStart', $scope);
3736
4240
  formGenerator.decorateScope($scope, formGenerator, recordHandler, $scope.sharedData);
3737
4241
  recordHandler.decorateScope($scope, $uibModal, recordHandler, ctrlState);
3738
- recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
3739
- // Tell the 'model controllers' that they can start fiddling with basescope
3740
- for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
3741
- if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
3742
- $scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
4242
+ function processTheForm() {
4243
+ recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
4244
+ // Tell the 'model controllers' that they can start fiddling with baseScope
4245
+ for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
4246
+ if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
4247
+ $scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
4248
+ }
3743
4249
  }
4250
+ $scope.$on('$destroy', function () {
4251
+ $scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
4252
+ $rootScope.$broadcast('fngControllersUnloaded');
4253
+ });
4254
+ }
4255
+ //Check that we are ready
4256
+ if (typeof fng.formsAngular.beforeProcess === "function") {
4257
+ fng.formsAngular.beforeProcess($scope, function (err) {
4258
+ if (err) {
4259
+ $scope.showError(err.message, 'Error preparing to process form');
4260
+ }
4261
+ else {
4262
+ processTheForm();
4263
+ }
4264
+ });
4265
+ }
4266
+ else {
4267
+ processTheForm();
3744
4268
  }
3745
- $scope.$on('$destroy', function () {
3746
- $scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
3747
- $rootScope.$broadcast('fngControllersUnloaded');
3748
- });
3749
4269
  }
3750
4270
  controllers.BaseCtrl = BaseCtrl;
3751
4271
  })(controllers = fng.controllers || (fng.controllers = {}));
@@ -3801,13 +4321,17 @@ var fng;
3801
4321
  var controllers;
3802
4322
  (function (controllers) {
3803
4323
  /*@ngInject*/
3804
- NavCtrl.$inject = ["$scope", "$location", "$filter", "routingService", "cssFrameworkService"];
3805
- function NavCtrl($scope, $location, $filter, routingService, cssFrameworkService) {
4324
+ NavCtrl.$inject = ["$rootScope", "$scope", "$location", "$filter", "routingService", "cssFrameworkService"];
4325
+ function NavCtrl($rootScope, $scope, $location, $filter, routingService, cssFrameworkService) {
3806
4326
  function clearContextMenu() {
3807
4327
  $scope.items = [];
3808
4328
  $scope.contextMenu = undefined;
3809
4329
  }
4330
+ $rootScope.navScope = $scope; // Lets plugins access menus
3810
4331
  clearContextMenu();
4332
+ $scope.toggleCollapsed = function () {
4333
+ $scope.collapsed = !$scope.collapsed;
4334
+ };
3811
4335
  /* isCollapsed and showShortcuts are used to control how the menu is displayed in a responsive environment and whether the shortcut keystrokes help should be displayed */
3812
4336
  $scope.isCollapsed = true;
3813
4337
  $scope.showShortcuts = false;
@@ -3835,8 +4359,8 @@ var fng;
3835
4359
  }
3836
4360
  }
3837
4361
  function filter(event) {
3838
- var tagName = (event.target || event.srcElement).tagName;
3839
- return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
4362
+ var tagName = (event.target).tagName;
4363
+ return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' || tagName == "DIV" && event.target.classList.contains('ck-editor__editable'));
3840
4364
  }
3841
4365
  //console.log(event.keyCode, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey);
3842
4366
  if (event.keyCode === 191 && (filter(event) || (event.ctrlKey && !event.altKey && !event.metaKey))) {
@@ -3865,7 +4389,7 @@ var fng;
3865
4389
  else if (event.keyCode === 45 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
3866
4390
  deferredBtnClick('newButton'); // Ctrl+Shift+Ins creates New record
3867
4391
  }
3868
- else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && event.altKey && !event.metaKey) {
4392
+ else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
3869
4393
  deferredBtnClick('deleteButton'); // Ctrl+Shift+X deletes record
3870
4394
  }
3871
4395
  };
@@ -3881,6 +4405,11 @@ var fng;
3881
4405
  };
3882
4406
  $scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
3883
4407
  $scope.contextMenu = sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false);
4408
+ if (sharedData.dropDownDisplayPromise) {
4409
+ sharedData.dropDownDisplayPromise.then(function (value) {
4410
+ $scope.contextMenu = value;
4411
+ });
4412
+ }
3884
4413
  });
3885
4414
  $scope.$on('fngControllersUnloaded', function (evt) {
3886
4415
  clearContextMenu();
@@ -3896,28 +4425,59 @@ var fng;
3896
4425
  }
3897
4426
  else {
3898
4427
  // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
3899
- var args = item.args || [], fn = item.fn;
3900
- switch (args.length) {
3901
- case 0:
3902
- fn();
3903
- break;
3904
- case 1:
3905
- fn(args[0]);
3906
- break;
3907
- case 2:
3908
- fn(args[0], args[1]);
3909
- break;
3910
- case 3:
3911
- fn(args[0], args[1], args[2]);
3912
- break;
3913
- case 4:
3914
- fn(args[0], args[1], args[2], args[3]);
3915
- break;
4428
+ var args = item.args || [];
4429
+ var fn = item.fn;
4430
+ if (typeof fn === "function") {
4431
+ switch (args.length) {
4432
+ case 0:
4433
+ fn();
4434
+ break;
4435
+ case 1:
4436
+ fn(args[0]);
4437
+ break;
4438
+ case 2:
4439
+ fn(args[0], args[1]);
4440
+ break;
4441
+ case 3:
4442
+ fn(args[0], args[1], args[2]);
4443
+ break;
4444
+ case 4:
4445
+ fn(args[0], args[1], args[2], args[3]);
4446
+ break;
4447
+ }
4448
+ }
4449
+ else if (fn) {
4450
+ throw new Error("Incorrect menu setup");
3916
4451
  }
3917
4452
  }
3918
4453
  };
3919
4454
  $scope.isHidden = function (index) {
3920
- return $scope.items[index].isHidden ? $scope.items[index].isHidden() : false;
4455
+ function explicitlyHidden(item) {
4456
+ return item.isHidden ? item.isHidden() : false;
4457
+ }
4458
+ var dividerHide = false;
4459
+ // Hide a divider if it appears under another
4460
+ if ($scope.items[index].divider) {
4461
+ if (index === 0) {
4462
+ dividerHide = true;
4463
+ }
4464
+ else {
4465
+ var foundVisible = false;
4466
+ var check = index - 1;
4467
+ while (check >= 0 && !dividerHide && !foundVisible) {
4468
+ if ($scope.items[check].divider) {
4469
+ dividerHide = true;
4470
+ }
4471
+ else if (!explicitlyHidden($scope.items[check])) {
4472
+ foundVisible = true;
4473
+ }
4474
+ else {
4475
+ --check;
4476
+ }
4477
+ }
4478
+ }
4479
+ }
4480
+ return dividerHide || explicitlyHidden($scope.items[index]);
3921
4481
  };
3922
4482
  $scope.isDisabled = function (index) {
3923
4483
  return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;
@@ -3976,6 +4536,7 @@ var fng;
3976
4536
  ])
3977
4537
  .controller('BaseCtrl', fng.controllers.BaseCtrl)
3978
4538
  .controller('SaveChangesModalCtrl', fng.controllers.SaveChangesModalCtrl)
4539
+ .controller('LinkCtrl', fng.controllers.LinkCtrl)
3979
4540
  .controller('ModelCtrl', fng.controllers.ModelCtrl)
3980
4541
  .controller('NavCtrl', fng.controllers.NavCtrl)
3981
4542
  .directive('modelControllerDropdown', fng.directives.modelControllerDropdown)
@@ -3987,6 +4548,7 @@ var fng;
3987
4548
  .directive('fngNakedDate', fng.directives.fngNakedDate)
3988
4549
  .filter('camelCase', fng.filters.camelCase)
3989
4550
  .filter('titleCase', fng.filters.titleCase)
4551
+ .filter('extractTimestampFromMongoID', fng.filters.extractTimestampFromMongoID)
3990
4552
  .service('addAllService', fng.services.addAllService)
3991
4553
  .provider('cssFrameworkService', fng.services.cssFrameworkService)
3992
4554
  .provider('routingService', fng.services.routingService)
@@ -4002,11 +4564,15 @@ var fng;
4002
4564
  // expose the library
4003
4565
  var formsAngular = fng.formsAngular;
4004
4566
 
4005
- angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <div class="container-fluid page-header report-header">\n <div ng-class="css(\'rowFluid\')">\n <div class="header-lhs col-xs-7 span7">\n <h1>{{ reportSchema.title }}</h1>\n </div>\n <div class="header-rhs col-xs-5 span5">\n <form-input schema="paramSchema" name="paramForm" ng-show="paramSchema" formstyle="horizontalCompact"></form-input>\n </div>\n </div>\n </div>\n <div class="container-fluid page-body report-body">\n <error-display></error-display>\n <div class="row-fluid">\n <div class="gridStyle" ui-grid="gridOptions" ui-grid-selection></div>\n </div>\n </div>\n</div>\n');
4006
- $templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h4>{{modelNameDisplay}} :\n <span ng-repeat="field in listSchema">{{getListData(record, field.name)}} </span>\n </h4>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <div form-buttons></div>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <error-display></error-display>\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact"></form-input>\n </div>\n <!--{{ formSchema | json }}-->\n</div>\n');
4007
- $templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <a ng-href="{{generateNewUrl()}}"><button id="newBtn" class="btn btn-default"><i class="icon-plus"></i> New</button></a>\n </div>\n </div>\n <div class="page-body list-body">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateEditUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
4567
+ angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <error-display></error-display>\n <div class="container-fluid page-header report-header">\n <div ng-class="css(\'rowFluid\')">\n <div class="header-lhs col-xs-7 span7">\n <h1>{{ titleWithSubstitutions }}</h1>\n </div>\n <div class="header-rhs col-xs-5 span5">\n <form-input schema="paramSchema" name="paramForm" ng-show="paramSchema" formstyle="horizontalCompact"></form-input>\n </div>\n </div>\n </div>\n <div class="container-fluid page-body report-body">\n <div class="row-fluid report-grow">\n <div class="gridStyle" style="height:100%;" ui-grid="gridOptions" ui-grid-selection ui-grid-resize-columns></div>\n </div>\n </div>\n</div>\n');
4568
+ $templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h1 id="header-text">{{modelNameDisplay}} :\n <span id="header-data-desc">\n <span ng-show="!!editFormHeader" >{{ editFormHeader() }}</span>\n <span ng-hide="!!editFormHeader" ng-repeat="field in listSchema" ng-bind-html="getListData(record, field.name) + \' \'"></span>\n </span>\n </h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <div form-buttons></div>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact"></form-input>\n </div>\n<!-- <pre>-->\n <!--Record-->\n <!--{{ record | json }}-->\n <!--formSchema-->\n <!--{{ formSchema | json }}-->\n<!-- </pre>-->\n</div>\n');
4569
+ $templateCache.put('base-list-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n </div>\n <div class="page-body list-body">\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateViewUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
4570
+ $templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <a ng-href="{{generateNewUrl()}}"><button id="newBtn" class="btn btn-default"><i class="icon-plus"></i> New</button></a>\n </div>\n </div>\n <div class="page-body list-body">\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateEditUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
4571
+ $templateCache.put('base-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h1 id="header-text">{{modelNameDisplay}} :\n <span ng-repeat="field in listSchema" ng-bind-html="getListData(record, field.name) + \' \'"></span>\n </h1>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact" viewform="true"></form-input>\n </div>\n</div>\n');
4572
+ $templateCache.put('error-display-bs2.html','<div id="display-error" ng-show="errorVisible" class="row-fluid ng-hide">\n <div class="alert alert-error offset1 span10">\n <button type="button" id="err-hide" class="close" ng-click="dismissError()"><i class="icon-remove"></i></button>\n <button type="button" id="err-pin" class="close" ng-click="stickError()"><i class="icon-eye-open"></i></button>\n <h4 id="err-title">{{alertTitle}}</h4>\n <div id="err-msg" ng-bind-html="errorMessage"></div>\n </div>\n</div>\n');
4573
+ $templateCache.put('error-display-bs3.html','<div id="display-error" ng-show="errorVisible" class="row ng-hide">\n <div class="alert alert-error col-md-offset-1 col-md-10 alert-danger">\n <button type="button" id="err-hide" class="close" ng-click="dismissError()"><i class="glyphicon glyphicon-remove"></i></button>\n <button type="button" id="err-pin" class="close" ng-click="stickError()"><i class="glyphicon glyphicon-pushpin"></i></button>\n <h4 id="err-title">{{alertTitle}}</h4>\n <div id="err-msg" ng-bind-html="errorMessage"></div>\n </div>\n</div>\n');
4008
4574
  $templateCache.put('error-messages.html','<div ng-message="required">A value is required for this field</div>\n<div ng-message="minlength">Too few characters entered</div>\n<div ng-message="maxlength">Too many characters entered</div>\n<div ng-message="min">That value is too small</div>\n<div ng-message="max">That value is too large</div>\n<div ng-message="email">You need to enter a valid email address</div>\n<div ng-message="pattern">This field does not match the expected pattern</div>\n');
4009
- $templateCache.put('form-button-bs2.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-mini btn-primary form-btn" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="icon-ok"></i> Save</button>\n <button id="cancelButton" class="btn btn-mini btn-warning form-btn" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="icon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-mini btn-success form-btn" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="icon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-mini btn-danger form-btn" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="icon-minus"></i> Delete</button>\n </div>\n</div>\n');
4010
- $templateCache.put('form-button-bs3.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-primary form-btn btn-xs" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="glyphicon glyphicon-ok"></i> Save</button>\n <button id="cancelButton" class="btn btn-warning form-btn btn-xs" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="glyphicon glyphicon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-success form-btn btn-xs" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="glyphicon glyphicon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-danger form-btn btn-xs" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="glyphicon glyphicon-minus"></i> Delete</button>\n </div>\n</div>\n');
4011
- $templateCache.put('search-bs2.html','<form class="navbar-search pull-right">\n <div id="search-cg" class="control-group" ng-class="errorClass">\n <input type="text" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <span ng-class="resultClass($index)" ng-click="selectResult($index)">{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
4012
- $templateCache.put('search-bs3.html','<form class="pull-right navbar-form">\n <div id="search-cg" class="form-group" ng-class="errorClass">\n <input type="text" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query form-control" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <span ng-class="resultClass($index)" ng-click="selectResult($index)" title={{result.additional}}>{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);
4575
+ $templateCache.put('form-button-bs2.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-mini btn-primary form-btn" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="icon-ok"></i> Save</button>\n <div id="why-disabled" ng-class="{showwhy:!!whyDisabled}" ng-bind-html="whyDisabled"></div>\n <button id="cancelButton" class="btn btn-mini btn-warning form-btn" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="icon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-mini btn-success form-btn" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="icon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-mini btn-danger form-btn" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="icon-minus"></i> Delete</button>\n </div>\n</div>\n');
4576
+ $templateCache.put('form-button-bs3.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-primary form-btn btn-xs" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="glyphicon glyphicon-ok"></i> Save</button>\n <div id="why-disabled" ng-class="{showwhy:!!whyDisabled}" ng-bind-html="whyDisabled"></div>\n <button id="cancelButton" class="btn btn-warning form-btn btn-xs" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="glyphicon glyphicon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-success form-btn btn-xs" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="glyphicon glyphicon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-danger form-btn btn-xs" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="glyphicon glyphicon-minus"></i> Delete</button>\n </div>\n</div>\n');
4577
+ $templateCache.put('search-bs2.html','<form class="navbar-search pull-right">\n <div id="search-cg" class="control-group" ng-class="errorClass">\n <input type="text" spellcheck="false" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <span ng-class="resultClass($index)" ng-click="selectResult($index)">{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
4578
+ $templateCache.put('search-bs3.html','<form class="pull-right navbar-form">\n <div id="search-cg" class="form-group" ng-class="errorClass">\n <input type="text" spellcheck="false" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query form-control" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <span ng-class="resultClass($index)" ng-click="selectResult($index)" title={{result.additional}}>{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);