From 77099ee0f0fac9999a8e7a7746f06fd3a71e6032 Mon Sep 17 00:00:00 2001
From: Coleman Watts
Date: Thu, 9 Oct 2014 01:45:14 -0400
Subject: [PATCH] Improve API explorer handling of operators
---
templates/CRM/Admin/Page/APIExplorer.hlp | 5 +-
templates/CRM/Admin/Page/APIExplorer.js | 81 ++++++++++++++++++------
2 files changed, 63 insertions(+), 23 deletions(-)
diff --git a/templates/CRM/Admin/Page/APIExplorer.hlp b/templates/CRM/Admin/Page/APIExplorer.hlp
index 9346899600..fcdce48057 100644
--- a/templates/CRM/Admin/Page/APIExplorer.hlp
+++ b/templates/CRM/Admin/Page/APIExplorer.hlp
@@ -53,8 +53,9 @@
- {ts}Simple values such as numbers, true, false and null.{/ts}
- {ts}Strings. You may include quotes around the string to ensure it's not interpreted otherwise, e.g. "false".{/ts}
- - {ts}Arrays should be in js format, e.g. [1, 2, 3, 'foo', 'bar', 'baz'].{/ts}
- - {ts}Associative arrays should be in js object format, e.g. {ldelim}first: "one", second: "two", third: 3{rdelim}.{/ts}
+ - {ts}Arrays in js format, e.g. [1, 2, 3, 'foo', 'bar', 'baz'].{/ts}
+ - {ts}Associative arrays in js object format, e.g. {ldelim}first: "one", second: "two", third: 3{rdelim}.{/ts}
+ - {ts}A comma-separated list will automatically be turned into an array for multivalue operations, or you can explicitly use the js array format above.{/ts}
{/htxt}
diff --git a/templates/CRM/Admin/Page/APIExplorer.js b/templates/CRM/Admin/Page/APIExplorer.js
index 2063c2a62f..e530b27a49 100644
--- a/templates/CRM/Admin/Page/APIExplorer.js
+++ b/templates/CRM/Admin/Page/APIExplorer.js
@@ -10,7 +10,12 @@
fieldTpl = _.template($('#api-param-tpl').html()),
optionsTpl = _.template($('#api-options-tpl').html()),
returnTpl = _.template($('#api-return-tpl').html()),
- chainTpl = _.template($('#api-chain-tpl').html());
+ chainTpl = _.template($('#api-chain-tpl').html()),
+
+ // Operators with special properties
+ BOOL = ['IS NULL', 'IS NOT NULL'],
+ TEXT = ['LIKE', 'NOT LIKE'],
+ MULTI = ['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'];
/**
* Call prettyPrint function if it successfully loaded from the cdn
@@ -28,7 +33,7 @@
function addField(name) {
$('#api-params').append($(fieldTpl({name: name || ''})));
var $row = $('tr:last-child', '#api-params');
- $('.api-param-name', $row).crmSelect2({
+ $('input.api-param-name', $row).crmSelect2({
data: fields.concat({id: '-', text: ts('Other') + '...'})
}).change();
}
@@ -167,20 +172,45 @@
}
/**
- * Add/remove option list for selected field's pseudoconstant
+ * Render value input as a textfield, option list, or hidden,
+ * Depending on selected param name and operator
*/
- function toggleOptions() {
- var name = $(this).val(),
- $valField = $(this).closest('tr').find('.api-param-value');
- if (options[name]) {
- $valField.val('').select2({
- multiple: true,
+ function renderValueField() {
+ var $row = $(this).closest('tr'),
+ name = $('input.api-param-name', $row).val(),
+ operator = $('.api-param-op', $row).val(),
+ operatorType = $.inArray(operator, MULTI) > -1 ? 'multi' : ($.inArray(operator, BOOL) > -1 ? 'bool' : 'single'),
+ $valField = $('input.api-param-value', $row),
+ currentVal = $valField.val();
+ // Boolean fields only have 1 possible value
+ if (operatorType == 'bool') {
+ if ($valField.data('select2')) {
+ $valField.select2('destroy');
+ }
+ $valField.css('visibility', 'hidden').val('1');
+ return;
+ }
+ $valField.css('visibility', '');
+ // Option list input
+ if (options[name] && $.inArray(operator, TEXT) < 0) {
+ // Reset value before switching to a select from something else
+ if ($(this).is('.api-param-name') || !$valField.data('select2')) {
+ $valField.val('');
+ }
+ // When switching from multi-select to single select
+ else if (operatorType == 'single' && currentVal.indexOf(',') > -1) {
+ $valField.val(currentVal.split(',')[0]);
+ }
+ $valField.select2({
+ multiple: (operatorType === 'multi'),
data: _.transform(options[name], function(result, option) {
result.push({id: option.key, text: option.value});
})
});
+ return;
}
- else if ($valField.data('select2')) {
+ // Plain text input
+ if ($valField.data('select2')) {
$valField.select2('destroy');
}
}
@@ -193,7 +223,7 @@
function evaluate(val, makeArray) {
try {
if (!val.length) {
- return val;
+ return makeArray ? [] : '';
}
var first = val.charAt(0),
last = val.slice(-1);
@@ -201,10 +231,6 @@
if (val === 'true' || val === 'false' || val === 'null') {
return eval(val);
}
- // Integers - quote any number that starts with 0 to avoid oddities
- if (!isNaN(val) && val.search(/[^\d]/) < 0 && (val.length === 1 || first !== '0')) {
- return parseInt(val, 10);
- }
// Quoted strings
if ((first === '"' || first === "'") && last === first) {
return val.slice(1, -1);
@@ -214,8 +240,16 @@
return eval('(' + val + ')');
}
// Transform csv to array
- if (makeArray && val.indexOf(',') > 0) {
- return val.split(',');
+ if (makeArray) {
+ var result = [];
+ $.each(val.split(','), function(k, v) {
+ result.push(evaluate($.trim(v)) || v);
+ });
+ return result;
+ }
+ // Integers - quote any number that starts with 0 to avoid oddities
+ if (!isNaN(val) && val.search(/[^\d]/) < 0 && (val.length === 1 || first !== '0')) {
+ return parseInt(val, 10);
}
// Ok ok it's really a string
return val;
@@ -269,9 +303,9 @@
});
$('input.api-param-value, input.api-option-value').each(function() {
var $row = $(this).closest('tr'),
- val = evaluate($(this).val(), $(this).is('.select2-offscreen')),
+ op = $('select.api-param-op', $row).val() || '=',
name = $('input.api-param-name', $row).val(),
- op = $('select.api-param-op', $row).val() || '=';
+ val = evaluate($(this).val(), $.inArray(op, MULTI) > -1);
// Ignore blank values for the return field
if ($(this).is('#api-return-value') && !val) {
@@ -348,6 +382,7 @@
q.php += " '" + key + "' => " + phpFormat(value) + ",\n";
q.json += " \"" + key + '": ' + js;
q.smarty += ' ' + key + '=' + smartyFormat(js, key);
+ // FIXME: This is not totally correct cli syntax
q.drush += key + '=' + js + ' ';
q.wpcli += key + '=' + js + ' ';
});
@@ -383,6 +418,10 @@
}
}
+ /**
+ * Execute api call and display the results
+ * Note: We have to manually execute the ajax in order to add the secret extra "prettyprint" param
+ */
function execute() {
$('#api-result').html('');
$.ajax({
@@ -422,8 +461,8 @@
.on('change keyup', 'input.api-input, #api-params select', buildParams)
.on('submit', submit);
$('#api-params')
- .on('change', '.api-param-name', toggleOptions)
- .on('change', '.api-param-name, .api-option-name', function() {
+ .on('change', 'input.api-param-name, select.api-param-op', renderValueField)
+ .on('change', 'input.api-param-name, .api-option-name', function() {
if ($(this).val() === '-') {
$(this).select2('destroy');
$(this).val('').focus();
--
2.25.1