Odyssey
mFeatureLst.prg
1 <?php
2  /**
3  * @package mFeatureLst.prg
4  *
5  * Purpose: Handle display and user interface for the Features feature in Monitor. Send data via AJAX as JSON
6  * encoded data.
7  *
8  * Display : Show list of enabled/disabled features defined by HomeCU.
9  * Actions : User interface actions available for creating/editting/deleting features
10  * - Add Feature : create single feature with unique feature_code
11  * - Edit Feature : edit single feature description and limit_type
12  * - Enable Features : enable multiple features at one time
13  * - Disable Features : disable multiple features at one time
14  * - Delete Feature : delete feature from existance
15  */
16 
17  $monLibrary= dirname(__FILE__) . "/../library";
18  require_once("$monLibrary/cu_top.i");
19  require_once("$monLibrary/ck_hticket.i");
20  require_once("$monLibrary/cu_pass.i");
21  require_once("$monLibrary/monitorView.i");
22 
23  if (!CheckPerm($link, $Hu, basename($_SERVER['SCRIPT_NAME']), $_SERVER['REMOTE_ADDR'])) {
24 
25  header("Location: /hcuadm/hcu_noperm.prg");
26  exit;
27  }
28 
29  printMonitorPageTop("CU Features", $homecuKendoVersion, $cloudfrontDomainName, $bootstrapVersion, $fontawesomeVersion, true);
30  printMonitorPageMiddle("CU Features", null);
31 ?>
32 
33 <!-- JAVASCRIPT CONTENT -->
34 <script type="text/javascript">
35 
36 var flGridSource = null;
37 var flGridView = null;
38 var flGridData = [];
39 
40 var flDropdownSource = null;
41 var flDropdownAction = null;
42 var flDropdownActionStack = [];
43 
44 var flWindowObserve = null;
45 var flWindowEditView = null;
46 
47 var flDialogChanges = null;
48 var flDialogDelete = null;
49 
50 var flButtonAdd = null;
51 var flValidator = null;
52 var flInfoBar = null;
53 
54 function ClearFeatureInformation() {
55 
56  flInfoBar.text("");
57  flInfoBar.css("display", "none");
58 }
59 
60 function ShowFeatureInfo(info, sql, status) {
61 
62  var text = "";
63 
64  textInfo = info || [];
65  textSql = sql || [];
66 
67  for (var i = 0; i < textInfo.length; i++) {
68  text += textInfo[i];
69  }
70 
71  for (var i = 0; i < textSql.length; i++) {
72  text += textSql[i];
73  }
74 
75  if (text == "") { return; }
76 
77  if (status) {
78  flInfoBar.addClass("k-success-colored");
79  } else {
80  flInfoBar.addClass("k-error-colored");
81  }
82 
83  flInfoBar.text(text);
84  flInfoBar.css("display", "block");
85 }
86 
87 function ValidateFeatureRequired(element) {
88 
89  var pass = true;
90  var reqrInput = $(element);
91  var reqrValue = reqrInput.val();
92 
93  if (reqrValue.length == 0) { pass = false; }
94 
95  return pass;
96 }
97 
98 function ValidateFeatureDesc(element) {
99 
100  var pass = true;
101  var descInput = $(element);
102  var descValue = descInput.val();
103  descValue = descValue.trim();
104 
105  if (descValue.length == 0) { pass = false; }
106 
107  return pass;
108 }
109 
110 function ValidateFeatureType(element) {
111 
112  var pass = true;
113  var typeInput = $(element);
114  var typeValue = typeInput.val();
115 
116  if (typeValue == "") { pass = false; }
117 
118  return pass;
119 }
120 
121 function ValidateFeatureCode(element) {
122 
123  var pass = true;
124  var codeInput = $(element);
125  var codeValue = codeInput.val();
126  var sourceIndex = flWindowObserve.sourceIndex;
127  var sourceFeature = flWindowObserve.sourceFeature;
128 
129  for (var i = 0; i < flGridData.length; i++) {
130 
131  var feature = flGridData[i];
132  var featureCode = feature.featureCode;
133 
134  if (i == sourceIndex) { continue; }
135  if (featureCode == codeValue) { pass = false; break; }
136  }
137 
138  return pass;
139 }
140 
141 function FeatureSort(data) {
142 
143  if (data.length < 2) {
144  return data;
145  }
146 
147  var middle = Math.floor(data.length / 2);
148  var right = data.slice(middle, data.length);
149  var left = data.slice(0, middle);
150 
151  var leftSort = FeatureSort(left);
152  var rightSort = FeatureSort(right);
153 
154  return FeaturSortMerge(leftSort, rightSort);
155 }
156 
157 function FeaturSortMerge(left, right) {
158 
159  var merge = [];
160 
161  while (left.length && right.length) {
162  if (left[0].featureCode < right[0].featureCode) {
163  merge.push(left.shift());
164  } else {
165  merge.push(right.shift());
166  }
167  }
168 
169  while (left.length) {
170  merge.push(left.shift());
171  }
172 
173  while (right.length) {
174  merge.push(right.shift());
175  }
176 
177  return merge;
178 }
179 
180 function DataSourceFindFeature(id) {
181 
182  var dataIndex = -1;
183  var dataId = id.replace(/["']/g, "");
184 
185  for (var i = 0; i < flGridData.length; i++) {
186 
187  if (flGridData[i].featureCode == dataId) {
188 
189  dataIndex = i;
190  break;
191  }
192  }
193 
194  return dataIndex;
195 }
196 
197 function StackRemoveFeature(id) {
198 
199  var featureCode = id.substring(4, id.length);
200 
201  for (var i = 0; i < flDropdownActionStack.length; i++) {
202 
203  var feature = flDropdownActionStack[i];
204  if (feature.featureCode == featureCode) {
205 
206  flDropdownActionStack.splice(i, 1);
207  break;
208  }
209  }
210 }
211 
212 function StackInsertFeature(id) {
213 
214  var featureCode = id.substring(4, id.length);
215  var featureIndex = DataSourceFindFeature(featureCode);
216  var feature = flGridData[featureIndex];
217 
218  flDropdownActionStack.push(feature);
219 }
220 
221 function ResetObserve() {
222  flWindowObserve.sourceFeature = null;
223  flWindowObserve.sourceDirty = false;
224  flWindowObserve.sourceIndex = null;
225  flWindowObserve.sourceField = false;
226  flWindowObserve.sourceLabel = false;
227 }
228 
229 function EventEnableCheckboxes(enable) {
230 
231  for (var i = 0; i < flDropdownActionStack.length; i++) {
232 
233  var feature = flDropdownActionStack[i];
234  var featureCode = feature.featureCode;
235 
236  var boxId = "chk_" + featureCode;
237  var box = $("input[id=" + boxId + "]");
238 
239  box.prop("checked", enable);
240  }
241 
242  if (!enable) {
243  flDropdownActionStack = [];
244  }
245 }
246 
247 function EventDeleteConfirm(e) {
248  flGridSource.read();
249 }
250 
251 function EventDeleteDeny(e) {
252  flDropdownAction.select(0);
253 }
254 
255 function EventChangeConfirm(e) {
256  flWindowEditView.close();
257  flGridView.dataSource.cancelChanges();
258 }
259 
260 function EventFeatureSave(e) {
261 
262  if (flValidator.validate()) {
263 
264  var feature = flWindowObserve.sourceFeature;
265  var featureCode = feature.featureCode;
266  var featureDescription = feature.featureDescription;
267  var featureLimit = feature.featureLimit;
268  var featureIndex = flWindowObserve.sourceIndex;
269 
270  var requestFeature = {
271  featureCode: feature.featureCode,
272  featureDescription: feature.featureDescription,
273  featureLimit: feature.featureLimit
274  };
275 
276  if (featureIndex == -1) {
277  requestFeature.action = "feature_create";
278  } else {
279  requestFeature.action = "feature_edit";
280  }
281 
282  flGridSource.transport.options.read.type = "POST";
283  flGridSource.read(requestFeature);
284 
285  flWindowEditView.close();
286  }
287 }
288 
289 function EventFeatureAdd(e) {
290 
291  var dataItem = flGridView.dataSource.add();
292  EventOpenEditWindow(dataItem, "Add");
293 }
294 
295 function EventOpenEditWindow(data, action) {
296 
297  var featureIndex = DataSourceFindFeature(data.featureCode);
298 
299  flWindowObserve.set("sourceFeature", data);
300  flWindowObserve.set("sourceDirty", false);
301  flWindowObserve.set("sourceIndex", featureIndex);
302 
303  if (featureIndex == -1) {
304  flWindowObserve.sourceField = true;
305  flWindowObserve.sourceLabel = false;
306  } else {
307  flWindowObserve.sourceField = false;
308  flWindowObserve.sourceLabel = true;
309  }
310 
311  kendo.bind("#wndFeatureMnt", flWindowObserve);
312  kendo.bind("#wndFeatureConfirm", flWindowObserve);
313 
314  flWindowEditView.title(action + " Feature");
315  flWindowEditView.center();
316  flWindowEditView.open();
317 }
318 
319 function EventUpdateFeatureActions() {
320 
321  var showEnable = false;
322  var showDisable = false;
323 
324  flDropdownSource.data([]);
325  if (flDropdownActionStack.length == 0) {
326 
327  flDropdownAction.enable(false);
328  return;
329  }
330 
331  flDropdownAction.enable(true);
332 
333  for (var i = 0; i < flDropdownActionStack.length; i++) {
334 
335  var feature = flDropdownActionStack[i];
336  var featureEnabled = feature.featureEnabled;
337 
338  if (featureEnabled) { showDisable = true; }
339  else { showEnable = true; }
340  }
341 
342  var featureActions = [];
343  featureActions.push({ text: "Select Action", value: "" });
344  if (showEnable && showDisable) { showEnable = false; showDisable = false; }
345  if (showEnable) { featureActions.push({ text: "Enable", value: "feature_enable" }); }
346  if (showDisable) { featureActions.push({ text: "Disable", value: "feature_disable" }); }
347  featureActions.push({ text: "Delete", value: "feature_delete" });
348 
349  flDropdownSource.data(featureActions);
350  flDropdownAction.select(0);
351 }
352 
353 function EventFeatureActionChange(e) {
354 
355  var featureAction = e.sender.value();
356  var featureCodes = "";
357 
358  for (var i = 0; i < flDropdownActionStack.length; i++) {
359 
360  featureCodes += flDropdownActionStack[i].featureCode;
361  if (i < flDropdownActionStack.length - 1) {
362  featureCodes += ",";
363  }
364  }
365 
366  var requestUpdate = {
367  action: featureAction,
368  featureList: featureCodes
369  };
370 
371  flGridSource.transport.options.read.type = "POST";
372  flGridSource.transport.options.read.data = requestUpdate;
373  ResetObserve();
374 
375  if (featureAction == "feature_delete") {
376  var fl_dialog_string = "<p>Your are about to delete the following feature(s):</p><p>(" + featureCodes + ")</p><p>Do you wish to continue?</p>";
377  flDialogDelete.content(fl_dialog_string);
378  flDialogDelete.open();
379  } else {
380  flGridSource.read();
381  }
382 
383 }
384 
385 function EventFeatureActionClose(e) {
386 
387  var elem = e.sender.element[0].parentElement.firstChild;
388  $(elem).removeClass("k-state-focused");
389 }
390 
391 function EventCheckboxChange(e, source) {
392 
393  var box = $(e);
394  var boxId = box.attr("id");
395  var boxValue = box.prop("checked");
396  var boxIndex = -1;
397 
398  if (source == "BOX") {
399  boxValue = !boxValue;
400  }
401 
402  if (boxValue) {
403 
404  StackRemoveFeature(boxId);
405  } else {
406 
407  StackInsertFeature(boxId);
408  }
409 
410  box.prop("checked", !boxValue);
411  EventUpdateFeatureActions();
412 }
413 
414 function EventGridDataBind(e) {
415 
416  $("html .k-grid td").css("border-right", "0");
417  $("html .k-grid td").css("border-left", "0");
418 
419  $("html .k-grid th").css("border-right", "0");
420  $("html .k-grid th").css("border-left", "0");
421 
422  $("html .k-grid tr").hover(function() {
423  $(this).css("cursor", "pointer");
424  });
425 
426  $('input[id^=chk_]').change(function(e) {
427  EventCheckboxChange(e.target, "BOX");
428  });
429 
430  EventEnableCheckboxes(true);
431 }
432 
433 function EventGridChange(e) {
434 
435  var cell = this.select();
436  var cellIndex = cell.index();
437 
438  if (cellIndex == 1) {
439 
440  var box = cell.find("input[id^=\"chk_\"]")[0];
441  EventCheckboxChange(box, "ROW");
442  } else {
443 
444  var dataSource = this.dataSource;
445  var dataIndex = cell.closest("tr").index(".k-master-row");
446  var dataItem = dataSource.view()[dataIndex];
447 
448  EventOpenEditWindow(dataItem, "Edit");
449  }
450 
451  $(this.select()).removeClass('k-state-selected');
452 }
453 
454 function InitDataActions() {
455  flDropdownAction = $("#drpActions").kendoDropDownList({
456  dataSource: flDropdownSource,
457  dataTextField: "text",
458  dataValueField: "value",
459  change: EventFeatureActionChange,
460  close: EventFeatureActionClose,
461  enabled: false
462  }).data("kendoDropDownList");
463 
464  flButtonAdd = $("#btnAdd").kendoButton({
465  click: EventFeatureAdd
466  });
467 }
468 
469 function InitDataViews() {
470  flGridView = $("#grdFeatureLst").kendoGrid({
471  dataSource: {
472  data: flGridData,
473  schema: {
474  model: {
475  id: "featureCode",
476  fields: {
477  featureCode: { type: "string" },
478  featureDescription: { type: "string" },
479  featureLimit: { type: "string" },
480  featureEnabled: { type: "boolean" }
481  }
482  }
483  }
484  },
485  rowTemplate: kendo.template($("#templateGridRow").html()),
486  dataBound: EventGridDataBind,
487  change: EventGridChange,
488  selectable: "cell",
489  height: 315,
490  columns: [
491  { title: "", width: "3px" },
492  { title: "", width: "25px" },
493  { title: "Code", width: "100px" },
494  { title: "Description" },
495  { title: "Limit Type", width: "75px" }
496  ]
497  }).data("kendoGrid");
498 
499  flWindowObserve = new kendo.observable({
500  sourceFeature: null,
501  sourceDirty: false,
502  sourceIndex: null,
503  sourceField: false,
504  sourceLabel: false,
505  sourceLimits: [
506  { text: "Choose Limit Type", value: "" },
507  { text: "Access Allowed", value: "A" },
508  { text: "Quantity", value: "Q" },
509  { text: "Dollar", value: "D" },
510  { text: "Quantity & Dollar", value: "B" }
511  ],
512  clickSave: EventFeatureSave,
513  changeCode: function(e) { this.sourceDirty = true;
514  var element = $(e.target);
515  var value = element.val();
516  value = value.toUpperCase();
517 
518  if (RegExp(/[@#\\!$%^&*()+|~=`{}\[\]:";'<>?,.\/]/).test(value)) {
519  value = value.slice(0, -1);
520  }
521 
522  element.val(value);
523  this.sourceFeature.featureCode = value;
524  },
525  changeDesc: function(e) { this.sourceDirty = true; },
526  changeLimit: function(e) { this.sourceDirty = true;
527 
528  var element = e.sender.element[0];
529  $(element).blur();
530  }
531  });
532 
533  flWindowEditView = $("#wndFeatureMnt").kendoWindow({
534  width: "300px",
535  modal: true,
536  visible: false,
537  resizable: false,
538  actions: ['Close'],
539  activate: function(e) {
540  $("#wndFeatureMnt span.k-tooltip-validation").hide();
541  $("#wndFeatureMnt #inpCode").focus();
542  },
543  close: function(e) {
544 
545  if (e.userTriggered) {
546 
547  if (flWindowObserve.sourceDirty) {
548  e.preventDefault();
549  flDialogChanges.open();
550  } else {
551  flGridView.dataSource.cancelChanges();
552  flWindowEditView.close();
553  ResetObserve();
554  }
555  }
556  }
557  }).data("kendoWindow");
558 
559  flDialogDelete = $("#dialogDelete").kendoDialog({
560  title: "Delete Feature(s)",
561  visible: false,
562  actions: [
563  {
564  text: "No",
565  action: EventDeleteDeny
566  },
567  {
568  text: "Yes", primary: true,
569  action: EventDeleteConfirm
570  }
571  ]
572  }).data("kendoDialog");
573 
574  flDialogChanges = $("#dialogChanges").kendoDialog({
575  title: "Discard Changes",
576  content: "<p>Changes have been made to this feature.</p><p>Do you wish to discard your changes?</p>",
577  visible: false,
578  actions: [
579  {
580  text: "No"
581  },
582  {
583  text: "Yes", primary: true,
584  action: EventChangeConfirm
585  }
586  ]
587  }).data("kendoDialog");
588 
589  flValidator = $("#wndFeatureMnt").kendoValidator({
590  rules: {
591  code: ValidateFeatureCode,
592  desc: ValidateFeatureDesc,
593  type: ValidateFeatureType,
594  required: ValidateFeatureRequired
595  }
596  }).data("kendoValidator");
597 
598  flInfoBar = $("#featureInfoBar");
599 }
600 
601 function InitDataSources() {
602  flDropdownSource = new kendo.data.DataSource({
603  data: []
604  });
605 
606  flGridSource = new kendo.data.DataSource({
607  transport: {
608  read: {
609  url: "mFeature.data",
610  dataType: "json",
611  contentType: "application/x-www-form-urlencoded",
612  type: "GET",
613  data: {
614  action: "feature_read"
615  },
616  cache: false
617  }
618  },
619  requestStart: function(response) {
620  ShowWaitWindow();
621  ClearFeatureInformation();
622  },
623  requestEnd: function(response) {
624  try {
625 
626  if (response.hasOwnProperty("response")) {
627 
628  if (response.response.hasOwnProperty("Results")) {
629 
630  var results = response.response.Results;
631  if (results.hasOwnProperty("errors")) {
632 
633  throw results.errors;
634  } else {
635 
636  ShowFeatureInfo(results.info, results.sql, true);
637  }
638  } else {
639 
640  throw "Error Parsing Server";
641  }
642  } else {
643 
644  throw "Error Parsing Server";
645  }
646  } catch (error) {
647 
648  ShowFeatureInfo(error, null, false);
649  }
650 
651  EventEnableCheckboxes(false);
652  EventUpdateFeatureActions();
653  CloseWaitWindow();
654  },
655  schema: {
656  parse: function(response) {
657  var results = response.Results;
658  var resultData = results.data;
659  var resultAction = results.action;
660 
661  if (resultData == undefined || resultData == null) {
662  return [];
663  }
664 
665  if (results.hasOwnProperty("errors")) {
666  return [];
667  }
668 
669  if (resultAction == "feature_read") {
670 
671  flGridData = resultData;
672  } else {
673 
674  for (var i = 0; i < resultData.length; i++) {
675 
676  var feature = resultData[i];
677  var featureCode = feature.featureCode;
678  var featureIndex = DataSourceFindFeature(featureCode);
679 
680  if (resultAction == "feature_create") {
681  flGridData.splice(0, 0, feature);
682  } else if (resultAction == "feature_delete") {
683  flGridData.splice(featureIndex, 1);
684  } else {
685  flGridData.splice(featureIndex, 1, feature);
686  }
687 
688  }
689  }
690 
691  // SET DATA TO GRID IF NO ERRORS
692  flGridData = FeatureSort(flGridData);
693  flGridView.dataSource.data(flGridData);
694 
695  return [];
696  }
697  }
698  });
699 }
700 
701 // START JAVASCRIPT
702 $(document).ready(function() {
703  InitDataSources();
704  InitDataViews();
705  InitDataActions();
706 
707  flGridSource.read();
708 });
709 
710 $(document).on('click', '.k-overlay', function() {
711  if (flWindowObserve.sourceDirty) {
712  flDialogChanges.open();
713  } else {
714  flGridView.dataSource.cancelChanges();
715  flWindowEditView.close();
716  }
717 
718  flDialogDelete.close();
719  flDropdownAction.select(0);
720 });
721 </script>
722 
723 <!-- HTML STYLING -->
724 <style type="text/css">
725 
726 /* TOP/BOTTOM MARGIN */
727 .hcu-container-margin {
728  margin: 15px 0;
729 }
730 
731 .hcu-no-padding {
732  padding: 0;
733 }
734 
735 .hcu-full-width {
736  width: 100%;
737 }
738 
739 .hcu-hidden {
740  display: none;
741 }
742 
743 .hcu-disabled {
744  background: rgb(255 , 215, 0);
745 }
746 
747 .hcu-max-width {
748  max-width: 700px;
749  margin: auto;
750 }
751 
752 .k-menu .k-item > .k-link {
753  padding: .5em 1.1em .4em;
754 }
755 
756 .k-grid > td {
757  white-space: nowrap;
758 }
759 
760 .k-grid > table > tbody > tr:hover,
761 .k-grid-content > table > tbody > tr:hover {
762  background-color: #ddd;
763 }
764 
765 </style>
766 
767 <!-- HTML CONTENT -->
768 <div class="container-fluid hcu-max-width">
769 
770  <!-- INFORMATION / ERROR -->
771  <div id="featureInfoBar" class="well well-sm col-sm-12" style="display: none; white-space: pre;">
772 
773  </div>
774 
775  <div>&nbsp;</div>
776 
777  <!-- CONTENT -->
778  <div class="well well-sm col-sm-12">
779 
780  <!-- HEADER -->
781  <div class="col-sm-12 hcu-no-padding">
782  <h3>Feature List</h3>
783  </div>
784 
785  <!-- ACTIONS -->
786  <div class="col-sm-12 hcu-no-padding hcu-container-margin">
787  <!-- BTN ADD -->
788  <div class="col-sm-3">
789  <button id="btnAdd" class="hcu-full-width">
790  <span class="fa fa-plus"></span>
791  <span>Add Feature</span>
792  </button>
793  </div>
794 
795  <!-- BTN ADD -->
796  <div class="col-sm-3">
797  <input id="drpActions" class="hcu-full-width">
798  </div>
799  </div>
800 
801  <!-- LIST CONTENT -->
802  <div class="col-sm-12 hcu-container-margin">
803 
804  <!-- GRID -->
805  <div id="grdFeatureLst"></div>
806  </div>
807 
808  </div>
809 </div>
810 
811 <div id="homecuSubmitWait"></div>
812 
813 <!-- TEMPLATE: GRID ROW -->
814 <script type="text/x-kendo-template" id="templateGridRow">
815  <tr class="k-master-row" data-uid='#= uid #'>
816  # if (featureEnabled) { #
817  <td></td>
818  # } else { #
819  <td class="hcu-disabled"></td>
820  # } #
821 
822  <td>
823  <input type="checkbox" id="chk_#= featureCode #"
824  style="margin-top: -2px; cursor: pointer;">
825  </td>
826  <td>#= featureCode #</td>
827  <td>#= featureDescription #</td>
828  # if (featureLimit == 'B') { #
829  <td>Q D</td>
830  # } else { #
831  <td>#= featureLimit #</td>
832  # } #
833  </tr>
834 </script>
835 
836 <div id="wndFeatureMnt" class="hcu-hidden">
837  <!-- CONTENT -->
838  <div class="well well-sm ">
839 
840  <div class="col-sm-12 hcu-no-padding hcu-container-margin">
841  <!-- FEATURE CODE -->
842  <div class="col-sm-12 hcu-no-padding">
843  <label>Code:</label>
844  <span class="k-invalid-msg" data-for="inpCode"></span>
845  &nbsp;
846  <span data-bind="visible: sourceLabel, text: sourceFeature.featureCode"></span>
847  </div>
848 
849  <div class="col-sm-12 hcu-no-padding">
850  <input id="inpCode" class="hcu-full-width k-textbox" name="inpCode" maxlength="10"
851  data-bind="value: sourceFeature.featureCode,
852  visible: sourceField,
853  events: { input: changeCode}"
854  data-code="#inpCode"
855  data-code-msg="Feature Code Exists"
856  data-required-msg="Feature Code is Empty"
857  required
858  style="padding-left: 0px;">
859  </div>
860  </div>
861 
862  <div class="col-sm-12 hcu-no-padding hcu-container-margin">
863  <!-- FEATURE DESCRIPTION -->
864  <div class="col-sm-12 hcu-no-padding">
865  <label>Description:</label>
866  <span class="k-invalid-msg" data-for="inpDescription"></span>
867  </div>
868 
869  <div class="col-sm-12 hcu-no-padding">
870  <textarea id="inpDescription" class="hcu-full-width k-textbox" name="inpDescription"
871  rows="5" maxlength="100"
872  data-bind="value: sourceFeature.featureDescription,
873  events: { input: changeDesc }"
874  data-desc="#inpDescription"
875  data-desc-msg="Feature Description is Empty"
876  data-required-msg="Feature Description is Empty"
877  required
878  style="padding-left: 10px;">
879  </textarea>
880  </div>
881  </div>
882 
883  <div class="col-sm-12 hcu-no-padding hcu-container-margin">
884  <!-- FEATURE LIMIT TYPE -->
885  <div class="col-sm-12 hcu-no-padding">
886  <label>Limit Type:</label>
887  <span class="k-invalid-msg" data-for="inpLimit"></span>
888  </div>
889 
890  <div class="col-sm-12 hcu-no-padding">
891  <input id="inpLimit" class="hcu-full-width" name="inpLimit"
892  data-role="dropdownlist"
893  data-text-field="text"
894  data-value-field="value"
895  data-bind="source: sourceLimits,
896  value: sourceFeature.featureLimit,
897  events: { change: changeLimit }"
898  data-type="#inpLimit"
899  data-type-msg="Choose Feature Limit Type"
900  data-required-msg="Choose Feature Limit Type"
901  required >
902  </div>
903  </div>
904 
905  <!-- CLEAR FOR POP UP WINDOW -->
906  <div style="clear: both;"></div>
907  </div>
908 
909  <!-- BTN SAVE -->
910  <div class="col-sm-3 hcu-no-padding"></div>
911  <div class="col-sm-6 hcu-no-padding">
912  <button id="btnSave" class="hcu-full-width k-button k-primary"
913  data-bind="events: { click: clickSave }">
914  <span class="fa fa-check"></span>
915  <span>Save</span>
916  </button>
917  </div>
918  <div class="col-sm-3 hcu-no-padding"></div>
919 </div>
920 
921 <div id="wndFeatureConfirm" class="hcu-hidden">
922  <!-- CONTENT -->
923  <div class="well well-sm ">
924 
925  <div class="col-sm-12 hcu-no-padding">
926  <span>This feature has been changed, do you wish to discard your changes?</span>
927  </div>
928 
929  <!-- CLEAR FOR POP UP WINDOW -->
930  <div style="clear: both;"></div>
931  </div>
932 
933  <!-- BTN SAVE -->
934  <div class="col-sm-5 hcu-no-padding">
935  <button id="btnConfirm" class="hcu-full-width k-button k-primary"
936  data-bind="events: { click: clickConfirm }">
937  <span>Discard</span>
938  </button>
939  </div>
940 
941  <div class="col-sm-2 hcu-no-padding"></div>
942 
943  <!-- BTN SAVE -->
944  <div class="col-sm-5 hcu-no-padding">
945  <button id="btnConfirm" class="hcu-full-width k-button"
946  data-bind="events: { click: clickDeny }">
947  <span>No</span>
948  </button>
949  </div>
950 </div>
951 
952 <div id="dialogChanges"></div>
953 <div id="dialogDelete"></div>
954 
955 <?php
956  printMonitorPageBottom();
957  exit;
958 ?>