CRM-13992 : QA fixes mentioned in #comment-57745
[civicrm-core.git] / js / Common.js
index d0108f6ac11359ad5567fbfe589255ed89c045d5..e4ccf6112d488579d20e0daa405400ed19ad1807 100644 (file)
@@ -123,7 +123,7 @@ function showHideByValue(trigger_field_id, trigger_value, target_element_id, tar
 
   if (field_type == 'select') {
     var trigger = trigger_value.split("|");
-    var selectedOptionValue = document.getElementById(trigger_field_id).options[document.getElementById(trigger_field_id).selectedIndex].value;
+    var selectedOptionValue = cj('#' + trigger_field_id).val();
 
     var target = target_element_id.split("|");
     for (var j = 0; j < target.length; j++) {
@@ -150,7 +150,7 @@ function showHideByValue(trigger_field_id, trigger_value, target_element_id, tar
     if (field_type == 'radio') {
       var target = target_element_id.split("|");
       for (var j = 0; j < target.length; j++) {
-        if (document.getElementsByName(trigger_field_id)[0].checked) {
+        if (cj('[name="' + trigger_field_id + '"]').is(':checked')) {
           if (invert) {
             cj('#' + target[j]).hide();
           }
@@ -171,222 +171,6 @@ function showHideByValue(trigger_field_id, trigger_value, target_element_id, tar
   }
 }
 
-/**
- *
- * Function for checking ALL or unchecking ALL check boxes in a resultset page.
- *
- * @access public
- * @param fldPrefix - common string which precedes unique checkbox ID and identifies field as
- *                    belonging to the resultset's checkbox collection
- * @param object - checkbox
- * Sample usage: onClick="javascript:changeCheckboxValues('chk_', cj(this) );"
- *
- * @return
- */
-function toggleCheckboxVals(fldPrefix, object) {
-  if (object.id == 'toggleSelect' && cj(object).is(':checked')) {
-    cj('Input[id*="' + fldPrefix + '"],Input[id*="toggleSelect"]').attr('checked', true);
-  }
-  else {
-    cj('Input[id*="' + fldPrefix + '"],Input[id*="toggleSelect"]').attr('checked', false);
-  }
-  // change the class of selected rows
-  on_load_init_checkboxes(object.form.name);
-}
-
-function countSelectedCheckboxes(fldPrefix, form) {
-  fieldCount = 0;
-  for (i = 0; i < form.elements.length; i++) {
-    fpLen = fldPrefix.length;
-    if (form.elements[i].type == 'checkbox' && form.elements[i].name.slice(0, fpLen) == fldPrefix && form.elements[i].checked == true) {
-      fieldCount++;
-    }
-  }
-  return fieldCount;
-}
-
-/**
- * Function to enable task action select
- */
-function toggleTaskAction(status) {
-  var radio_ts = document.getElementsByName('radio_ts');
-  if (!radio_ts[1]) {
-    radio_ts[0].checked = true;
-  }
-  if (radio_ts[0].checked || radio_ts[1].checked) {
-    status = true;
-  }
-
-  var formElements = ['task', 'Go', 'Print'];
-  for (var i = 0; i < formElements.length; i++) {
-    var element = document.getElementById(formElements[i]);
-    if (element) {
-      if (status) {
-        element.disabled = false;
-      }
-      else {
-        element.disabled = true;
-      }
-    }
-  }
-}
-
-/**
- * This function is used to check if any actio is selected and also to check if any contacts are checked.
- *
- * @access public
- * @param fldPrefix - common string which precedes unique checkbox ID and identifies field as
- *                    belonging to the resultset's checkbox collection
- * @param form - name of form that checkboxes are part of
- * Sample usage: onClick="javascript:checkPerformAction('chk_', myForm );"
- *
- */
-function checkPerformAction(fldPrefix, form, taskButton, selection) {
-  var cnt;
-  var gotTask = 0;
-
-  // taskButton TRUE means we don't need to check the 'task' field - it's a button-driven task
-  if (taskButton == 1) {
-    gotTask = 1;
-  }
-  else {
-    if (document.forms[form].task.selectedIndex) {
-      //force user to select all search contacts, CRM-3711
-      if (document.forms[form].task.value == 13 || document.forms[form].task.value == 14) {
-        var toggleSelect = document.getElementsByName('toggleSelect');
-        if (toggleSelect[0].checked || document.forms[form].radio_ts[0].checked) {
-          return true;
-        }
-        else {
-          alert("Please select all contacts for this action.\n\nTo use the entire set of search results, click the 'all records' radio button.");
-          return false;
-        }
-      }
-      gotTask = 1;
-    }
-  }
-
-  if (gotTask == 1) {
-    // If user wants to perform action on ALL records and we have a task, return (no need to check further)
-    if (document.forms[form].radio_ts[0].checked) {
-      return true;
-    }
-
-    cnt = (selection == 1) ? countSelections() : countSelectedCheckboxes(fldPrefix, document.forms[form]);
-    if (!cnt) {
-      alert("Please select one or more contacts for this action.\n\nTo use the entire set of search results, click the 'all records' radio button.");
-      return false;
-    }
-  }
-  else {
-    alert("Please select an action from the drop-down menu.");
-    return false;
-  }
-}
-
-/**
- * This function changes the style for a checkbox block when it is selected.
- *
- * @access public
- * @param chkName - it is name of the checkbox
- * @return null
- */
-function checkSelectedBox(chkName) {
-  var checkElement = cj('#' + chkName);
-  if (checkElement.attr('checked')) {
-    cj('input[value=ts_sel]:radio').attr('checked', true);
-    checkElement.parents('tr').addClass('crm-row-selected');
-  }
-  else {
-    checkElement.parents('tr').removeClass('crm-row-selected');
-  }
-}
-
-/**
- * This function is to show the row with  selected checkbox in different color
- * @param form - name of form that checkboxes are part of
- *
- * @access public
- * @return null
- */
-function on_load_init_checkboxes(form) {
-  var formName = form;
-  var fldPrefix = 'mark_x';
-  for (i = 0; i < document.forms[formName].elements.length; i++) {
-    fpLen = fldPrefix.length;
-    if (document.forms[formName].elements[i].type == 'checkbox' && document.forms[formName].elements[i].name.slice(0, fpLen) == fldPrefix) {
-      checkSelectedBox(document.forms[formName].elements[i].name, formName);
-    }
-  }
-}
-
-/**
- * Function to change the color of the class
- *
- * @param form - name of the form
- * @param rowid - id of the <tr>, <div> you want to change
- *
- * @access public
- * @return null
- */
-function changeRowColor(rowid, form) {
-  switch (document.getElementById(rowid).className) {
-    case 'even-row'          :
-      document.getElementById(rowid).className = 'selected even-row';
-      break;
-    case 'odd-row'           :
-      document.getElementById(rowid).className = 'selected odd-row';
-      break;
-    case 'selected even-row' :
-      document.getElementById(rowid).className = 'even-row';
-      break;
-    case 'selected odd-row'  :
-      document.getElementById(rowid).className = 'odd-row';
-      break;
-    case 'form-item'         :
-      document.getElementById(rowid).className = 'selected';
-      break;
-    case 'selected'          :
-      document.getElementById(rowid).className = 'form-item';
-  }
-}
-
-/**
- * This function is to show the row with  selected checkbox in different color
- * @param form - name of form that checkboxes are part of
- *
- * @access public
- * @return null
- */
-function on_load_init_check(form) {
-  for (i = 0; i < document.forms[form].elements.length; i++) {
-    if (( document.forms[form].elements[i].type == 'checkbox'
-      && document.forms[form].elements[i].checked == true )
-      || ( document.forms[form].elements[i].type == 'hidden'
-      && document.forms[form].elements[i].value == 1 )) {
-      var ss = document.forms[form].elements[i].id;
-      var row = 'rowid' + ss;
-      changeRowColor(row, form);
-    }
-  }
-}
-
-/**
- * reset all the radio buttons with a given name
- *
- * @param string fieldName
- * @param object form
- * @return null
- */
-function unselectRadio(fieldName, form) {
-  for (i = 0; i < document.forms[form].elements.length; i++) {
-    if (document.forms[form].elements[i].name == fieldName) {
-      document.forms[form].elements[i].checked = false;
-    }
-  }
-  return;
-}
-
 /**
  * Function to change button text and disable one it is clicked
  *
@@ -447,6 +231,7 @@ function showHideRow(index) {
   return false;
 }
 
+CRM.utils = CRM.utils || {};
 CRM.strings = CRM.strings || {};
 CRM.validate = CRM.validate || {
   params: {},
@@ -455,32 +240,161 @@ CRM.validate = CRM.validate || {
 
 (function ($, undefined) {
   "use strict";
-  $(document).ready(function () {
-    $().crmtooltip();
-    $('.crm-container table.row-highlight').on('change', 'input.select-row, input.select-rows', function () {
-      var target, table = $(this).closest('table');
-      if ($(this).hasClass('select-rows')) {
-        target = $('tbody tr', table);
-        $('input.select-row', table).prop('checked', $(this).prop('checked'));
+
+  // https://github.com/ivaynberg/select2/pull/2090
+  $.fn.select2.defaults.width = 'resolve';
+
+  // Workaround for https://github.com/ivaynberg/select2/issues/1246
+  $.ui.dialog.prototype._allowInteraction = function(e) {
+    return !!$(e.target).closest('.ui-dialog, .ui-datepicker, .select2-drop').length;
+  };
+
+  /**
+   * Populate a select list, overwriting the existing options except for the placeholder.
+   * @param $el jquery collection - 1 or more select elements
+   * @param options array in format returned by api.getoptions
+   */
+  CRM.utils.setOptions = function($el, options) {
+    $el.each(function() {
+      var
+        $elect = $(this),
+        val = $elect.val() || [];
+      if (typeof(val) !== 'array') {
+        val = [val];
       }
-      else {
-        target = $(this).closest('tr');
-        $('input.select-rows', table).prop('checked', $(".select-row:not(':checked')", table).length < 1);
+      $elect.find('option[value!=""]').remove();
+      $.each(options, function(key, option) {
+        var selected = ($.inArray(''+option.key, val) > -1) ? 'selected="selected"' : '';
+        $elect.append('<option value="' + option.key + '"' + selected + '>' + option.value + '</option>');
+      });
+      $elect.trigger('crmOptionsUpdated').trigger('change');
+    });
+  };
+
+  CRM.utils.formatSelect2Result = function(row) {
+    var markup = '<div class="crm-select2-row">';
+    if (row.image !== undefined) {
+      markup += '<div class="crm-select2-image"><img src="' + row.image + '"/></div>';
+    }
+    else if (row.icon_class) {
+      markup += '<div class="crm-select2-icon"><div class="crm-icon ' + row.icon_class + '-icon"></div></div>';
+    }
+    markup += '<div><div class="crm-select2-row-label">' + row.label + '</div>';
+    markup += '<div class="crm-select2-row-description">';
+    $.each(row.description || [], function(k, text) {
+      markup += '<p>' + text + '</p>';
+    });
+    markup += '</div></div></div>';
+    return markup;
+  };
+  
+  CRM.utils.formatSelect2CreateLinks = function($el) {
+    var markup = '';
+    $.each($el.data('create-links'), function(k, link) {
+      markup += ' <a class="crm-add-entity crm-hover-button" href="' + link.url + '">';
+      if (link.name) {
+        markup += '<span class="icon ' + link.name + '-icon"></span> ';
       }
-      target.toggleClass('crm-row-selected', $(this).is(':checked'));
+      markup += link.label + '</a>';
     });
-    $('#crm-container').live('click', function (event) {
-      if ($(event.target).is('.btn-slide')) {
-        var currentActive = $('#crm-container .btn-slide-active');
-        currentActive.children().hide();
-        currentActive.removeClass('btn-slide-active');
-        $(event.target).children().show();
-        $(event.target).addClass('btn-slide-active');
+    return markup;
+  };
+
+  // Initialize widgets
+  $(document).on('crmLoad', function(e) {
+    $('table.row-highlight', e.target)
+      .off('.rowHighlight')
+      .on('change.rowHighlight', 'input.select-row, input.select-rows', function () {
+        var target, table = $(this).closest('table');
+        if ($(this).hasClass('select-rows')) {
+          target = $('tbody tr', table);
+          $('input.select-row', table).prop('checked', $(this).prop('checked'));
+        }
+        else {
+          target = $(this).closest('tr');
+          $('input.select-rows', table).prop('checked', $(".select-row:not(':checked')", table).length < 1);
+        }
+        target.toggleClass('crm-row-selected', $(this).is(':checked'));
+      })
+      .find('input.select-row:checked').parents('tr').addClass('crm-row-selected');
+    $('.crm-select2', e.target).each(function() {
+      var $el = $(this), options = {};
+      // quickform doesn't support optgroups so here's a hack :(
+      $('option[value^=crm_optgroup]', this).each(function() {
+        $(this).nextUntil('option[value^=crm_optgroup]').wrapAll('<optgroup label="' + $(this).text() + '" />');
+        $(this).remove();
+      });
+      // Defaults for single-selects
+      if ($el.is('select:not([multiple])')) {
+        options.minimumResultsForSearch = 10;
+        options.allowClear = !($el.hasClass('required'));
+        if ($('option:first', this).val() === '') {
+          options.placeholderOption = 'first';
+        }
       }
-      else {
-        $('.btn-slide .panel').hide();
-        $('.btn-slide-active').removeClass('btn-slide-active');
+      $.extend(options, $el.data('select-params') || {});
+      // Autocomplete using the getlist api
+      if ($el.data('api-entity') && $el.hasClass('crm-form-entityref')) {
+        $el.addClass('crm-ajax-select');
+        var settings = {
+          // Use select2 ajax helper instead of CRM.api because it provides more value
+          ajax: {
+            url: CRM.url('civicrm/ajax/rest'),
+            data: function (input, page_num) {
+              var params = $el.data('api-params') || {};
+              params.input = input;
+              params.page_num = page_num;
+              return {
+                entity: $el.data('api-entity'),
+                action: 'getlist',
+                json: JSON.stringify(params)
+              };
+            },
+            results: function(data) {
+              return {more: data.more_results, results: data.values || []};
+            }
+          },
+          formatResult: CRM.utils.formatSelect2Result,
+          formatSelection: function(row) {
+            return row.label;
+          },
+          escapeMarkup: function (m) {return m;},
+          initSelection: function(el, callback) {
+            callback(el.data('entity-value'));
+          }
+        };
+        if ($el.data('create-links')) {
+          options.formatInputTooShort = function() {
+            var txt = $el.data('select-params').formatInputTooShort || $.fn.select2.defaults.formatInputTooShort;
+            return txt + ' ' + ts('or') + '<br />' + CRM.utils.formatSelect2CreateLinks($el);
+          };
+          options.formatNoMatches = function() {
+            var txt = $el.data('select-params').formatNoMatches || $.fn.select2.defaults.formatNoMatches;
+            return txt + '<br />' + CRM.utils.formatSelect2CreateLinks($el);
+          };
+          $el.on('select2-open', function() {
+            var $el = $(this);
+            $('#select2-drop').off('.crmEntity').on('click.crmEntity', 'a.crm-add-entity', function(e) {
+              $el.select2('close');
+              CRM.loadForm($(this).attr('href'), {
+                dialog: {width: 500, height: 'auto'}
+              }).on('crmFormSuccess', function(e, data) {
+                if ($el.select2('container').hasClass('select2-container-multi')) {
+                  var selection = $el.select2('data');
+                  selection.push(data);
+                  $el.select2('data', selection);
+                } else {
+                  $el.select2('data', data);
+                }
+              });
+              return false;
+            });
+          });
+        }
+        options = $.extend(settings, options);
       }
+      options.dropdownCssClass = 'crm-container';
+      $(this).select2(options).removeClass('crm-select2');
     });
   });
 
@@ -566,15 +480,20 @@ CRM.validate = CRM.validate || {
       .on('click', 'a.crm-summary-link', false);
   };
 
-  var h;
+  var helpDisplay, helpPrevious;
   CRM.help = function (title, params, url) {
-    h && h.close && h.close();
-    var options = {
-      expires: 0
-    };
-    h = CRM.alert('...', title, 'crm-help crm-msg-loading', options);
+    if (helpDisplay && helpDisplay.close) {
+      // If the same link is clicked twice, just close the display - todo use underscore method for this comparison
+      if (helpDisplay.isOpen && helpPrevious === JSON.stringify(params)) {
+        helpDisplay.close();
+        return;
+      }
+      helpDisplay.close();
+    }
+    helpPrevious = JSON.stringify(params);
     params.class_name = 'CRM_Core_Page_Inline_Help';
     params.type = 'page';
+    helpDisplay = CRM.alert('...', title, 'crm-help crm-msg-loading', {expires: 0});
     $.ajax(url || CRM.url('civicrm/ajax/inline'),
       {
         data: params,
@@ -590,6 +509,37 @@ CRM.validate = CRM.validate || {
       }
     );
   };
+  /**
+   * @param startMsg string
+   * @param endMsg string|function
+   * @param deferred optional jQuery deferred object
+   * @return jQuery deferred object - if not supplied a new one will be created
+   */
+  var fadeOut;
+  CRM.status = function(startMsg, endMsg, deferred) {
+    var $bar = $('#civicrm-menu');
+    if (!$bar.length) {
+      console && console.log && console.log('CRM.status called on a page with no menubar');
+      return;
+    }
+    $('.crm-menubar-status-container', $bar).remove();
+    fadeOut && window.clearTimeout(fadeOut);
+    $bar.append('<li class="crm-menubar-status-container status-busy"><div class="crm-menubar-status-progressbar"><div class="crm-menubar-status-msg">' + startMsg + '</div></div></li>');
+    $('.crm-menubar-status-container', $bar).css('min-width', $('.crm-menubar-status-container', $bar).width());
+    deferred || (deferred = new $.Deferred());
+    deferred.done(function(data) {
+      var msg = typeof(endMsg) === 'function' ? endMsg(data) : endMsg;
+      $('.crm-menubar-status-container', $bar).removeClass('status-busy').addClass('status-done').show().find('.crm-menubar-status-msg').html(msg);
+      if (msg) {
+        fadeOut = window.setTimeout(function() {
+          $('.crm-menubar-status-container', $bar).fadeOut('slow');
+        }, 2000);
+      } else {
+        $('.crm-menubar-status-container', $bar).hide();
+      }
+    });
+    return deferred;
+  };
 
   /**
    * @param string text Displayable message
@@ -650,6 +600,7 @@ CRM.validate = CRM.validate || {
    *  passing in a function instead of an object is a shortcut for a sinlgle button labeled "Continue"
    * @param options {object|void} Override defaults, keys include 'title', 'message',
    *  see jQuery.dialog for full list of available params
+   * @param cancelLabel {string}
    */
   CRM.confirm = function (buttons, options, cancelLabel) {
     var dialog, callbacks = {};
@@ -679,8 +630,9 @@ CRM.validate = CRM.validate || {
     }
     $.each(callbacks, function (label, callback) {
       settings.buttons[label] = function () {
-        callback.call(dialog);
-        dialog.dialog('close');
+        if (callback.call(dialog) !== false) {
+          dialog.dialog('close');
+        }
       };
     });
     dialog = $('<div class="crm-container crm-confirm-dialog"></div>')
@@ -768,43 +720,335 @@ CRM.validate = CRM.validate || {
     });
   }
 
+  $.widget('civi.crmSnippet', {
+    options: {
+      url: null,
+      block: true,
+      crmForm: null
+    },
+    _originalContent: null,
+    _originalUrl: null,
+    isOriginalUrl: function() {
+      var 
+        args = {}, 
+        same = true,
+        newUrl = this._formatUrl(this.options.url),
+        oldUrl = this._formatUrl(this._originalUrl);
+      // Compare path
+      if (newUrl.split('?')[0] !== oldUrl.split('?')[0]) {
+        return false;
+      }
+      // Compare arguments
+      $.each(newUrl.split('?')[1].split('&'), function(k, v) {
+        var arg = v.split('=');
+        args[arg[0]] = arg[1];
+      });
+      $.each(oldUrl.split('?')[1].split('&'), function(k, v) {
+        var arg = v.split('=');
+        if (args[arg[0]] !== undefined && arg[1] !== args[arg[0]]) {
+          same = false;
+        }
+      });
+      return same;
+    },
+    resetUrl: function() {
+      this.options.url = this._originalUrl;
+    },
+    _create: function() {
+      this.element.addClass('crm-ajax-container');
+      if (!this.element.is('.crm-container *')) {
+        this.element.addClass('crm-container');
+      }
+      this._handleOrderLinks();
+      // Set default if not supplied
+      this.options.url = this.options.url || document.location.href;
+      this._originalUrl = this.options.url;
+    },
+    _onFailure: function(data) {
+      this.options.block && this.element.unblock();
+      this.element.trigger('crmAjaxFail', data);
+      CRM.alert(ts('Unable to reach the server. Please refresh this page in your browser and try again.'), ts('Network Error'), 'error');
+    },
+    _formatUrl: function(url) {
+      // Strip hash
+      url = url.split('#')[0];
+      // Add snippet argument to url
+      if (url.search(/[&?]snippet=/) < 0) {
+        url += (url.indexOf('?') < 0 ? '?' : '&') + 'snippet=json';
+      } else {
+        url = url.replace(/snippet=[^&]*/, 'snippet=json');
+      }
+      return url;
+    },
+    // Hack to deal with civicrm legacy sort functionality
+    _handleOrderLinks: function() {
+      var that = this;
+      $('a.crm-weight-arrow', that.element).click(function(e) {
+        that.options.block && that.element.block();
+        $.getJSON(that._formatUrl(this.href)).done(function() {
+          that.refresh();
+        });
+        e.stopImmediatePropagation();
+        return false;
+      });
+    },
+    refresh: function() {
+      var that = this;
+      var url = this._formatUrl(this.options.url);
+      this.options.block && $('.blockOverlay', this.element).length < 1 && this.element.block();
+      $.getJSON(url, function(data) {
+        if (typeof(data) != 'object' || typeof(data.content) != 'string') {
+          that._onFailure(data);
+          return;
+        }
+        data.url = url;
+        that.element.trigger('crmBeforeLoad', data);
+        if (that._originalContent === null) {
+          that._originalContent = that.element.contents().detach();
+        }
+        that.element.html(data.content);
+        that._handleOrderLinks();
+        that.element.trigger('crmLoad', data);
+        that.options.crmForm && that.element.trigger('crmFormLoad', data);
+      }).fail(function() {
+          that._onFailure();
+        });
+    },
+    _destroy: function() {
+      this.element.removeClass('crm-ajax-container');
+      if (this._originalContent !== null) {
+        this.element.empty().append(this._originalContent);
+      }
+    }
+  });
+
+  var dialogCount = 0;
+  CRM.loadPage = function(url, options) {
+    var settings = {
+      target: '#crm-ajax-dialog-' + (dialogCount++),
+      dialog: false
+    };
+    if (!options || !options.target) {
+      settings.dialog = {
+        modal: true,
+        width: '65%',
+        height: parseInt($(window).height() * .75),
+        close: function() {
+          $(this).dialog('destroy').remove();
+        }
+      };
+    }
+    options && $.extend(true, settings, options);
+    settings.url = url;
+    // Create new dialog
+    if (settings.dialog) {
+      $('<div id="'+ settings.target.substring(1) +'"><div class="crm-loading-element">' + ts('Loading') + '...</div></div>').dialog(settings.dialog);
+    }
+    if (settings.dialog && !settings.dialog.title) {
+      $(settings.target).on('crmLoad', function(event, data) {
+        data.title && $(this).dialog('option', 'title', data.title);
+      });
+    }
+    $(settings.target).crmSnippet(settings).crmSnippet('refresh');
+    return $(settings.target);
+  };
+
+  CRM.loadForm = function(url, options) {
+    var settings = {
+      crmForm: {
+        ajaxForm: {},
+        autoClose: true,
+        validate: true,
+        refreshAction: ['next_new', 'submit_savenext'],
+        cancelButton: '.cancel.form-submit',
+        openInline: 'a.button:not("[href=#], .no-popup")',
+        onCancel: function(event) {},
+        onError: function(data) {
+          var $el = $(this);
+          $el.html(data.content).trigger('crmLoad', data).trigger('crmFormLoad', data).trigger('crmFormError', data);
+          if (typeof(data.errors) == 'object') {
+            $.each(data.errors, function(formElement, msg) {
+              $('[name="'+formElement+'"]', $el).crmError(msg);
+            });
+          }
+        }
+      }
+    };
+    // Hack to make delete dialogs smaller
+    if (url.indexOf('/delete') > 0 || url.indexOf('action=delete') > 0) {
+      settings.dialog = {
+        width: 400,
+        height: 300
+      };
+    }
+    // Move options that belong to crmForm. Others will be passed through to crmSnippet
+    options && $.each(options, function(key, value) {
+      if (typeof(settings.crmForm[key]) !== 'undefined') {
+        settings.crmForm[key] = value;
+      }
+      else {
+        settings[key] = value;
+      }
+    });
+
+    var widget = CRM.loadPage(url, settings);
+
+    widget.on('crmFormLoad', function(event, data) {
+      var $el = $(this);
+      var settings = $el.crmSnippet('option', 'crmForm');
+      settings.cancelButton && $(settings.cancelButton, this).click(function(event) {
+        var returnVal = settings.onCancel.call($el, event);
+        if (returnVal !== false) {
+          $el.trigger('crmFormCancel', event);
+          if ($el.data('uiDialog') && settings.autoClose) {
+            $el.dialog('close');
+          }
+          else if (!settings.autoClose) {
+            $el.crmSnippet('resetUrl').crmSnippet('refresh');
+          }
+        }
+        return returnVal === false;
+      });
+      if (settings.validate) {
+        $("form", this).validate(typeof(settings.validate) == 'object' ? settings.validate : CRM.validate.params);
+      }
+      $("form", this).ajaxForm($.extend({
+        url: data.url.replace(/reset=1[&]?/, ''),
+        dataType: 'json',
+        success: function(response) {
+          if (response.status !== 'form_error') {
+            $el.crmSnippet('option', 'block') && $el.unblock();
+            $el.trigger('crmFormSuccess', response);
+            // Reset form for e.g. "save and new"
+            if (response.userContext && settings.refreshAction && $.inArray(response.buttonName, settings.refreshAction) >= 0) {
+              $el.crmSnippet('option', 'url', response.userContext).crmSnippet('refresh');
+            }
+            else if ($el.data('uiDialog') && settings.autoClose) {
+              $el.dialog('close');
+            }
+            else if (settings.autoClose === false) {
+              $el.crmSnippet('resetUrl').crmSnippet('refresh');
+            }
+          }
+          else {
+            response.url = data.url;
+            settings.onError.call($el, response);
+          }
+        },
+        beforeSerialize: function(form, options) {
+          if (window.CKEDITOR && window.CKEDITOR.instances) {
+            $.each(CKEDITOR.instances, function() {
+              this.updateElement && this.updateElement();
+            });
+          }
+        },
+        beforeSubmit: function(submission) {
+          $el.crmSnippet('option', 'block') && $el.block();
+          $el.trigger('crmFormSubmit', submission);
+        }
+      }, settings.ajaxForm));
+      if (settings.openInline) {
+        settings.autoClose = $el.crmSnippet('isOriginalUrl');
+        $(settings.openInline, this).click(function(event) {
+          $el.crmSnippet('option', 'url', $(this).attr('href')).crmSnippet('refresh');
+          return false;
+        });
+      }
+    });
+    return widget;
+  };
+
+  // Preprocess all cj ajax calls to display messages
+  $(document).ajaxSuccess(function(event, xhr, settings) {
+    try {
+      if ((!settings.dataType || settings.dataType == 'json') && xhr.responseText) {
+        var response = $.parseJSON(xhr.responseText);
+        if (typeof(response.crmMessages) == 'object') {
+          $.each(response.crmMessages, function(n, msg) {
+            CRM.alert(msg.text, msg.title, msg.type, msg.options);
+          })
+        }
+      }
+    }
+    // Suppress errors
+    catch (e) {}
+  });
+
+  /**
+   * Temporary stub to get around name conflict with legacy jQuery.autocomplete plugin
+   */
+  $.widget('civi.crmAutocomplete', $.ui.autocomplete, {});
+
   $(function () {
+    // Trigger crmLoad on initial content for consistency. It will also be triggered for ajax-loaded content.
+    $('.crm-container').trigger('crmLoad');
+
     if ($('#crm-notification-container').length) {
       // Initialize notifications
       $('#crm-notification-container').notify();
       messagesFromMarkup.call($('#crm-container'));
-      $('#crm-container').on('crmFormLoad', '*', messagesFromMarkup);
     }
 
     // bind the event for image popup
-    $('body').on('click', 'a.crm-image-popup', function() {
-      var o = $('<div class="crm-container crm-custom-image-popup"><img src=' + $(this).attr('href') + '></div>');
+    $('body')
+      .on('click', 'a.crm-image-popup', function() {
+        var o = $('<div class="crm-container crm-custom-image-popup"><img src=' + $(this).attr('href') + '></div>');
 
-      CRM.confirm('',
-        {
-          title: ts('Preview'),
-          message: o
-        },
-        ts('Done')
-      );
-      return false;
-    });
+        CRM.confirm('',
+          {
+            title: ts('Preview'),
+            message: o
+          },
+          ts('Done')
+        );
+        return false;
+      })
+
+      .on('click', function (event) {
+        $('.btn-slide-active').removeClass('btn-slide-active').find('.panel').hide();
+        if ($(event.target).is('.btn-slide')) {
+          $(event.target).addClass('btn-slide-active').find('.panel').show();
+        }
+      })
+
+      .on('click', 'a.crm-option-edit-link', function() {
+        var link = $(this);
+        CRM.loadForm(this.href, {openInline: 'a:not("[href=#], .no-popup")'})
+          // Lots of things can happen once the form opens, this is the only event we can really rely on
+          .on('dialogclose', function() {
+            link.trigger('crmOptionsEdited');
+            var $elects = $('select[data-option-edit-path="' + link.data('option-edit-path') + '"]');
+            if ($elects.data('api-entity') && $elects.data('api-field')) {
+              CRM.api3($elects.data('api-entity'), 'getoptions', {sequential: 1, field: $elects.data('api-field')})
+                .done(function(data) {
+                  CRM.utils.setOptions($elects, data.values);
+                });
+            }
+          });
+        return false;
+      })
+      // Handle clear button for form elements
+      .on('click', 'a.crm-clear-link', function() {
+        $(this).css({visibility: 'hidden'}).siblings('.crm-form-radio:checked').prop('checked', false).change();
+        $(this).siblings('input:text').val('').change();
+        return false;
+      })
+      .on('change', 'input.crm-form-radio:checked', function() {
+        $(this).siblings('.crm-clear-link').css({visibility: ''});
+      });
+    $().crmtooltip();
   });
 
   $.fn.crmAccordions = function (speed) {
-    var container = $('#crm-container');
-    if (speed === undefined) {
-      speed = 200;
-    }
-    if ($(this).length > 0) {
-      container = $(this);
-    }
-    if (container.length > 0 && !container.hasClass('crm-accordion-processed')) {
+    var container = $(this).length > 0 ? $(this) : $('.crm-container');
+    speed = speed === undefined ? 200 : speed;
+    container
+      .off('click.crmAccordions')
       // Allow normal clicking of links
-      container.on('click', 'div.crm-accordion-header a', function (e) {
+      .on('click.crmAccordions', 'div.crm-accordion-header a', function (e) {
         e.stopPropagation && e.stopPropagation();
-      });
-      container.on('click', '.crm-accordion-header, .crm-collapsible .collapsible-title', function () {
+      })
+      .on('click.crmAccordions', '.crm-accordion-header, .crm-collapsible .collapsible-title', function () {
         if ($(this).parent().hasClass('collapsed')) {
           $(this).next().css('display', 'none').slideDown(speed);
         }
@@ -814,8 +1058,6 @@ CRM.validate = CRM.validate || {
         $(this).parent().toggleClass('collapsed');
         return false;
       });
-      container.addClass('crm-accordion-processed');
-    }
   };
   $.fn.crmAccordionToggle = function (speed) {
     $(this).each(function () {