}
}
}
+
+ /**
+ * @param mixed $values
+ * @param string $valueType
+ * @param bool $flatten
+ *
+ * @return array
+ */
+ static function getChainSelectValues($values, $valueType, $flatten = FALSE) {
+ if (!$values) {
+ return array();
+ }
+ $values = (array) $values;
+ $elements = array();
+ $list = &$elements;
+ $method = $valueType == 'country' ? 'stateProvinceForCountry' : 'countyForState';
+ foreach ($values as $val) {
+ $result = CRM_Core_PseudoConstant::$method($val);
+
+ // Format for quickform
+ if ($flatten) {
+ // Option-groups for multiple categories
+ if ($result && count($values) > 1) {
+ $elements["crm_optgroup_$val"] = CRM_Core_PseudoConstant::$valueType($val, FALSE);
+ }
+ $elements += $result;
+ }
+
+ // Format for js
+ else {
+ // Option-groups for multiple categories
+ if ($result && count($values) > 1) {
+ $elements[] = array(
+ 'value' => CRM_Core_PseudoConstant::$valueType($val, FALSE),
+ 'children' => array(),
+ );
+ $list = & $elements[count($elements) - 1]['children'];
+ }
+ foreach ($result as $id => $name) {
+ $list[] = array(
+ 'value' => $name,
+ 'key' => $id,
+ );
+ }
+ }
+ }
+ return $elements;
+ }
}
*/
CONST CB_PREFIX = 'mark_x_', CB_PREFIY = 'mark_y_', CB_PREFIZ = 'mark_z_', CB_PREFIX_LEN = 7;
+ /**
+ * @internal to keep track of chain-select fields
+ * @var array
+ */
+ private $_chainSelectFields = array();
+
/**
* Constructor for the basic form page
*
$attributes = '', $required = FALSE, $extra = NULL
) {
// Normalize this property
- if ($type == 'select' && is_array($extra) && !empty($extra['multiple'])) {
- $extra['multiple'] = 'multiple';
+ if ($type == 'select' && is_array($extra)) {
+ if (!empty($extra['multiple'])) {
+ $extra['multiple'] = 'multiple';
+ }
+ else {
+ unset($extra['multiple']);
+ }
}
$element = $this->addElement($type, $name, $label, $attributes, $extra);
if (HTML_QuickForm::isError($element)) {
* @access public
*/
function toSmarty() {
+ $this->preProcessChainSelectFields();
$renderer = $this->getRenderer();
$this->accept($renderer);
$content = $renderer->toArray();
break;
}
}
+
+ /**
+ * Create a chain-select target field. All settings are optional; the defaults usually work.
+ *
+ * @param string $elementName
+ * @param array $settings
+ *
+ * @return HTML_QuickForm_Element
+ */
+ public function addChainSelect($elementName, $settings = array()) {
+ $props = $settings += array(
+ 'control_field' => str_replace(array('state_province', 'StateProvince', 'county', 'County'), array('country', 'Country', 'state_province', 'StateProvince'), $elementName),
+ 'data-callback' => strpos($elementName, 'rovince') ? 'civicrm/ajax/jqState' : 'civicrm/ajax/jqCounty',
+ 'label' => strpos($elementName, 'rovince') ? ts('State / Province') : ts('County'),
+ 'data-empty-prompt' => strpos($elementName, 'rovince') ? ts('Choose country first') : ts('Choose state first'),
+ 'data-none-prompt' => ts('- N/A -'),
+ 'multiple' => FALSE,
+ 'required' => FALSE,
+ 'placeholder' => empty($settings['required']) ? ts('- none -') : ts('- select -'),
+ );
+ CRM_Utils_Array::remove($props, 'label', 'required', 'control_field');
+ $props['class'] = (empty($props['class']) ? '' : "{$props['class']} ") . 'crm-chain-select-target crm-select2';
+ $props['data-select-prompt'] = $props['placeholder'];
+ $props['data-name'] = $elementName;
+
+ $this->_chainSelectFields[$settings['control_field']] = $elementName;
+
+ return $this->add('select', $elementName, $settings['label'], array(), $settings['required'], $props);
+ }
+
+ /**
+ * Set options and attributes for chain select fields based on the controlling field's value
+ */
+ private function preProcessChainSelectFields() {
+ foreach ($this->_chainSelectFields as $control => $target) {
+ $controlField = $this->getElement($control);
+ $targetField = $this->getElement($target);
+
+ $css = (string) $controlField->getAttribute('class');
+ $controlField->updateAttributes(array(
+ 'class' => ($css ? "$css " : 'crm-select2 ') . 'crm-chain-select-control',
+ 'data-target' => $target,
+ ));
+ $selection = $controlField->getValue();
+ $options = array();
+ if ($selection) {
+ $options = CRM_Core_BAO_Location::getChainSelectValues($selection, strpos($control, 'rovince') ? 'stateProvince' : 'country', TRUE);
+ if (!$options) {
+ $targetField->setAttribute('placeholder', $targetField->getAttribute('data-none-prompt'));
+ }
+ }
+ else {
+ $targetField->setAttribute('placeholder', $targetField->getAttribute('data-empty-prompt'));
+ $targetField->setAttribute('disabled', 'disabled');
+ }
+ if (!$targetField->getAttribute('multiple')) {
+ $options = array('' => $targetField->getAttribute('placeholder')) + $options;
+ $targetField->removeAttribute('placeholder');
+ }
+ $targetField->loadArray($options);
+ }
+ }
}
}
static function jqState() {
- if (empty($_GET['_value'])) {
- CRM_Utils_System::civiExit();
- }
- $countries = (array) $_GET['_value'];
- $elements = array();
- $list = &$elements;
- foreach ($countries as $val) {
- $result = CRM_Core_PseudoConstant::stateProvinceForCountry($val);
-
- // Option-groups for multiple countries
- if ($result && count($countries) > 1) {
- $elements[] = array(
- 'name' => CRM_Core_PseudoConstant::country($val, FALSE),
- 'children' => array(),
- );
- $list = &$elements[count($elements)-1]['children'];
- }
- foreach ($result as $id => $name) {
- $list[] = array(
- 'name' => $name,
- 'value' => $id,
- );
- }
- }
- $placeholder = array(array('value' => '', 'name' => $elements ? ts('- select -') : ts('- N/A -')));
- echo json_encode(array_merge($placeholder, $elements));
- CRM_Utils_System::civiExit();
+ CRM_Utils_JSON::output(CRM_Core_BAO_Location::getChainSelectValues($_GET['_value'], 'country'));
}
static function jqCounty() {
- $elements = array();
- if (!isset($_GET['_value']) || CRM_Utils_System::isNull($_GET['_value'])) {
- $elements = array(
- array('name' => ts('Choose state first'), 'value' => '')
- );
- }
- else {
- $states = (array) $_GET['_value'];
- $list = &$elements;
- foreach ($states as $val) {
- $result = CRM_Core_PseudoConstant::countyForState($val);
-
- // Option-groups for multiple countries
- if ($result && count($states) > 1) {
- $elements[] = array(
- 'name' => CRM_Core_PseudoConstant::stateProvince($val, FALSE),
- 'children' => array(),
- );
- $list = &$elements[count($elements)-1]['children'];
- }
- foreach ($result as $id => $name) {
- $list[] = array(
- 'name' => $name,
- 'value' => $id,
- );
- }
- }
- $placeholder = array(array('value' => '', 'name' => $elements ? ts('- select -') : ts('- N/A -')));
- $elements = array_merge($placeholder, $elements);
- }
-
- echo json_encode($elements);
- CRM_Utils_System::civiExit();
+ CRM_Utils_JSON::output(CRM_Core_BAO_Location::getChainSelectValues($_GET['_value'], 'stateProvince'));
}
static function getLocBlock() {
.crm-container .select2-container-multi.crm-ajax-select .select2-choices:before {
background-position: right -26px;
}
+.crm-container .select2-container-multi.loading .select2-choices:before,
+.crm-container .select2-container.loading .select2-choice .select2-arrow b {
+ background: url('../i/loading.gif') no-repeat center center;
+}
/* Reduce select2 size to match other inputs */
.crm-container .select2-container-multi .select2-choices {
min-height: 25px;
* 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
- * @param removePlaceholder bool
+ * @param placeholder string
*/
- CRM.utils.setOptions = function($el, options, removePlaceholder) {
+ CRM.utils.setOptions = function($el, options, placeholder) {
$el.each(function() {
var
$elect = $(this),
val = $elect.val() || [],
- opts = removePlaceholder ? '' : '[value!=""]';
+ multiple = $el.is('[multiple]'),
+ opts = placeholder || placeholder === '' ? '' : '[value!=""]',
+ newOptions = '',
+ theme = function(options) {
+ _.each(options, function(option) {
+ if (option.children) {
+ newOptions += '<optgroup label="' + option.value + '">';
+ theme(option.children);
+ newOptions += '</optgroup>';
+ } else {
+ var selected = ($.inArray('' + option.key, val) > -1) ? 'selected="selected"' : '';
+ newOptions += '<option value="' + option.key + '"' + selected + '>' + option.value + '</option>';
+ }
+ });
+ };
if (!$.isArray(val)) {
val = [val];
}
$elect.find('option' + opts).remove();
- _.each(options, function(option) {
- var selected = ($.inArray(''+option.key, val) > -1) ? 'selected="selected"' : '';
- $elect.append('<option value="' + option.key + '"' + selected + '>' + option.value + '</option>');
- });
+ theme(options);
+ if (typeof placeholder === 'string') {
+ if (multiple) {
+ $el.attr('placeholder', placeholder);
+ } else {
+ newOptions = '<option value="">' + placeholder + '</option>' + newOptions;
+ }
+ }
+ $elect.append(newOptions);
$elect.trigger('crmOptionsUpdated', $.extend({}, options)).trigger('change');
});
};
+ function chainSelect() {
+ var $form = $(this).closest('form'),
+ $target = $('select[data-name="' + $(this).data('target') + '"]', $form),
+ data = $target.data(),
+ val = $(this).val();
+ $target.prop('disabled', true);
+ if ($target.is('select.crm-chain-select-control')) {
+ $('select[data-name="' + $target.data('target') + '"]', $form).prop('disabled', true).blur();
+ }
+ if (!(val && val.length)) {
+ CRM.utils.setOptions($target.blur(), [], data.emptyPrompt);
+ } else {
+ $target.addClass('loading');
+ $.getJSON(CRM.url(data.callback), {_value: val}, function(vals) {
+ $target.prop('disabled', false).removeClass('loading');
+ CRM.utils.setOptions($target, vals || [], (vals && vals.length ? data.selectPrompt : data.nonePrompt));
+ });
+ }
+ }
+
/**
* Compare Form Input values against cached initial value.
*
}
$('.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);
// Cache Form Input initial values
$('form[data-warn-changes] :input', e.target).each(function() {
$(this).data('crm-initial-value', $(this).val());