Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-21840: Show Options Edit Link for Radio and Checkbox Groups #11819

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions CRM/Core/BAO/CustomField.php
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ public static function addQuickFormElement(
$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);
Expand Down Expand Up @@ -837,23 +838,27 @@ public static function addQuickFormElement(
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;
}
}

Expand Down Expand Up @@ -933,10 +938,18 @@ public static function addQuickFormElement(

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');
}
Expand Down Expand Up @@ -988,9 +1001,15 @@ public static function addQuickFormElement(
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');
}
Expand Down
32 changes: 25 additions & 7 deletions CRM/Core/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,12 @@ public function &addRadio($name, $title, $values, $attributes = array(), $separa
$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');
}
Expand Down Expand Up @@ -1144,25 +1150,29 @@ public function addCheckBox(
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']);
Expand Down Expand Up @@ -1425,8 +1435,16 @@ public function addField($name, $props = array(), $required = FALSE, $legacyDate
}

// 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'];
}
Expand Down
5 changes: 4 additions & 1 deletion CRM/Core/Form/Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ public function _elementToArray(&$element, $required, $error) {
}
// 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);
}

Expand Down
162 changes: 156 additions & 6 deletions js/crm.optionEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,162 @@ jQuery(function($) {
.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/>' : '&nbsp;';

// 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(/&nbsp;/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);
}
});