Merge pull request #8101 from colemanw/CRM-18379
[civicrm-core.git] / js / Common.js
index 5abee08f67ee6aceb92502852e3f8db3914aeaba..c936f4f386c36b87590f357975bc89487468ddcb 100644 (file)
@@ -586,10 +586,12 @@ if (!CRM.vars) CRM.vars = {};
       combined = _.cloneDeep(params),
       filter = $.extend({}, $el.data('user-filter') || {});
     if (filter.key && filter.value) {
+      // Fieldname may be prefixed with joins
+      var fieldName = _.last(filter.key.split('.'));
       // Special case for contact type/sub-type combo
-      if (filter.key === 'contact_type' && (filter.value.indexOf('__') > 0)) {
-        combined.params.contact_type = filter.value.split('__')[0];
-        combined.params.contact_sub_type = filter.value.split('__')[1];
+      if (fieldName === 'contact_type' && (filter.value.indexOf('__') > 0)) {
+        combined.params[filter.key] = filter.value.split('__')[0];
+        combined.params[filter.key.replace('contact_type', 'contact_sub_type')] = filter.value.split('__')[1];
       } else {
         // Allow json-encoded api filters e.g. {"BETWEEN":[123,456]}
         combined.params[filter.key] = filter.value.charAt(0) === '{' ? $.parseJSON(filter.value) : filter.value;
@@ -600,7 +602,7 @@ if (!CRM.vars) CRM.vars = {};
 
   function copyAttributes($source, $target, attributes) {
     _.each(attributes, function(name) {
-      if ($source.attr(name)) {
+      if ($source.attr(name) !== undefined) {
         $target.attr(name, $source.attr(name));
       }
     });
@@ -612,15 +614,25 @@ if (!CRM.vars) CRM.vars = {};
   $.fn.crmDatepicker = function(options) {
     return $(this).each(function() {
       if ($(this).is('.crm-form-date-wrapper .crm-hidden-date')) {
-        // Already initialized
+        // Already initialized - destroy
+        $(this)
+          .off('.crmDatepicker')
+          .css('display', '')
+          .removeClass('crm-hidden-date')
+          .siblings().remove();
+        $(this).unwrap();
+      }
+      if (options === 'destroy') {
         return;
       }
       var
         $dataField = $(this).wrap('<span class="crm-form-date-wrapper" />'),
-        settings = $.extend({}, $dataField.data('datepicker') || {}, options || {}),
+        settings = _.cloneDeep(options || {}),
         $dateField = $(),
         $timeField = $(),
-        $clearLink = $();
+        $clearLink = $(),
+        hasDatepicker = settings.date !== false && settings.date !== 'yy',
+        type = hasDatepicker ? 'text' : 'number';
 
       if (settings.allowClear !== undefined ? settings.allowClear : !$dataField.is('.required, [required]')) {
         $clearLink = $('<a class="crm-hover-button crm-clear-link" title="'+ ts('Clear') +'"><i class="crm-i fa-times"></i></a>')
@@ -639,18 +651,32 @@ if (!CRM.vars) CRM.vars = {};
           });
       }
       if (settings.date !== false) {
-        $dateField = $('<input>').insertAfter($dataField);
+        // Render "number" field for year-only format, calendar popup for all other formats
+        $dateField = $('<input type="' + type + '">').insertAfter($dataField);
         copyAttributes($dataField, $dateField, ['placeholder', 'style', 'class', 'disabled']);
-        $dateField.addClass('crm-form-text crm-form-date');
-        settings.date = typeof settings.date === 'string' ? settings.date : CRM.config.dateInputFormat;
-        settings.changeMonth = _.includes('m', settings.date);
-        settings.changeYear = _.includes('y', settings.date);
-        $dateField.datepicker(settings).change(updateDataField);
+        $dateField.addClass('crm-form-' + type);
+        settings.minDate = settings.minDate ? CRM.utils.makeDate(settings.minDate) : null;
+        settings.maxDate = settings.maxDate ? CRM.utils.makeDate(settings.maxDate) : null;
+        if (hasDatepicker) {
+          settings.dateFormat = typeof settings.date === 'string' ? settings.date : CRM.config.dateInputFormat;
+          settings.changeMonth = _.includes(settings.dateFormat, 'm');
+          settings.changeYear = _.includes(settings.dateFormat, 'y');
+          $dateField.addClass('crm-form-date').datepicker(settings);
+        } else {
+          $dateField.attr('min', settings.minDate ? CRM.utils.formatDate(settings.minDate, 'yy') : '1000');
+          $dateField.attr('max', settings.maxDate ? CRM.utils.formatDate(settings.maxDate, 'yy') : '4000');
+        }
+        $dateField.change(updateDataField);
       }
       // Rudimentary validation. TODO: Roll into use of jQUery validate and ui.datepicker.validation
       function isValidDate() {
+        // FIXME: parseDate doesn't work with incomplete date formats; skip validation if no month, day or year in format
+        var lowerFormat = settings.dateFormat.toLowerCase();
+        if (lowerFormat.indexOf('y') < 0 || lowerFormat.indexOf('m') < 0 || lowerFormat.indexOf('d') < 0) {
+          return true;
+        }
         try {
-          $.datepicker.parseDate(settings.date, $dateField.val());
+          $.datepicker.parseDate(settings.dateFormat, $dateField.val());
           return true;
         } catch (e) {
           return false;
@@ -660,8 +686,10 @@ if (!CRM.vars) CRM.vars = {};
         var val = $dataField.val(),
           time = null;
         if (context !== 'userInput' && context !== 'crmClear') {
-          if ($dateField.length) {
+          if (hasDatepicker) {
             $dateField.datepicker('setDate', _.includes(val, '-') ? $.datepicker.parseDate('yy-mm-dd', val) : null);
+          } else if ($dateField.length) {
+            $dateField.val(val.slice(0, 4));
           }
           if ($timeField.length) {
             if (val.length === 8) {
@@ -679,9 +707,11 @@ if (!CRM.vars) CRM.vars = {};
         if (context !== 'crmClear') {
           var val = '';
           if ($dateField.val()) {
-            if (isValidDate()) {
+            if (hasDatepicker && isValidDate()) {
               val = $.datepicker.formatDate('yy-mm-dd', $dateField.datepicker('getDate'));
               $dateField.removeClass('crm-error');
+            } else if (!hasDatepicker) {
+              val = $dateField.val() + '-01-01';
             } else {
               $dateField.addClass('crm-error');
             }
@@ -692,7 +722,7 @@ if (!CRM.vars) CRM.vars = {};
           $dataField.val(val).trigger('change', ['userInput']);
         }
       }
-      $dataField.hide().addClass('crm-hidden-date').on('change', updateInputFields);
+      $dataField.hide().addClass('crm-hidden-date').on('change.crmDatepicker', updateInputFields);
       updateInputFields();
     });
   };
@@ -703,6 +733,7 @@ if (!CRM.vars) CRM.vars = {};
       var defaults = {
         "processing": true,
         "serverSide": true,
+        "aaSorting": [],
         "dom": '<"crm-datatable-pager-top"lfp>rt<"crm-datatable-pager-bottom"ip>',
         "pageLength": 25,
         "drawCallback": function(settings) {
@@ -843,7 +874,9 @@ if (!CRM.vars) CRM.vars = {};
         CRM.utils.setOptions($valField, filterSpec.options, false, filter.value);
       } else {
         $valField.prop('disabled', true);
-        CRM.api3(filterSpec.entity || $el.data('api-entity'), 'getoptions', {field: filter.key, context: 'search', sequential: 1})
+        // Fieldname may be prefixed with joins - strip those out
+        var fieldName = _.last(filter.key.split('.'));
+        CRM.api3(filterSpec.entity || $el.data('api-entity'), 'getoptions', {field: fieldName, context: 'search', sequential: 1})
           .done(function(result) {
             var entity = $el.data('api-entity').toLowerCase(),
               globalFilterSpec = _.find(CRM.config.entityRef.filters[entity], {key: filter.key}) || {};
@@ -925,6 +958,9 @@ if (!CRM.vars) CRM.vars = {};
       $('.crm-select2:not(.select2-offscreen, .select2-container)', e.target).crmSelect2();
       $('.crm-form-entityref:not(.select2-offscreen, .select2-container)', e.target).crmEntityRef();
       $('select.crm-chain-select-control', e.target).off('.chainSelect').on('change.chainSelect', chainSelect);
+      $('.crm-form-text[data-crm-datepicker]', e.target).each(function() {
+        $(this).crmDatepicker($(this).data('crmDatepicker'));
+      });
       // Cache Form Input initial values
       $('form[data-warn-changes] :input', e.target).each(function() {
         $(this).data('crm-initial-value', $(this).is(':checkbox, :radio') ? $(this).prop('checked') : $(this).val());
@@ -1006,21 +1042,29 @@ if (!CRM.vars) CRM.vars = {};
   };
 
   var helpDisplay, helpPrevious;
+  // Non-ajax example:
+  //   CRM.help('Example title', 'Here is some text to describe this example');
+  // Ajax example (will load help id "foo" from templates/CRM/bar.tpl):
+  //   CRM.help('Example title', {id: 'foo', file: 'CRM/bar'});
   CRM.help = function (title, params, url) {
+    var ajax = typeof params !== 'string';
     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)) {
+      // If the same link is clicked twice, just close the display
+      if (helpDisplay.isOpen && _.isEqual(helpPrevious, 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'),
-      {
+    helpPrevious = _.cloneDeep(params);
+    helpDisplay = CRM.alert(ajax ? '...' : params, title, 'crm-help ' + (ajax ? 'crm-msg-loading' : 'info'), {expires: 0});
+    if (ajax) {
+      if (!url) {
+        url = CRM.url('civicrm/ajax/inline');
+        params.class_name = 'CRM_Core_Page_Inline_Help';
+        params.type = 'page';
+      }
+      $.ajax(url, {
         data: params,
         dataType: 'html',
         success: function (data) {
@@ -1031,8 +1075,8 @@ if (!CRM.vars) CRM.vars = {};
           $('#crm-notification-container .crm-help .notify-content:last').html('Unable to load help file.');
           $('#crm-notification-container .crm-help').removeClass('crm-msg-loading').addClass('error');
         }
-      }
-    );
+      });
+    }
   };
   /**
    * @see https://wiki.civicrm.org/confluence/display/CRMDOC/Notification+Reference
@@ -1388,7 +1432,7 @@ if (!CRM.vars) CRM.vars = {};
       })
 
       // Allow normal clicking of links within accordions
-      .on('click.crmAccordions', 'div.crm-accordion-header a', function (e) {
+      .on('click.crmAccordions', 'div.crm-accordion-header a, .collapsible-title a', function (e) {
         e.stopPropagation();
       })
       // Handle accordions