$field = self::getFieldObject($fieldId);
$widget = $field->html_type;
$element = NULL;
+ $customFieldAttributes = array();
// Custom field HTML should indicate group+field name
$groupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $field->custom_group_id);
if ($search || ($widget !== 'AdvMulti-Select' && strpos($widget, 'Select') !== FALSE)) {
$widget = 'Select';
}
- $selectAttributes = array(
- 'data-crm-custom' => $dataCrmCustomVal,
- 'class' => 'crm-select2',
- );
+
+ $customFieldAttributes['data-crm-custom'] = $dataCrmCustomVal;
+ $selectAttributes = array('class' => 'crm-select2');
+
// Search field is always multi-select
if ($search || strpos($field->html_type, 'Multi') !== FALSE) {
$selectAttributes['class'] .= ' huge';
$selectAttributes['multiple'] = 'multiple';
$selectAttributes['placeholder'] = $placeholder;
}
+
// Add data for popup link. Normally this is handled by CRM_Core_Form->addSelect
- if ($field->option_group_id && !$search && $widget == 'Select' && CRM_Core_Permission::check('administer CiviCRM')) {
- $selectAttributes += array(
+ $isSupportedWidget = in_array($widget, ['Select', 'Radio']);
+ $canEditOptions = CRM_Core_Permission::check('administer CiviCRM');
+ if ($field->option_group_id && !$search && $isSelect && $canEditOptions) {
+ $customFieldAttributes += array(
'data-api-entity' => $field->getEntity(),
'data-api-field' => 'custom_' . $field->id,
'data-option-edit-path' => 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $field->option_group_id),
);
+ $selectAttributes += $customFieldAttributes;
}
}
case 'Radio':
$choice = array();
+ parse_str($field->attributes, $radioAttributes);
+ $radioAttributes = array_merge($radioAttributes, $customFieldAttributes);
+
foreach ($options as $v => $l) {
- $choice[] = $qf->createElement('radio', NULL, '', $l, (string) $v, $field->attributes);
+ $choice[] = $qf->createElement('radio', NULL, '', $l, (string) $v, $radioAttributes);
}
$element = $qf->addGroup($choice, $elementName, $label);
+ $optionEditKey = 'data-option-edit-path';
+ if (isset($selectAttributes[$optionEditKey])) {
+ $element->setAttribute($optionEditKey, $selectAttributes[$optionEditKey]);
+ }
+
if ($useRequired && !$search) {
$qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
}
case 'CheckBox':
$check = array();
foreach ($options as $v => $l) {
- $check[] = &$qf->addElement('advcheckbox', $v, NULL, $l, array('data-crm-custom' => $dataCrmCustomVal));
+ $check[] = &$qf->addElement('advcheckbox', $v, NULL, $l, $customFieldAttributes);
}
- $element = $qf->addGroup($check, $elementName, $label);
+
+ $group = $element = $qf->addGroup($check, $elementName, $label);
+ $optionEditKey = 'data-option-edit-path';
+ if (isset($customFieldAttributes[$optionEditKey])) {
+ $group->setAttribute($optionEditKey, $customFieldAttributes[$optionEditKey]);
+ }
+
if ($useRequired && !$search) {
$qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required');
}
$options[] = $this->createElement('radio', NULL, NULL, $var, $key, $attributes);
}
$group = $this->addGroup($options, $name, $title, $separator);
+
+ $optionEditKey = 'data-option-edit-path';
+ if (!empty($attributes[$optionEditKey])) {
+ $group->setAttribute($optionEditKey, $attributes[$optionEditKey]);
+ }
+
if ($required) {
$this->addRule($name, ts('%1 is a required field.', array(1 => $title)), 'required');
}
if ($javascriptMethod) {
foreach ($values as $key => $var) {
if (!$flipValues) {
- $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod);
+ $options[] = $this->createElement('checkbox', $var, NULL, $key, $javascriptMethod, $attributes);
}
else {
- $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod);
+ $options[] = $this->createElement('checkbox', $key, NULL, $var, $javascriptMethod, $attributes);
}
}
}
else {
foreach ($values as $key => $var) {
if (!$flipValues) {
- $options[] = $this->createElement('checkbox', $var, NULL, $key);
+ $options[] = $this->createElement('checkbox', $var, NULL, $key, $attributes);
}
else {
- $options[] = $this->createElement('checkbox', $key, NULL, $var);
+ $options[] = $this->createElement('checkbox', $key, NULL, $var, $attributes);
}
}
}
- $this->addGroup($options, $id, $title, $separator);
+ $group = $this->addGroup($options, $id, $title, $separator);
+ $optionEditKey = 'data-option-edit-path';
+ if (!empty($attributes[$optionEditKey])) {
+ $group->setAttribute($optionEditKey, $attributes[$optionEditKey]);
+ }
if ($other) {
$this->addElement('text', $id . '_other', ts('Other'), $attributes[$id . '_other']);
}
// Add data for popup link.
- if ((!empty($props['option_url']) || !array_key_exists('option_url', $props)) && ($context != 'search' && $widget == 'Select' && CRM_Core_Permission::check('administer CiviCRM'))) {
- $props['data-option-edit-path'] = !empty($props['option_url']) ? $props['option_url'] : CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
+ $canEditOptions = CRM_Core_Permission::check('administer CiviCRM');
+ $hasOptionUrl = !empty($props['option_url']);
+ $optionUrlKeyIsSet = array_key_exists('option_url', $props);
+ $shouldAdd = $context !== 'search' && $isSelect && $canEditOptions;
+
+ // Only add if key is not set, or if non-empty option url is provided
+ if (($hasOptionUrl || !$optionUrlKeyIsSet) && $shouldAdd) {
+ $optionUrl = $hasOptionUrl ? $props['option_url'] :
+ CRM_Core_PseudoConstant::getOptionEditUrl($fieldSpec);
+ $props['data-option-edit-path'] = $optionUrl;
$props['data-api-entity'] = $props['entity'];
$props['data-api-field'] = $props['name'];
}
}
// Active form elements
else {
- if ($element->getType() == 'select' && $element->getAttribute('data-option-edit-path')) {
+ $typesToShowEditLink = array('select', 'group');
+ $hasEditPath = NULL !== $element->getAttribute('data-option-edit-path');
+
+ if (in_array($element->getType(), $typesToShowEditLink) && $hasEditPath) {
$this->addOptionsEditLink($el, $element);
}
.on('click', 'a.crm-option-edit-link', CRM.popup)
.on('crmPopupFormSuccess', 'a.crm-option-edit-link', function() {
$(this).trigger('crmOptionsEdited');
- var $elects = $('select[data-option-edit-path="' + $(this).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);
- });
+ var optionEditPath = $(this).data('option-edit-path');
+ var $selects = $('select[data-option-edit-path="' + optionEditPath + '"]');
+ var $inputs = $('input[data-option-edit-path="' + optionEditPath + '"]');
+ var $radios = $inputs.filter('[type=radio]');
+ var $checkboxes = $inputs.filter('[type=checkbox]');
+
+ if ($selects.length > 0) {
+ rebuildOptions($selects, CRM.utils.setOptions);
}
+ else if ($radios.length > 0) {
+ rebuildOptions($radios, rebuildRadioOptions);
+ }
+ else if ($checkboxes.length > 0) {
+ rebuildOptions($checkboxes, rebuildCheckboxOptions);
+ }
+ });
+
+ /**
+ * Fetches options using metadata from the existing ones and calls the
+ * function to rebuild them
+ * @param $existing {object} The existing options, used as metadata store
+ * @param rebuilder {function} Function to be called to rebuild the options
+ */
+ function rebuildOptions($existing, rebuilder) {
+ if ($existing.data('api-entity') && $existing.data('api-field')) {
+ CRM.api3($existing.data('api-entity'), 'getoptions', {
+ sequential: 1,
+ field: $existing.data('api-field')
+ })
+ .done(function(data) {
+ rebuilder($existing, data.values);
+ });
+ }
+ }
+
+ /**
+ * Rebuild checkbox input options, overwriting the existing options
+ *
+ * @param $existing {object} the existing checkbox options
+ * @param newOptions {array} in format returned by api.getoptions
+ */
+ function rebuildCheckboxOptions($existing, newOptions) {
+ var $parent = $existing.first().parent(),
+ $firstExisting = $existing.first(),
+ optionName = $firstExisting.attr('name'),
+ optionAttributes =
+ 'data-option-edit-path =' + $firstExisting.data('option-edit-path') +
+ ' data-api-entity = ' + $firstExisting.data('api-entity') +
+ ' data-api-field = ' + $firstExisting.data('api-field');
+
+ var prefix = optionName.substr(0, optionName.lastIndexOf("["));
+
+ var checkedBoxes = [];
+ $parent.find('input:checked').each(function() {
+ checkedBoxes.push($(this).attr('id'));
+ });
+
+ // remove existing checkboxes
+ $parent.find('input[type=checkbox]').remove();
+
+ // find existing labels for the checkboxes
+ var $checkboxLabels = $parent.find('label').filter(function() {
+ var forAttr = $(this).attr('for') || '';
+
+ return forAttr.indexOf(prefix) !== -1;
+ });
+
+ // find what is used to separate the elements; spaces or linebreaks
+ var $elementAfterLabel = $checkboxLabels.first().next();
+ var separator = $elementAfterLabel.is('br') ? '<br/>' : ' ';
+
+ // remove existing labels
+ $checkboxLabels.remove();
+
+ // remove linebreaks in container
+ $parent.find('br').remove();
+
+ // remove separator whitespace in container
+ $parent.html(function (i, html) {
+ return html.replace(/ /g, '');
+ });
+
+ var renderedOptions = '';
+ // replace missing br at start of element
+ if (separator === '<br/>') {
+ $parent.prepend(separator);
+ renderedOptions = separator;
+ }
+
+ newOptions.forEach(function(option) {
+ var optionId = prefix + '_' + option.key,
+ checked = '';
+
+ if ($.inArray(optionId, checkedBoxes) !== -1) {
+ checked = ' checked="checked"';
+ }
+
+ renderedOptions += '<input type="checkbox" ' +
+ ' value="1"' +
+ ' id="' + optionId + '"' +
+ ' name="' + prefix + '[' + option.key +']' + '"' +
+ checked +
+ ' class="crm-form-checkbox"' +
+ optionAttributes +
+ '><label for="' + optionId + '">' + option.value + '</label>' +
+ separator;
+ });
+
+ // remove final separator
+ renderedOptions = renderedOptions.substring(0, renderedOptions.lastIndexOf(separator));
+
+ var $editLink = $parent.find('.crm-option-edit-link');
+
+ // try to insert before the edit link to maintain structure
+ if ($editLink.length > 0) {
+ $(renderedOptions).insertBefore($editLink);
+ }
+ else {
+ $parent.append(renderedOptions);
+ }
+ }
+
+ /**
+ * Rebuild radio input options, overwriting the existing options
+ *
+ * @param $existing {object} the existing input options
+ * @param newOptions {array} in format returned by api.getoptions
+ */
+ function rebuildRadioOptions($existing, newOptions) {
+ var $parent = $existing.first().parent(),
+ $firstExisting = $existing.first(),
+ optionName = $firstExisting.attr('name'),
+ renderedOptions = '',
+ checkedValue = parseInt($parent.find('input:checked').attr('value')),
+ optionAttributes =
+ 'data-option-edit-path =' + $firstExisting.attr('data-option-edit-path') +
+ ' data-api-entity = ' + $firstExisting.attr('data-api-entity') +
+ ' data-api-field = ' + $firstExisting.attr('data-api-field');
+
+ // remove existing radio inputs and labels
+ $parent.find('input, label').remove();
+
+ newOptions.forEach(function(option) {
+ var optionId = 'CIVICRM_QFID_' + option.key + '_' + optionName,
+ checked = (option.key === checkedValue) ? ' checked="checked"' : '';
+
+ renderedOptions += '<input type="radio" ' +
+ ' value=' + option.key +
+ ' id="' + optionId +'"' +
+ ' name="' + optionName + '"' +
+ checked +
+ ' class="crm-form-radio"' +
+ optionAttributes +
+ '><label for="' + optionId + '">' + option.value + '</label> ';
});
+
+ $parent.prepend(renderedOptions);
+ }
});