Odyssey
commonJsFunctions.i
1 <?php
2 /* File: commonJsFunctions.php
3  * Purpose: Contains common Javascript functions but I wanted to not have to call to an external JS file every time that I needed one of these functions.
4  * This also allows some PHP variables directly into these functions if necessary.
5  * SPB-- Created to reduce copying of code.
6  */
7 
8 function getCancelFunction() {
9 /* This will cancel the popup editor. It clears out the invalid request variables and then cancels the row. There was something funky going on so that is why the cancelRow has to be called manually. */
10 ?>
11 function cancel(e)
12 {
13  $("#invalidRequestClientSide").val(false);
14  $("#invalidRequestServerSide").val(false);
15  e.sender.cancelRow();
16  e.sender.refresh();
17  e.preventDefault();
18  return false;
19 }
20 <?php }
21 
22 function getParseCrudGridFunction() {
23 /* This function is used in most grids in the dataSource.schema.parse function slot. I organized all my CRUD calls to return a sql array (if not production), an error array,
24  * the actual data, and an operation parameter that is: read, update, add, or delete. This function parses that and shows errors and SQL.
25  * (Otherwise, Kendo will treat the whole data as data for the grid.) It also sets the invalid request parameter.
26  * This parameter is useful later in preventing the window from closing when there actually is an error.
27  * Parameters: Object data-- data from CRUD AJAX call.
28 */
29 ?>
30 function parseCrudGrid(data, isDetail)
31 {
32  var statusField= typeof(isDetail) != "undefined" && isDetail ? "formValidatePopupDetailDiv" : "formValidatePopupDiv";
33  var errorLocation= data.operation == "update" || data.operation == "add" ? statusField : "formValidateMainDiv";
34  $("#invalidRequestServerSide").val(errorsAreShown(data, errorLocation));
35 
36  return data.record;
37 }
38 <?php }
39 
40 function getSetupValidatorInGridFunction() {
41 /* For most all editable grids, this is called the first line in the edit function. It has multiple purposes. 1) Adds the custom HomeCU validator for the popup editor.
42  * 2) Since, anytime "required" and several other attributes are added to the HTML, the default validator fires so there is logic to get around that.
43  * 3) The window of the popup is altered to only close when the data is valid (and user doesn't click on the cancel button). Data came become invalid through the client side or the server side.
44  * Parameters: Object e-- generic event parameter
45 */
46 ?>
47 function setupValidatorInGrid(e, isDetail, validatorDefinition, stillHomecuValidator)
48 {
49  if ($("#invalidRequestClientSide").length == 0)
50  $("body").append("<input type='hidden' id='invalidRequestClientSide'>");
51  if ($("#invalidRequestServerSide").length == 0)
52  $("body").append("<input type='hidden' id='invalidRequestServerSide'>");
53  var form= "popupForm";
54  var statusField= "formValidatePopupDiv";
55  if (typeof(isDetail) != "undefined" && isDetail)
56  {
57  form= "popupFormDetail";
58  statusField= "formValidatePopupDetailDiv";
59  }
60 
61  if (typeof(validatorDefinition) == "undefined")
62  $.homecuValidator.setup({formValidate: form, formErrorTitle: 'The following errors were detected:',
63  formStatusField: statusField});
64  else if (stillHomecuValidator != null && stillHomecuValidator)
65  $.homecuValidator.setup(validatorDefinition);
66  else
67  $("#"+form).kendoValidator(validatorDefinition);
68 
69  // Always return valid (To get around Kendo autovalidate).
70  e.sender.editable.validatable.validate = function () { return true; };
71  e.sender.editable.validatable.validateInput = function(input) { return true; };
72 
73  // To always force saves
74  e.model.dirty= true;
75 
76  $("#invalidRequestClientSide").val(false);
77  $("#invalidRequestServerSide").val(false);
78 
79  var cancelClicked= false;
80 
81  $(".k-popup-edit-form .k-grid-cancel").click(function () {
82  cancelClicked= true;
83  });
84 
85  $(".k-popup-edit-form .k-grid-update").click(function () {
86  cancelClicked= false;
87  if (!$(this).hasClass("k-state-disabled"))
88  {
89  $(this).addClass("k-state-disabled");
90  }
91  else
92  {
93  e.preventDefault();
94  return false;
95  }
96  });
97 
98  var window = e.sender.editable.element.data("kendoWindow");
99  window.bind("close", function(eWindow){
100  var invalidRequestClientSide= $("#invalidRequestClientSide").val() == "true";
101  var invalidRequestServerSide= $("#invalidRequestServerSide").val() == "true";
102  if (!cancelClicked && (invalidRequestClientSide || invalidRequestServerSide))
103  {
104  eWindow.preventDefault();
105  $(".k-popup-edit-form .k-grid-update").removeClass("k-state-disabled");
106  return false;
107  }
108  });
109 }
110 <?php }
111 
112 function getUseValidatorInGridFunction() {
113 /* For most all editable grids, this is called in the save function. It calls the validate function on the validator and if that returns any error messages, then it prevents the window from closing.
114  * Parameters: Object e-- generic event parameter
115 */
116 ?>
117 function useValidatorInGrid(e, isDetail, useCommonValidator)
118 {
119  if ($("#invalidRequestClientSide").length == 0)
120  $("body").append("<input type='hidden' id='invalidRequestClientSide'>");
121  if ($("#invalidRequestServerSide").length == 0)
122  $("body").append("<input type='hidden' id='invalidRequestServerSide'>");
123  var statusField= typeof(isDetail) != "undefined" && isDetail ? "formValidatePopupDetailDiv" : "formValidatePopupDiv";
124 
125  if (typeof(useCommonValidator) == "undefined" || !useCommonValidator)
126  $.homecuValidator.validate();
127  else
128  $("#"+statusField).data("kendoValidator").validate();
129  if ($("#" + statusField).is(":visible"))
130  {
131  $("#invalidRequestClientSide").val(true);
132  e.preventDefault();
133  $(".k-popup-edit-form .k-grid-update").removeClass("k-state-disabled");
134  hideWaitWindow();
135  return false;
136  }
137  else $("#invalidRequestClientSide").val(false);
138 }
139 <?php }
140 
141 function getEscapeHTMLFunction() {
142 /* Simple function to escape HTML code e.g. "<b>Hi</b>" will show up literally, rather than a bolded "Hi."
143  * Parameters: String s-- the string to escape. */
144 ?>
145 function escapeHTML(s)
146 {
147  if (s == null || (s+"").trim() == "")
148  return "";
149  return s.replace(/&/g, '&amp;')
150  .replace(/"/g, '&quot;')
151  .replace(/</g, '&lt;')
152  .replace(/>/g, '&gt;');
153 }
154 <?php }
155 
156 function getUnescapeHTMLFunction() {
157 /* Reverse of previous function.
158  * Parameters: String s-- the string to unescape. */
159  ?>
160 function unescapeHTML(s)
161 {
162  if (s == null || (s+"").trim() == "")
163  return "";
164  return s.replace(/&amp;/g, '&')
165  .replace(/&quot;/g, '"')
166  .replace(/&lt;/g, '<')
167  .replace(/&gt;/g, '>');
168 }
169 <?php }
170 
171 function getShowSQLFunction($showSQL) {
172 /* Function is called in the parseCrudGrid function which is used in nearly every grid. For any other grid, this function is called directly.
173  * In the inc.php, there is a boolean for showing SQL. If it is set, then #showSQL will be 1 and any AJAX calls will also return an array of SQL used.
174  * This function will separate the SQL by a line in the DIV provided for showing SQL.
175  * Parameters: Object data-- full data from any AJAX call, Boolean shouldAppend-- if set to true, SQL will be added on the end of the DIV, rather than replace a previous AJAX call's SQL. */
176  ?>
177 function showSQL(data, shouldAppend)
178 {
179  <?php if ($showSQL)
180  { ?>
181  if (typeof shouldAppend == "undefined" || !shouldAppend)
182  $("#sqlOutput").empty();
183  else
184  $("#sqlOutput").append("<br /><br />");
185  var first= true;
186 
187  if (typeof(data.sql) == "undefined")
188  $("#sqlOutput").append("(No SQL available)");
189  else
190  for (var i=0; i!= data.sql.length; i++)
191  {
192  if (!first)
193  $("#sqlOutput").append("<br /><br />");
194  $("#sqlOutput").append(escapeHTML(data.sql[i]));
195  first= false;
196  }
197  <?php } ?>
198 }
199 <?php }
200 
201 function getErrorsAreShownFunction() {
202 /* Function is called in the parseCrudGrid function which is used in nearly every grid. For any other grid, this function is called directly.
203  * It mimics the same structure as the validator so the invalid client side messages and invalid server side messages look the same. Each error message is appended as a list item.
204  * Function returns a boolean. True: there are errors; False: no errors.
205  * Parameters: Object data-- full data from any AJAX call, String outputDiv-- specifies the error DIV (there is one for the edit popup and one for the main page),
206  * Boolean shouldAppend-- if set to true, the errors are appended to the end of the error DIV, rather than overwriting the whole error DIV.
207  */
208  ?>
209 function errorsAreShown(data, outputDiv, shouldAppend, notId)
210 {
211  var selector= notId == null ? "#"+outputDiv : outputDiv;
212  if (data.error.length > 0)
213  {
214  if (typeof shouldAppend == "undefined" || !shouldAppend)
215  $(selector).empty();
216  $(selector).append("<p>The following errors were detected:</p><ul></ul>");
217  for (var i=0; i!= data.error.length; i++)
218  $(selector+" ul").append("<li>"+escapeHTML(data.error[i])+"</li>");
219  $(selector).show();
220  }
221  else
222  {
223  $(selector).empty();
224  $(selector).hide();
225  }
226  return data.error.length > 0;
227 }
228 <?php }
229 
230 function getAddMinifiedActionStyleFunction() {
231 /* Adds a special class for formatting. If using tooltips, then tooltips are created.
232  * Prerequisites: On grid, need: toolbar: [{name: "create", text: ""}] and in the first of the columns: {title: "Action", columns: [{command: [{name: "edit", text: ""},
233  * {name: "customDelete", text: "", iconClass: "k-icon k-delete"}], width: "8%"}]},
234  * Parameters: String gridId-- name of the grid, Boolean useTooltips-- if yes, create them!
235  */
236 ?>
237 function addMinifiedActionStyle(gridElement, useTooltips, additionalTooltips)
238 {
239  var position= "left";
240  var showAfter= 2000; // 2 seconds
241  var tooltipHandles= [];
242 
243  var tooltips= [{filter: ".k-grid-add", content: "Add new record"},
244  {filter: ".k-grid-customDelete", content: "Delete"},
245  {filter: ".k-grid-edit", content: "Edit"}
246  ];
247 
248  if (typeof(additionalTooltips) != "undefined" && additionalTooltips.length > 0)
249  {
250  tooltips= tooltips.concat(additionalTooltips);
251  }
252 
253  // Setup tooltips (default)
254  if (typeof(useTooltips) == "undefined" || useTooltips)
255  {
256  // Add class for CSS
257  $(gridElement).addClass("minifiedActionGrid");
258 
259  for(var i=0; i!= tooltips.length; i++)
260  {
261  tooltipHandles.push($(gridElement).kendoTooltip({
262  filter: tooltips[i].filter,
263  content: tooltips[i].content,
264  position: position,
265  showAfter: showAfter,
266  callout: false
267  }).data("kendoTooltip"));
268  }
269  }
270  else
271  $(gridElement).addClass("actionGrid");
272  return tooltipHandles;
273 }
274 <?php }
275 
276 function getAddDuplicateErrorFunction() {
277 /* Type of custom error validation. It was the ONLY type and then I had to separate it out.
278  */
279 ?>
280 function addDuplicateError(e, field, onSave)
281 {
282  addError(e,field, onSave, "duplicate");
283 }
284 <?php }
285 
286 function getAddErrorFunction() {
287 /* Goal is to mimic the validation already in place. On field blur, invalid message will show up. On a save, list of invalid messages will show up on the top.
288  * So, the first part is to set up the (!) invalid error message. The tooltip will not change, however, if there is more validation on that field. It will show the same as previously.
289  * Therefore, the second part is to change that. However, Kendo doesn't label tooltips. There will be a tooltip per input with a validation so I cannot assume that it is the first tooltip there is.
290  * So, I iterate through the tooltips, find content that fits the validation messages for any validation the field has e.g. required, homecuDateGT, etc. Once I find that, I add a class to that tooltip.
291  * So, I check for that tooltip class and once found, I change the value of the tooltip. Finally, if the onSave is true, then the error message needs to be appended to the top.
292  * Parameters: Object e-- generic event parameter, String field-- name of the input, Boolean onSave-- two modes: on blur and onSave, the onSave option adds in the appension to the top,
293  * String errorType-- specifies where to get the error message. Script is looking for the data-{errorType}-msg attribute of the input field.
294  */
295  ?>
296 function addError(e, field, onSave, errorType, formValidateDiv, byId)
297 {
298  // Construct invalid message (!) on the field.
299  var input= byId != null && byId ? "#" + field + "Input" : "input[name='"+field+"']";
300  var invalidMsgDiv= $(".k-invalid-msg[data-for='"+ field+ "']");
301  var invalidMsg= $(input).attr("data-"+ errorType +"-msg");
302  $(invalidMsgDiv).addClass("k-widget k-tooltip k-tooltip-validation");
303  $(invalidMsgDiv).attr("data-role", "alert");
304  $(invalidMsgDiv).attr("title", invalidMsg);
305  $(invalidMsgDiv).html("<span class='k-icon k-warning'></span>");
306  $(invalidMsgDiv).show();
307 
308  var div= typeof(formValidateDiv) == "undefined" ? "formValidatePopupDiv" : formValidateDiv;
309 
310  // If class is not already found, then I will need to iterate through the tooltips and add the class.
311  if ($(".k-animation-container.tooltipFor_"+field).length == 0)
312  {
313  // Find all possible messages.
314  var possibleMsgs= [];
315  $(input).each(function (e) {
316  $.each(this.attributes, function(i, attrib){
317  var name = attrib.name.trim();
318  var value = attrib.value.trim();
319 
320  if (name.match(/^data-\w+-msg$/))
321  possibleMsgs.push(value);
322  });
323  });
324 
325  // Search for possible messages and if there is a match, then add a class to the first one and remove the rest.
326  var found= false;
327  $(".k-animation-container .k-tooltip-content").each(function (e) {
328  var value= $(this).text().trim();
329  if ($.grep(possibleMsgs, function(n, i) {return n == value; }).length != 0)
330  {
331  if (!found)
332  {
333  $(this).closest(".k-animation-container").addClass("tooltipFor_"+field);
334  found= true;
335  }
336  else $(this).closest(".k-animation-container").remove();
337  }
338  });
339  }
340 
341  // Now, if the class is found, then I need to change the tooltip to the right value. (If it isn't found, then Kendo will initialize a tooltip for the invalid-msg which is fine behavior.)
342  if ($(".k-animation-container.tooltipFor_"+field).length != 0)
343  {
344  var previousMsg= $(".k-animation-container.tooltipFor_"+field).text();
345  if (previousMsg != invalidMsgDiv)
346  $("#" + div+ " li:contains('"+ previousMsg + "')").each(function (e) {
347  if ($(this).text().trim() == previousMsg) // just because it contains the message doesn't mean that it is the one.
348  $(this).addClass("removeLi");
349  });
350  $(".k-animation-container.tooltipFor_"+field+" .k-tooltip-content").text(invalidMsg);
351  }
352 
353  // It will manually append error message to the end of the error DIV. Currently that is set to formValidatePopupDiv but if necessary, that can move into a variable.
354  if (onSave)
355  {
356  $("#" + div + " .removeLi").remove();
357  if ($("#" + div + " #"+ errorType +"Error").length != 0)
358  $("#" + div + " #"+ errorType +"Error").text(invalidMsg);
359  else if ($("#" + div + " ul").length != 0)
360  $("#" + div + " ul").append("<li id='"+ errorType +"Error'>"+invalidMsg+"</li>");
361  else $("#" + div).html("<p>The following errors were detected:</p><ul><li id='" + errorType +"Error'>"+invalidMsg+"</li></ul>");
362  $("#" + div).show();
363  $("#invalidRequestClientSide").val(true);
364  e.preventDefault();
365  $(".k-popup-edit-form .k-grid-update").removeClass("k-state-disabled");
366  hideWaitWindow();
367  return false;
368  }
369 }
370 <?php }
371 
372 function getSetupDeleteConfirmDialogFunction() {
373 /* For most all editable grids, this is called in the dataBound. 1) Creates a kendoWindow from a template and placeholder found in the precontent.html file and set visibility to false.
374  * 2) Sets up the click events for the button in the template. The AJAX call is sent as a parameter in the function.
375  * 3) Per delete button in the grid, adds logic to open the kendoWindow positionally relative to the delete button.
376  * Parameters: String deleteOperation-- this is the AJAX call e.g. main.php?mainAction=data&operation={deleteOperation},
377  * String gridName-- name of the grid, String gridId-- name of the primary id of the grid.
378  */
379 ?>
380 var gridIdToDelete= null;
381 function setupDeleteConfirmDialog(deleteOperation, gridElement, gridId, dialogWindowSelector, runWhenDeletedFunc, runWhenOpenedFunc, additionalParameters)
382 {
383  var grid= $(gridElement).data("kendoGrid");
384  var deleteConfirm= $("#"+dialogWindowSelector).data("kendoWindow");
385 
386  if (deleteConfirm == null)
387  {
388  deleteConfirm= $("<div id='"+dialogWindowSelector+"'></div>").kendoWindow({
389  content: {template: $("#deleteConfirmTemplate").html()},
390  visible: false,
391  modal: true,
392  title: "Confirm deletion",
393  width: 400,
394  height: 125,
395  resizable: false,
396  open: function() {
397  if (window.activeWindows != null)
398  window.activeWindow.push(this);
399  },
400  close: function() {
401  if (window.activeWindows != null)
402  window.activeWindows.pop();
403  }
404  }).data("kendoWindow");
405 
406  $("#"+dialogWindowSelector).closest(".k-window").addClass("k-windowDelete");
407 
408  var continueBtn= $("#"+dialogWindowSelector).find(".deleteConfirmContinueBtn");
409  $(continueBtn).click(function () {
410  var parameters= typeof(additionalParameters) == "object" ? additionalParameters : {};
411  parameters[gridId]= gridIdToDelete;
412  showWaitWindow();
413  $.post(deleteOperation, parameters,
414  function (data){
415  hideWaitWindow();
416 
417  // If no errors, then remove record from the grid
418  if (!errorsAreShown(data, "formValidateMainDiv"))
419  {
420  var data= grid.dataSource.data().slice(0);
421  data= $.grep(data, function(n,i) { return n[gridId] != gridIdToDelete; });
422  grid.dataSource.data(data);
423 
424  if (typeof(runWhenDeletedFunc) == "function")
425  (runWhenDeletedFunc)();
426  }
427  });
428  deleteConfirm.close();
429  return false;
430  });
431 
432  var cancelBtn= $("#"+dialogWindowSelector).find(".deleteConfirmCancelBtn");
433  $(cancelBtn).click(function () {
434  deleteConfirm.close();
435  return false;
436  });
437  }
438 
439  $(gridElement).find(".k-grid-customDelete").click(function(e){
440  var tr= $(e.target).closest("tr");
441  gridIdToDelete= grid.dataItem(tr)[gridId];
442 
443  deleteConfirm.open();
444  if (window.activeWindows != null)
445  window.activeWindows.push(deleteConfirm);
446 
447  if (typeof(runWhenOpenedFunc) == "function")
448  (runWhenOpenedFunc)(e);
449  return false;
450  });
451 }
452 <?php }
453 
454 function getSetupDeleteConfirmDialogFunction_v2($gridId, $gridPrimary, $gridError, $deleteDialogId, $deleteOperation= null, $template= null)
455 { ?>
456  function initDelete()
457  {
458  $("<div id='<?php echo $deleteDialogId; ?>'></div>").appendTo("body");
459 
460  $("#<?php echo $gridId; ?>").on("click", ".k-grid-custdelete", function() {
461  var deleteDialog= $("#<?php echo $deleteDialogId; ?>").data("kendoWindow");
462  var tr= $(this).closest("tr");
463  $("#<?php echo $deleteDialogId; ?>").data("tr", tr);
464 
465  if (deleteDialog == null)
466  {
467  <?php if (isset($template)) {?>
468  var template= '<?php echo $template; ?>';
469  <?php } else { ?>
470  var template= '<div class="k-edit-form-container"><div class="container_12">\
471  <div class="grid_12 message">Are you sure that you want to delete?</div>\
472  <div class="k-edit-buttons k-state-default"><a class="k-button k-button-icontext deleteConfirmContinueBtn" href="\\#"><span class="k-icon k-update"></span>\
473  Continue</a><a class="k-button k-button-icontext deleteConfirmCancelBtn" href="\\#"><span class="k-icon k-cancel"></span>Cancel</a> </div></div>';
474  <?php } ?>
475 
476  deleteDialog= $("#<?php echo $deleteDialogId; ?>").kendoWindow({
477  content: {template: template},
478  visible: false,
479  modal: true,
480  title: "Confirm deletion",
481  width: 400,
482  height: 125,
483  resizable: false,
484  close: function() {
485  if (window.activeWindows != null)
486  window.activeWindows.pop();
487  }
488  }).data("kendoWindow");
489 
490  $("#<?php echo $deleteDialogId; ?>").closest(".k-window").css({<?php printTopCenterCss(200, "", "jsGuts"); ?>});
491  }
492 
493  deleteDialog.open();
494  if (window.activeWindows != null)
495  window.activeWindows.push(deleteDialog);
496  });
497 
498  $("#<?php echo $deleteDialogId; ?>").on("click", ".deleteConfirmContinueBtn", function() {
499  var tr= $("#<?php echo $deleteDialogId; ?>").data("tr");
500  var grid= $("#<?php echo $gridId; ?>").data("kendoGrid");
501  var primary= grid.dataItem(tr).<?php echo $gridPrimary; ?>;
502  <?php if (isset($deleteOperation)) { ?>
503  showWaitWindow();
504  $.post("<?php echo $deleteOperation; ?>", {<?php echo $gridPrimary; ?>: primary}, function(data) {
505  hideWaitWindow();
506  if (!errorsAreShown(data, "<?php echo $gridError; ?>"))
507  {
508  grid.removeRow(tr);
509  }
510  });
511  <?php } else { ?>
512  grid.removeRow(tr);
513  <?php } ?>
514  $("#<?php echo $deleteDialogId; ?>").data("kendoWindow").close();
515  return false;
516  });
517 
518  $("#<?php echo $deleteDialogId; ?>").on("click", ".deleteConfirmCancelBtn", function() {
519  $("#<?php echo $deleteDialogId; ?>").data("kendoWindow").close();
520  return false;
521  });
522  }
523 <?php }
524 
525 function getShowWaitFunctions() {
526 /* Plagiarized from Mark. These functions open up a Kendo window with the Kendo loading symbol and then close it.
527  */
528 ?>
529 
530 function showWaitWindow()
531 {
532  if ($("#hideSubmitWait").length == 0)
533  {
534  $("body").append("<div id='hideSubmitWait' style='position:relative; left:-2000px;top:-2000px;'></div>");
535  $("#hideSubmitWait").append("<div id='homecuSubmitWait' class='k-block'></div>");
536  $("#homecuSubmitWait").append("<div class='k-loading-image'></div>");
537  }
538 
539  var submitWaitWindow = $("#homecuSubmitWait").data('kendoWindow');
540  if (submitWaitWindow == null)
541  submitWaitWindow= $("#homecuSubmitWait").kendoWindow({
542  title: false,
543  visible:false,
544  resizable: false,
545  modal: true,
546  height:55,
547  minHeight:48,
548  width:54,
549  minWidth:48
550  }).data("kendoWindow");
551  submitWaitWindow.center().open();
552 }
553 
554 function hideWaitWindow()
555 {
556  if ($("#hideSubmitWait").length == 0)
557  {
558  $("body").append("<div id='hideSubmitWait' style='position:relative; left:-2000px;top:-2000px;'></div>");
559  $("#hideSubmitWait").append("<div id='homecuSubmitWait' class='k-block'></div>");
560  $("#homecuSubmitWait").append("<div class='k-loading-image'></div>");
561  }
562 
563  var submitWaitWindow = $("#homecuSubmitWait").data('kendoWindow');
564  if (submitWaitWindow != null)
565  submitWaitWindow.close();
566 }
567 
568 <?php }
569 function getContextMenuForSingleLevelGrid() {
570 
571 ?>
572 
573 function getFullContextMenuSingle(gridSelector, contextMenuSelector, deleteDialogSelector, gridId, deleteUrl)
574 {
575  var fullContextMenu= this;
576  this.contextMenuSelector= contextMenuSelector;
577  this.contextMenu= null;
578  this.gridSelector= gridSelector;
579  this.grid= $("#" + gridSelector).data("kendoGrid");
580  this.deleteUrl= deleteUrl;
581  this.gridId= gridId;
582  this.deleteDialogSelector= deleteDialogSelector;
583  this.openFunc= null;
584  this.selectFunc= null;
585 
586  this.gridCancel= function ()
587  {
588  var rows= $("#" + this.gridSelector +" .k-grid-content tr");
589  var grid= this.grid;
590 
591  $(rows).off("click");
592  $(rows).on("click", function () {
593  grid.editRow($(this));
594  });
595 
596  $(rows).off("mouseenter");
597  $(rows).on("mouseenter", function () {
598  $(this).addClass("k-state-hover");
599  });
600 
601  $(rows).off("mouseleave");
602  $(rows).on("mouseleave", function () {
603  $(this).removeClass("k-state-hover");
604  });
605  }
606 
607  this.gridDatabound= function (initialized)
608  {
609  var rows= $("#" + this.gridSelector +" .k-grid-content tr");
610  var grid= this.grid;
611 
612  $(rows).on("click", function () {
613  grid.editRow($(this));
614  });
615 
616  $(rows).on("mouseenter", function () {
617  $(this).addClass("k-state-hover");
618  });
619 
620  $(rows).on("mouseleave", function () {
621  $(this).removeClass("k-state-hover");
622  });
623 
624  this.initContextMenu();
625  }
626 
627  this.addFunctions= function(openFunc, selectFunc)
628  {
629  this.openFunc= openFunc;
630  this.selectFunc= selectFunc;
631  }
632 
633  this.initContextMenu= function ()
634  {
635  this.contextMenu= $("#" + this.contextMenuSelector).data("kendoContextMenu");
636  if (this.contextMenu == null)
637  {
638  var dataItemForDelete= null;
639  var deleteConfirm= $("<div id='" + this.deleteDialogSelector + "'></div>").kendoWindow({
640  content: {template: $("#deleteConfirmTemplate").html()},
641  visible: false,
642  modal: true,
643  title: "Confirm deletion",
644  width: 400,
645  height: 125,
646  resizable: false,
647  close: function() {
648  if (window.activeWindows != null)
649  window.activeWindows.pop();
650  }
651  }).data("kendoWindow");
652 
653  $("#" + this.deleteDialogSelector).closest(".k-window.k-widget").css({<?php printTopCenterCss(200, "", "jsGuts"); ?>})
654 
655  var gridId= this.gridId;
656  var deleteUrl= this.deleteUrl;
657  var grid= this.grid;
658 
659  $("#" + this.deleteDialogSelector + " .deleteConfirmContinueBtn").click(function () {
660  var parameters= {};
661  parameters[gridId]= dataItemForDelete[gridId];
662  showWaitWindow();
663  $.post(deleteUrl, parameters, function (data){
664  hideWaitWindow();
665  showSQL(data);
666 
667  // If no errors, then remove record from the grid
668  if (!errorsAreShown(data, "formValidateMainDiv"))
669  {
670  var data= grid.dataSource.data().slice(0);
671  data= $.grep(data, function(n,i) { return n[gridId] != dataItemForDelete[gridId]; });
672  grid.dataSource.data(data);
673  }
674  });
675  deleteConfirm.close();
676  return false;
677  });
678 
679  $("#" + this.deleteDialogSelector + " .deleteConfirmCancelBtn").click(function () {
680  deleteConfirm.close();
681  return false;
682  });
683 
684  var openFunc= this.openFunc;
685  var selectFunc= this.selectFunc;
686  this.contextMenu= $("<div id='" + this.contextMenuSelector + "'></div>").kendoContextMenu({
687  target: "#" + this.gridSelector,
688  filter: ".k-grid-content tr",
689  alignToAnchor: true,
690  orientation: "vertical",
691  popupCollision: false,
692  open: function (e)
693  {
694  this.remove("li");
695 
696  var options= [{text: "Edit", cssClass: "editLi"}, {text: "Delete", cssClass: "deleteLi"}];
697  var tr= $(e.event.target).closest("tr"); // If set at the tr level, it returns itself. If set at the td level, it return its parent.
698  var dataItem= grid.dataItem($(tr));
699 
700  if (typeof(openFunc) == "function")
701  (openFunc)(options, dataItem);
702  this.append(options);
703  },
704  select: function (e)
705  {
706  var item= $(e.item);
707  var tr= $(e.target).closest("tr"); // If set at the tr level, it returns itself. If set at the td level, it return its parent.
708  var dataItem= grid.dataItem($(tr));
709 
710  if ($(item).hasClass("editLi"))
711  {
712  grid.editRow($(tr));
713  }
714  else if ($(item).hasClass("deleteLi"))
715  {
716  dataItemForDelete= dataItem;
717  deleteConfirm.open();
718  if (window.activeWindows != null)
719  window.activeWindows.push(deleteConfirm);
720  }
721 
722  if (typeof(selectFunc) == "function")
723  (selectFunc)(item, dataItem);
724  }
725  }).data("kendoContextMenu");
726  }
727  }
728 }
729 
730 <?php }
731 function getContextMenuForDoubleLevelGrid() {
732 
733 ?>
734 
735 function getFullContextMenu(grid, contextMenuId, masterId, detailId)
736 {
737  var fullContextMenu= this;
738 
739  this.contextMenu= null;
740  this.grid= grid;
741  this.contextMenuId= contextMenuId;
742  this.masterId= masterId;
743  this.detailId= detailId;
744  this.detailGrids= [];
745  this.limitFromDatabound= [];
746  this.getDetailData= null;
747  this.dataFile= null;
748  this.masterDeleteOperation= null;
749  this.detailDeleteOperation= null;
750  this.postDeleteFunc= null;
751  this.getNumDetailsFunc= null;
752 
753  this.setDeleteOptions= function(dataFile, masterDeleteOperation, detailDeleteOperation, postDeleteFunc, getNumDetailsFunc)
754  {
755  this.dataFile= dataFile;
756  this.masterDeleteOperation= masterDeleteOperation;
757  this.detailDeleteOperation= detailDeleteOperation;
758  this.postDeleteFunc= postDeleteFunc;
759  this.getNumDetailsFunc= getNumDetailsFunc;
760  }
761 
762  this.setOptionFuncs= function(masterOptionFunc, detailOptionFunc, additionalSelectOptionFunc)
763  {
764  this.masterOptionFunc= masterOptionFunc;
765  this.detailOptionFunc= detailOptionFunc;
766  this.additionalSelectOptionFunc= additionalSelectOptionFunc;
767  }
768 
769  this.setLimitFromDatabound= function(limitFromDatabound)
770  {
771  this.limitFromDatabound= limitFromDatabound;
772  }
773 
774  this.gridCancel= function (e, grid, isMaster)
775  {
776  var rows= null;
777  if (isMaster)
778  {
779  rows= $(grid.tbody).find("tr.k-master-row");
780 
781  if (this.limitFromDatabound.indexOf("click") == -1)
782  {
783  // Put it at the td level because expanding/collapsing the master row should not open up the edit.
784  $(rows).find("[role='gridcell']").off("click");
785  $(rows).find("[role='gridcell']").on("click", function () {
786  grid.editRow($(this).parent());
787  });
788  }
789 
790  }
791  else
792  {
793  rows= $(grid.tbody).find("tr");
794 
795  if (this.limitFromDatabound.indexOf("click") == -1)
796  {
797  $(rows).off("click");
798  $(rows).on("click", function () {
799  grid.editRow($(this));
800  });
801  }
802  }
803 
804  if (this.limitFromDatabound.indexOf("mouseenter") == -1)
805  {
806  $(rows).off("mouseenter");
807  $(rows).on("mouseenter", function () {
808  $(this).addClass("k-state-hover");
809  });
810  }
811 
812  if (this.limitFromDatabound.indexOf("mouseleave") == -1)
813  {
814  $(rows).off("mouseleave");
815  $(rows).on("mouseleave", function () {
816  $(this).removeClass("k-state-hover");
817  });
818  }
819 
820  }
821 
822  this.gridDatabound= function (isMaster, initialized, thisGrid)
823  {
824  var rows= null;
825  if (isMaster)
826  {
827  rows= $(thisGrid.tbody).find("tr.k-master-row");
828  if (!initialized)
829  {
830  // Make sure that all detail grids are initialized for "add subtask option"
831  $(rows).each(function() {
832  thisGrid.expandRow($(this));
833  thisGrid.collapseRow($(this));
834  });
835  }
836  this.initContextMenu();
837 
838  if (this.limitFromDatabound.indexOf("click") == -1)
839  // Put it at the td level because expanding/collapsing the master row should not open up the edit.
840  $(rows).find("[role='gridcell']").on("click", function () {
841  thisGrid.editRow($(this).parent());
842  });
843  }
844  else
845  {
846  rows= $(thisGrid.tbody).find("tr");
847 
848  if (this.limitFromDatabound.indexOf("click") == -1)
849  $(rows).on("click", function () {
850  thisGrid.editRow($(this));
851  });
852  }
853 
854  if (this.limitFromDatabound.indexOf("mouseenter") == -1)
855  $(rows).on("mouseenter", function () {
856  $(this).addClass("k-state-hover");
857  rowForMenu= $(this);
858  });
859 
860  if (this.limitFromDatabound.indexOf("mouseleave") == -1)
861  $(rows).on("mouseleave", function () {
862  $(this).removeClass("k-state-hover");
863  });
864  }
865 
866  this.addToDetailGrid= function (detailGridId, detailGrid)
867  {
868  this.detailGrids[detailGridId]= detailGrid;
869  }
870 
871  this.initContextMenu= function ()
872  {
873  contextMenu= $("#" + contextMenuId).data("kendoContextMenu");
874  if (contextMenu == null)
875  {
876  var dataItemForDelete= null;
877  var gridForDelete= null;
878 
879  // Since these were added later, apparent the variables are not found in this context without reinitialization.
880  var detailData= this.detailData;
881  var dataFile= this.dataFile;
882  var masterDeleteOperation= this.masterDeleteOperation;
883  var detailDeleteOperation= this.detailDeleteOperation;
884  var postDeleteFunc= this.postDeleteFunc;
885 
886  var deleteConfirm= $("<div id='deleteDialog'></div>").kendoWindow({
887  content: {template: $("#deleteConfirmTemplate").html()},
888  visible: false,
889  modal: true,
890  title: "Confirm deletion",
891  width: 400,
892  height: 125,
893  resizable: false,
894  close: function() {
895  if (window.activeWindows != null)
896  window.activeWindows.pop();
897  }
898  }).data("kendoWindow");
899 
900  $("#deleteDialog").closest(".k-window.k-widget").css({<?php printTopCenterCss(200, "", "jsGuts"); ?> });
901 
902  $("#deleteDialog .deleteConfirmContinueBtn").click(function () {
903  var isMaster= $(gridForDelete.tbody).find("tr.k-master-row").length > 0;
904  var url= dataFile + "?operation=";
905  var parameters= {};
906  parameters[masterId]= dataItemForDelete[masterId];
907  if (isMaster)
908  {
909  url+= masterDeleteOperation;
910  }
911  else
912  {
913  url+= detailDeleteOperation;
914  parameters[detailId]= dataItemForDelete[detailId];
915  }
916  showWaitWindow();
917  $.post(url, parameters, function (data){
918  hideWaitWindow();
919  showSQL(data);
920 
921  // If no errors, then remove record from the grid
922  if (!errorsAreShown(data, "formValidateMainDiv"))
923  {
924  if (typeof(postDeleteFunc) == "function")
925  (postDeleteFunc)(isMaster, dataItemForDelete);
926  }
927  });
928  deleteConfirm.close();
929  return false;
930  });
931 
932  $("#deleteDialog .deleteConfirmCancelBtn").click(function () {
933  deleteConfirm.close();
934  return false;
935  });
936 
937  var detailOptionFunc= this.detailOptionFunc;
938  var masterOptionFunc= this.masterOptionFunc;
939  var additionalSelectOptionFunc= this.additionalSelectOptionFunc;
940  var getNumDetailsFunc= this.getNumDetailsFunc;
941 
942  contextMenu= $("<div id='" + contextMenuId + "'></div>").kendoContextMenu({
943  target: "#grid",
944  filter: ".k-master-row [role='gridcell'], .detailGrid .k-grid-content tr",
945  alignToAnchor: true,
946  orientation: "vertical",
947  popupCollision: false,
948  open: function (e)
949  {
950  // Prevent this logic from occurring when a submenu is opened. is("[role='menubar']") refers to the main context menu.
951  if ($(e.item).is("[role='menuitem']"))
952  return;
953 
954  this.remove("li");
955 
956  var options= [];
957  var grid= $(e.event.target).closest(".k-grid").data("kendoGrid");
958  var tr= $(e.event.target).closest("tr"); // If set at the tr level, it returns itself. If set at the td level, it return its parent.
959  var dataItem= grid.dataItem($(tr));
960 
961  if ($(grid.tbody).find(".k-master-row").length != 0)
962  {
963  if (typeof(masterOptionFunc) != "function")
964  options= [{text: "Edit", cssClass: "editLi"}, {text: "Delete", cssClass: "deleteLi"}, {text: "Add detail", cssClass: "addDetailLi"}];
965  else
966  (masterOptionFunc)(options, dataItem);
967  }
968  else
969  {
970  if (typeof(detailOptionFunc) != "function")
971  options= [{text: "Edit", cssClass: "editLi"}, {text: "Delete", cssClass: "deleteLi"}];
972  else
973  (detailOptionFunc)(options, dataItem);
974  }
975 
976  this.append(options);
977  },
978  select: function (e)
979  {
980  var item= $(e.item);
981  var tr= $(e.target).closest("tr"); // If set at the tr level, it returns itself. If set at the td level, it return its parent.
982  var grid= $(e.target).closest(".k-grid").data("kendoGrid");
983  var dataItem= grid.dataItem($(tr));
984 
985  if ($(item).hasClass("editLi"))
986  {
987  grid.editRow($(tr));
988  }
989  else if ($(item).hasClass("deleteLi"))
990  {
991  var numDetails= $(e.target).closest(".detailGrid") > 0 ? 0 : (typeof(getNumDetailsFunc) == "function" ? (getNumDetailsFunc)(dataItem) : 0);
992  if (numDetails > 0)
993  {
994  $("#detailDeleteMsg").show();
995  $("#detailDeleteMsg span").text(numDetails);
996  }
997  else
998  {
999  $("#detailDeleteMsg").hide();
1000  }
1001  dataItemForDelete= dataItem;
1002  gridForDelete= grid;
1003  deleteConfirm.open();
1004  if (window.activeWindows != null)
1005  window.activeWindows.push(deleteConfirm);
1006  }
1007  else if ($(item).hasClass("addDetailLi"))
1008  {
1009  fullContextMenu.detailGrids[dataItem[masterId]].addRow();
1010  }
1011 
1012  if (typeof(additionalSelectOptionFunc) == "function")
1013  (additionalSelectOptionFunc)(item, tr, grid, dataItem);
1014  }
1015  }).data("kendoContextMenu");
1016  }
1017  }
1018 }
1019 <?php }
1020 
1021 function printDeleteTemplate() {
1022 ?>
1023  <script id="deleteConfirmTemplate" type="text/x-kendo-template">
1024  <div class="k-edit-form-container">
1025  <div class="container_12">
1026  <div class="grid_12 message">Are you sure that you want to delete? <span id="detailDeleteMsg" style="display:none;">This will also delete <span>1</span> detail(s) as well.</div>
1027  </div>
1028  <div class="k-edit-buttons k-state-default">
1029  <a class="k-button k-button-icontext deleteConfirmContinueBtn" href="\#">
1030  <span class="k-icon k-update"></span>
1031  Continue
1032  </a>
1033  <a class="k-button k-button-icontext deleteConfirmCancelBtn" href="\#">
1034  <span class="k-icon k-cancel"></span>
1035  Cancel
1036  </a>
1037  </div>
1038  </div>
1039  </script>
1040 <?php }
1041 
1042 function printShuttleGrid($shuttleDiv, $idColumn= "id", $shortColumn= "short", $longColumn= "long", $selectedColumn= "selected", $prevSelectedColumn= "prevSelected")
1043 {
1044  $schema= "schema: {\n\tmodel: {\n\t\tid: \"$idColumn\",\n\t\tfields: {\n\t\t\t$idColumn: {type: \"string\"},\n\t\t\t$shortColumn: {type: \"string\"},\n\t\t\t$longColumn: {type: \"string\"},"
1045  . "\n\t\t\t$selectedColumn: {type: \"boolean\"},\n\t\t\t$prevSelectedColumn: {type: \"boolean\"}\n\t\t}\n\t}\n}";
1046  ?>
1047  function initShuttleGrid(shuttleData)
1048  {
1049  $("#<?php echo $shuttleDiv;?>").html("<div class='grid_12 dontPrint'>\n<div class='grid_7 alpha'>\n<a href='#' class='k-button allAvailable'>Select All</a>\n</div>\n"
1050  + "<div class='grid_1'>\n&nbsp;</div>\n<div class='grid_4 omega'>\n<a href='#' class='k-button allSelected'>Select All</a>\n</div>\n</div>\n"
1051  + "<div class='grid_12'>\n<div class='grid_7 alpha dontPrint'>\n<div class='availableGrid' style='width:100%'></div>\n</div>\n"
1052  + "<div class='grid_1 dontPrint'><a href='#' class='k-button toSelected shuttleNav' style='width:100%'>&#8614;</a>\n"
1053  + "<a href='#' class='k-button toAvailable shuttleNav' style='width:100%'>&#8612;</a>\n</div>\n<div class='printOnly grid_3 alpha' style='display:none;'>Selected Programs:</div>"
1054  + "\n<div class='grid_4 omega'>\n<div class='selectedGrid' style='width:100%'></div>\n</div>\n</div>");
1055 
1056  $("#<?php echo $shuttleDiv; ?>").data("changed", false);
1057 
1058  var first= true;
1059  var alreadyExists= false;
1060  for(var i=0; i!= shuttleData.length; i++)
1061  {
1062  if (first)
1063  {
1064  if (shuttleData[i].<?php echo $prevSelectedColumn; ?> != null)
1065  break; // Column already exists. Don't add it here.
1066  first= false;
1067  }
1068  shuttleData[i].<?php echo $prevSelectedColumn; ?>= shuttleData[i].<?php echo $selectedColumn; ?>;
1069  }
1070 
1071  var shuttleDataSource= new kendo.data.DataSource({
1072  data: new kendo.data.ObservableArray(shuttleData),
1073  <?php echo $schema; ?>,
1074  sort: {field: "<?php echo $shortColumn; ?>", dir: "asc"}
1075  });
1076 
1077  var rowA= 0;
1078  var availableGrid= $("#<?php echo $shuttleDiv; ?> .availableGrid").kendoGrid({
1079  dataSource: shuttleDataSource,
1080  columns: [
1081  {title: "Available", width: "33%"},
1082  {title: "Description"}
1083  ],
1084  rowTemplate: function(model)
1085  {
1086  return model.<?php echo $selectedColumn;?> ? "" : "<tr data-uid='" + model.uid + "'" + (rowA++ % 2 == 1 ? " class='k-alt'" : "") + "><td>"
1087  + model.<?php echo $shortColumn; ?> + "</td><td>" + model.<?php echo $longColumn; ?> + "</td></tr>";
1088  },
1089  dataBound: function()
1090  {
1091  rowA= 0;
1092  },
1093  height: "300px"
1094  }).data("kendoGrid");
1095 
1096  var rowB= 0;
1097  var selectedGrid= $("#<?php echo $shuttleDiv; ?> .selectedGrid").kendoGrid({
1098  dataSource: shuttleDataSource,
1099  columns: [
1100  {field: "<?php echo $shortColumn; ?>", title: "Selected"}
1101  ],
1102  rowTemplate: function(model)
1103  {
1104  return !model.<?php echo $selectedColumn;?> ? "" : "<tr data-uid='" + model.uid + "'" + (rowB++ % 2 == 1 ? " class='k-alt'" : "") + "><td>"
1105  + model.<?php echo $shortColumn; ?> + "</td></tr>";
1106  },
1107  dataBound: function()
1108  {
1109  rowB= 0;
1110  },
1111  height: "300px"
1112  }).data("kendoGrid");
1113 
1114  $("#<?php echo $shuttleDiv; ?>").on("click", ".k-grid tbody tr", function() {
1115  $(this).toggleClass("k-state-selected");
1116  return false;
1117  });
1118 
1119  $("#<?php echo $shuttleDiv; ?> .toSelected").click(function() {
1120  var uids= {};
1121  var numUids= 0;
1122  $("#<?php echo $shuttleDiv; ?> .availableGrid .k-state-selected").each(function() {
1123  uids[$(this).data("uid")]= true;
1124  numUids++;
1125  });
1126 
1127  var data= shuttleDataSource.data();
1128  for(var i=0; i!= data.length; i++)
1129  {
1130  if (uids[data[i].uid] != null)
1131  {
1132  data[i].<?php echo $selectedColumn;?>= true;
1133  numUids--;
1134  }
1135  if (numUids <= 0)
1136  break; // no more need to go through the data.
1137  }
1138  shuttleDataSource.read();
1139  $("#<?php echo $shuttleDiv; ?>").data("changed", true);
1140  return false;
1141  });
1142 
1143  $("#<?php echo $shuttleDiv; ?> .toAvailable").click(function() {
1144  var uids= {};
1145  var numUids= 0;
1146  $("#<?php echo $shuttleDiv; ?> .selectedGrid .k-state-selected").each(function() {
1147  uids[$(this).data("uid")]= true;
1148  numUids++;
1149  });
1150 
1151  var data= shuttleDataSource.data();
1152  for(var i=0; i!= data.length; i++)
1153  {
1154  if (uids[data[i].uid] != null)
1155  {
1156  data[i].<?php echo $selectedColumn;?>= false;
1157  numUids--;
1158  }
1159  if (numUids <= 0)
1160  break; // no more need to go through the data.
1161  }
1162  shuttleDataSource.read();
1163  $("#<?php echo $shuttleDiv; ?>").data("changed", true);
1164  return false;
1165  });
1166 
1167  $("#<?php echo $shuttleDiv; ?> .allSelected").click(function() {
1168  $("#<?php echo $shuttleDiv; ?> .selectedGrid tbody tr:not(.k-state-selected)").addClass("k-state-selected");
1169  return false;
1170  });
1171 
1172  $("#<?php echo $shuttleDiv; ?> .allAvailable").click(function() {
1173  $("#<?php echo $shuttleDiv; ?> .availableGrid tbody tr:not(.k-state-selected)").addClass("k-state-selected");
1174  return false;
1175  });
1176 
1177  var availableData= availableGrid.dataSource.data();
1178  var selectedData= selectedGrid.dataSource.data();
1179  if (availableData.length == 0)
1180  $("#<?php echo $shuttleDiv; ?> .toSelected").enable(false);
1181  if (selectedData.length == 0)
1182  $("#<?php echo $shuttleDiv; ?> .toAvailable").enable(false);
1183  }
1184 <?php }
1185 
1186 function printClickOverlayEvent() { ?>
1187  $("body").on("click", ".k-overlay", function() { if (activeWindows.length > 0) activeWindows[activeWindows.length-1].close(); return false; });
1188 <?php }
1189 
1190 function printTopCenterCss($offset=0, $class="", $mode="css")
1191 {
1192  $class= $class == "" ? ".k-widget.k-window" : "$class";
1193  $offset= intval($offset);
1194  $guts= array("text-align" => "left", "position" => "absolute", "top" => "10px", "left" => "calc(50% - ${offset}px)");
1195  $jsGuts= array();
1196  $cssGuts= array();
1197 
1198  foreach($guts as $key => $value)
1199  {
1200  $jsGuts[]= "\"$key\": \"$value\"";
1201  $cssGuts[]= "$key: $value";
1202  }
1203  $jsGuts= implode(", ", $jsGuts);
1204  $cssGuts= implode(";\n", $cssGuts);
1205 
1206  switch($mode)
1207  {
1208  case "js":
1209  print "\$(\"$class\").css({$jsGuts});";
1210  break;
1211  case "jsGuts":
1212  print $jsGuts;
1213  break;
1214  case "css":
1215  case "":
1216  print "$class {\n$cssGuts;\n}\n";
1217  break;
1218  case "cssGuts":
1219  print $cssGuts;
1220  break;
1221  }
1222 }
1223 
1224 /**
1225  * function IEFixStartsWith()
1226  * startsWith isn't available for IE 11. Here is the fix.
1227  */
1228 function IEFixStartsWith()
1229 { ?>
1230  if (!String.prototype.startsWith) {
1231  String.prototype.startsWith = function(searchString, position){
1232  position = position || 0;
1233  return this.substr(position, searchString.length) === searchString;
1234  };
1235  }
1236 <?php }
1237 
1238 /**
1239  * function printExtendShowOverflown()
1240  * This extends jQuery so that kendoTooltip can only appear on text that is over the bounds with "text-overflow" set.
1241  * Usage: $(".elementclass:overflown")
1242  */
1243 function printExtendShowOverflown()
1244 { ?>
1245  jQuery.extend(jQuery.expr[':'], {
1246  overflown: function (el) {
1247  return el.offsetHeight < el.scrollHeight || el.offsetWidth < el.scrollWidth;
1248  }
1249  });
1250 <?php }
1251 
1252