-CRM.$(function($) {
- var restURL = CRM.url("civicrm/ajax/rest");
+(function($, _, undefined) {
+ var
+ entity,
+ action,
+ actions = ['get'],
+ fields = [],
+ options = {},
+ params = {},
+ smartyStub,
+ fieldTpl = _.template($('#api-param-tpl').html()),
+ optionsTpl = _.template($('#api-options-tpl').html()),
+ returnTpl = _.template($('#api-return-tpl').html()),
+ chainTpl = _.template($('#api-chain-tpl').html());
- function toggleField (name, label, type) {
- var h = '<div>\
- <label for="' + name + '">'+label+'</label>: <input name="' + name + '" data-id="'+name+ '" />\
- <a href="#" class="remove-extra" title=' + ts('Remove Field') + '>X</a>\
- </div>';
- if ( $('#extra [name=' + name + ']').length > 0) {
- $('#extra [name=' + name + ']').parent().remove();
+ /**
+ * Call prettyPrint function if it successfully loaded from the cdn
+ */
+ function prettyPrint() {
+ if (window.prettyPrint) {
+ window.prettyPrint();
}
- else {
- $('#extra').append (h);
+ }
+
+ /**
+ * Add a "fields" row
+ * @param name
+ */
+ function addField(name) {
+ $('#api-params').append($(fieldTpl({name: name || ''})));
+ var $row = $('tr:last-child', '#api-params');
+ $('.api-param-name', $row).crmSelect2({
+ data: fields.concat({id: '-', text: ts('Other') + '...'})
+ }).change();
+ }
+
+ /**
+ * Add a new "options" row
+ * @param name
+ */
+ function addOptionField(name) {
+ if ($('.api-options-row', '#api-params').length) {
+ $('.api-options-row:last', '#api-params').after($(optionsTpl({})));
+ } else {
+ $('#api-params').append($(optionsTpl({})));
}
+ var $row = $('.api-options-row:last', '#api-params');
+ $('.api-option-name', $row).crmSelect2({data: [
+ {id: 'limit', text: 'limit'},
+ {id: 'offset', text: 'offset'},
+ {id: 'sort', text: 'sort'},
+ {id: 'metadata', text: 'metadata'},
+ {id: '-', text: ts('Other') + '...'}
+ ]});
}
- function buildForm (params) {
- var h = '<label>ID</label><input data-id="id" size="3" maxlength="20" />';
- if (params.action == 'delete') {
- $('#extra').html(h);
+ /**
+ * Add an "api chain" row
+ */
+ function addChainField() {
+ $('#api-params').append($(chainTpl({})));
+ var $row = $('tr:last-child', '#api-params');
+ $('.api-chain-entity', $row).crmSelect2({
+ formatSelection: function(item) {
+ return '<span class="icon ui-icon-link"></span> API ' + item.text;
+ },
+ placeholder: '<span class="icon ui-icon-link"></span> ' + ts('Entity'),
+ escapeMarkup: function(m) {return m}
+ });
+ }
+
+ /**
+ * Fetch fields for entity+action
+ */
+ function getFields() {
+ var required = [];
+ fields = [];
+ options = {};
+ // Special case for getfields
+ if (action === 'getfields') {
+ fields.push({
+ id: 'api_action',
+ text: 'Action'
+ });
+ options.api_action = [];
+ $('option', '#api-action').each(function() {
+ if (this.value) {
+ options.api_action.push({key: this.value, value: $(this).text()});
+ }
+ });
+ showFields(['api_action']);
return;
}
-
- CRM.api(params.entity, 'getFields', {}, {
- success:function (data) {
- h = '<i>' + ts('Available fields (click to add/remove):') + '</i>';
- $.each(data.values, function(key, value) {
- var required = value.required ? " required" : "";
- h += "<a data-id='" + key + "' class='type_" + value.type + required + "'>" + value.title + "</a>";
- });
- $('#selector').html(h);
+ CRM.api3(entity, 'getFields', {'api_action': action, sequential: 1, options: {get_options: 'all'}}).done(function(data) {
+ _.each(data.values, function(field) {
+ if (field.name) {
+ fields.push({
+ id: field.name,
+ text: field.title || field.name,
+ required: field['api.required'] || false
+ });
+ if (field['api.required']) {
+ required.push(field.name);
+ }
+ if (field.options) {
+ options[field.name] = field.options;
+ }
+ }
+ });
+ showFields(required);
+ if (action === 'get' || action === 'getsingle') {
+ showReturn();
}
});
}
- function generateQuery () {
- var params = {};
- $('#api-explorer input:checkbox:checked, #api-explorer select, #extra input').each(function() {
- var val = $(this).val();
- if (val) {
- params[$(this).data('id')] = val;
- }
+ /**
+ * For "get" actions show the "return" options
+ */
+ function showReturn() {
+ $('#api-params').prepend($(returnTpl({})));
+ console.log($(returnTpl({})));
+ $('#api-return-value').crmSelect2({data: fields, multiple: true});
+ }
+
+ /**
+ * Fetch actions for entity
+ */
+ function getActions() {
+ if (entity) {
+ CRM.api3(entity, 'getactions').done(function(data) {
+ // Ensure 'get' is always an action
+ actions = _.union(['get'], data.values);
+ populateActions();
+ });
+ } else {
+ actions = ['get'];
+ populateActions();
+ }
+ }
+
+ /**
+ * Called after getActions to populate action list
+ * @param el
+ */
+ function populateActions(el) {
+ $('#api-action').select2({
+ data: _.transform(actions, function(ret, item) {ret.push({text: item, id: item})})
});
- query = CRM.url("civicrm/ajax/rest", params);
- $('#query').val(query);
- if (params.action == 'delete' && $('#selector a').length == 0) {
- buildForm (params);
- return;
+ }
+
+ /**
+ * Called after getfields to show buttons and required fields
+ * @param required
+ */
+ function showFields(required) {
+ $('#api-params').empty();
+ $('#api-param-buttons').show();
+ if (required.length) {
+ _.each(required, addField);
+ } else {
+ addField();
}
- if (params.action == 'create' && $('#selector a').length == 0) {
- buildForm (params);
- return;
+ }
+
+ /**
+ * Add/remove option list for selected field's pseudoconstant
+ */
+ function toggleOptions() {
+ var name = $(this).val(),
+ $valField = $(this).closest('tr').find('.api-param-value');
+ if (options[name]) {
+ $valField.val('').select2({
+ multiple: true,
+ data: _.transform(options[name], function(result, option) {
+ result.push({id: option.key, text: option.value});
+ })
+ });
+ }
+ else if ($valField.data('select2')) {
+ $valField.select2('destroy');
}
}
- function runQuery() {
- var vars = [],
- hash,
- smarty = '',
- php = "$params = array(<br /> 'version' => 3,",
- json = "{",
- link = "",
- key,
- value,
- entity,
- action,
- query = $('#query').val(),
- hashes = query.slice(query.indexOf('?') + 1).split('&');
- for(var i = 0; i < hashes.length; i++) {
- hash = hashes[i].split('=');
- key = hash[0];
- value = hash[1];
-
- switch (key) {
- case 'version':
- case 'debug':
- case 'json':
- break;
- case 'action':
- action = value.toLowerCase();
- $('#action').val(action);
- break;
- case 'entity':
- entity = value.charAt(0).toUpperCase() + value.substr(1);
- $('#entity').val(entity);
- break;
- default:
- if (typeof value == 'undefined') {
- break;
- }
- value = isNaN(value) ? "'" + value + "'" : value;
- smarty += ' ' + key + '=' + value;
- php += "<br />  '" + key +"' => " + value + ",";
- json += "'" + key + "': " + value + ", ";
+ /**
+ * Attempt to parse a string into a value of the intended type
+ * @param val
+ */
+ function evaluate(val, makeArray) {
+ try {
+ if (!val.length) {
+ return val;
}
+ var first = val.charAt(0),
+ last = val.slice(-1);
+ // Simple types
+ if (val === 'true' || val === 'false' || val === 'null' || !isNaN(val)) {
+ return eval(val);
+ }
+ // Quoted strings
+ if ((first === '"' || first === "'") && last === first) {
+ return val.slice(1, -1);
+ }
+ // Parse json
+ if ((first === '[' && last === ']') || (first === '{' && last === '}')) {
+ return eval('(' + val + ')');
+ }
+ // Transform csv to array
+ if (makeArray && val.indexOf(',') > 0) {
+ return val.split(',');
+ }
+ // Ok ok it's really a string
+ return val;
+ } catch(e) {
+ // If eval crashed return undefined
+ return undefined;
}
+ }
- if (!entity) {
- $('#query').val(ts('Choose an entity.'));
- $('#entity').val('');
- window.location.hash = 'explorer';
- return;
+ /**
+ * Format value to look like php code
+ * @param val
+ */
+ function phpFormat(val) {
+ var ret = '';
+ if ($.isPlainObject(val)) {
+ $.each(val, function(k, v) {
+ ret += (ret ? ', ' : '') + "'" + k + "' => " + phpFormat(v);
+ });
+ return 'array(' + ret + ')';
}
- if (!action) {
- $('#query').val(ts('Choose an action.'));
- $('#action').val('');
- window.location.hash = 'explorer';
- return;
+ if ($.isArray(val)) {
+ $.each(val, function(k, v) {
+ ret += (ret ? ', ' : '') + phpFormat(v);
+ });
+ return 'array(' + ret + ')';
}
+ return JSON.stringify(val);
+ }
- window.location.hash = query;
- $('#result').block();
- $.post(query,function(data) {
- $('#result').unblock().text(data);
- },'text');
- link="<a href='"+query+"' title='open in a new tab' target='_blank'>ajax query</a> ";
- var RESTquery = CRM.config.resourceBase + "extern/rest.php?"+ query.substring(restURL.length,query.length) + "&api_key={yoursitekey}&key={yourkey}";
- $("#link").html(link+"|<a href='"+RESTquery+"' title='open in a new tab' target='_blank'>REST query</a>.");
+ /**
+ * Smarty doesn't support array literals so we provide a stub
+ * @param js string
+ */
+ function smartyFormat(js, key) {
+ if (js.indexOf('[') > -1 || js.indexOf('{') > -1) {
+ smartyStub = true;
+ return '$' + key.replace(/[. -]/g, '_');
+ }
+ return js;
+ }
+ /**
+ * Create the params array from user input
+ * @param e
+ */
+ function buildParams(e) {
+ params = {};
+ $('.api-param-checkbox:checked').each(function() {
+ params[this.name] = 1;
+ });
+ $('input.api-param-value, input.api-option-value').each(function() {
+ var $row = $(this).closest('tr'),
+ val = evaluate($(this).val(), $(this).is('.select2-offscreen')),
+ name = $('input.api-param-name', $row).val(),
+ op = $('select.api-param-op', $row).val() || '=';
- json = (json.length > 1 ? json.slice (0,-2) : '{') + '}';
- php += "<br />);<br />";
- $('#php').html(php + "$result = civicrm_api('" + entity + "', '" + action + "', $params);");
- $('#jQuery').html ("CRM.api('"+entity+"', '"+action+"', "+json+",<br /> {success: function(data) {<br /> cj.each(data, function(key, value) {// do something });<br /> }<br /> }<br />);");
+ // Ignore blank values for the return field
+ if ($(this).is('#api-return-value') && !val) {
+ return;
+ }
+ // Special syntax for api chaining
+ if (!name && $('select.api-chain-entity', $row).val()) {
+ name = 'api.' + $('select.api-chain-entity', $row).val() + '.' + $('select.api-chain-action', $row).val();
+ }
+ // Special handling for options
+ if ($(this).is('.api-option-value')) {
+ op = $('input.api-option-name', $row).val();
+ if (op) {
+ name = 'options';
+ }
+ }
+ if (name && val !== undefined) {
+ params[name] = op === '=' ? val : (params[name] || {});
+ if (op !== '=') {
+ params[name][op] = val;
+ }
+ clearError(this);
+ }
+ else if (name && (!e || e.type !== 'keyup')) {
+ setError(this);
+ }
+ });
+ if (entity && action) {
+ formatQuery();
+ }
+ }
- if (action.substring(0, 3) == "get") {//using smarty only make sense for get actions
- $('#smarty').html("{crmAPI var='result' entity='" + entity + "' action='" + action + "' " + smarty + '}<br />{foreach from=$result.values item=' + entity + '}<br/> <li>{$' + entity +'.some_field}</li><br />{/foreach}');
- } else {
- $('#smarty').html("smarty uses only 'get' actions");
+ function setError(el) {
+ if (!$(el).hasClass('crm-error')) {
+ $(el)
+ .addClass('crm-error')
+ .css('width', '82%')
+ .attr('title', ts('Syntax error'))
+ .before('<div class="icon red-icon ui-icon-alert"/>');
}
- $('#generated').show();
}
- var query = window.location.hash;
- if (query.substring(1, restURL.length + 1) === restURL) {
- $('#query').val (query.substring(1)).focus();
- runQuery();
- } else {
- window.location.hash="explorer"; //to be sure to display the result under the generated code in the viewport
+ function clearError(el) {
+ $(el)
+ .removeClass('crm-error')
+ .attr('title', '')
+ .css('width', '85%')
+ .siblings('.ui-icon-alert').remove();
}
- $('#entity, #action').change (function() {
- $("#selector, #extra").empty();
- generateQuery();
- runQuery();
- });
- $('#api-explorer input:checkbox').change(function() {
- generateQuery(); runQuery();
- });
- $('#api-explorer').submit(function(e) {
+
+ function formatQuery() {
+ var i = 0, q = {
+ smarty: "{crmAPI var='result' entity='" + entity + "' action='" + action + "'",
+ php: "$result = civicrm_api3('" + entity + "', '" + action + "'",
+ json: "CRM.api3('" + entity + "', '" + action + "'",
+ rest: CRM.config.resourceBase + "extern/rest.php?entity=" + entity + "&action=" + action + "&json=" + JSON.stringify(params) + "&api_key=yoursitekey&key=yourkey"
+ };
+ smartyStub = false;
+ $.each(params, function(key, value) {
+ var js = JSON.stringify(value);
+ if (!i++) {
+ q.php += ", array(\n";
+ q.json += ", {\n";
+ } else {
+ q.json += ",\n";
+ }
+ q.php += " '" + key + "' => " + phpFormat(value) + ",\n";
+ q.json += " \"" + key + '": ' + js;
+ q.smarty += ' ' + key + '=' + smartyFormat(js, key);
+ });
+ if (i) {
+ q.php += ")";
+ q.json += "\n}";
+ }
+ q.php += ");";
+ q.json += ").done(function(result) {\n // do something\n});";
+ q.smarty += "}\n{foreach from=$result.values item=" + entity.toLowerCase() + "}\n {$" + entity.toLowerCase() + ".some_field}\n{/foreach}";
+ if (action.indexOf('get') < 0) {
+ q.smarty = '{* Smarty API only works with get actions *}';
+ } else if (smartyStub) {
+ q.smarty = "{* Smarty does not have a syntax for array literals; assign complex variables on the server *}\n" + q.smarty;
+ }
+ $.each(q, function(type, val) {
+ $('#api-' + type).removeClass('prettyprinted').text(val);
+ });
+ prettyPrint();
+ }
+
+ function submit(e) {
e.preventDefault();
- runQuery();
- });
- $('#extra').on('keyup', 'input', generateQuery);
- $('#extra').on('click', 'a.remove-extra', function() {
- $(this).parent().remove();
- generateQuery();
- });
- $('#selector').on('click', 'a', function() {
- toggleField($(this).data('id'), this.innerHTML, this.class);
+ if (!entity || !action) {
+ alert(ts('Select an entity.'));
+ return;
+ }
+ if (action.indexOf('get') < 0) {
+ var msg = action === 'delete' ? ts('This will delete data from CiviCRM. Are you sure?') : ts('This will write to the database. Continue?');
+ CRM.confirm({title: ts('Confirm %1', {1: action}), message: msg}).on('crmConfirm:yes', execute);
+ } else {
+ execute();
+ }
+ }
+
+ function execute() {
+ $('#api-result').html('<div class="crm-loading-element"></div>');
+ $.ajax({
+ url: CRM.url('civicrm/ajax/rest'),
+ data: {
+ entity: entity,
+ action: action,
+ prettyprint: 1,
+ json: JSON.stringify(params)
+ },
+ type: action.indexOf('get') < 0 ? 'POST' : 'GET',
+ dataType: 'text'
+ }).done(function(text) {
+ $('#api-result').addClass('prettyprint').removeClass('prettyprinted').text(text);
+ prettyPrint();
+ });
+ }
+
+ $(document).ready(function() {
+ $('form#api-explorer')
+ .on('change', '#api-entity, #api-action', function() {
+ entity = $('#api-entity').val();
+ if ($(this).is('#api-entity')) {
+ $('#api-action').select2('val', 'get');
+ getActions();
+ }
+ action = $('#api-action').val();
+ if (entity && action) {
+ $('#api-params').html('<tr><td colspan="4" class="crm-loading-element"></td></tr>');
+ $('#api-params-table thead').show();
+ getFields();
+ buildParams();
+ } else {
+ $('#api-params, #api-generated pre').empty();
+ $('#api-param-buttons, #api-params-table thead').hide();
+ }
+ })
+ .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() {
+ if ($(this).val() === '-') {
+ $(this).select2('destroy');
+ $(this).val('').focus();
+ }
+ })
+ .on('click', '.api-param-remove', function(e) {
+ e.preventDefault();
+ $(this).closest('tr').remove();
+ buildParams();
+ });
+ $('#api-params-add').on('click', function(e) {
+ e.preventDefault();
+ addField();
+ });
+ $('#api-option-add').on('click', function(e) {
+ e.preventDefault();
+ addOptionField();
+ });
+ $('#api-chain-add').on('click', function(e) {
+ e.preventDefault();
+ addChainField();
+ });
+ $('#api-entity').change();
});
-});
+}(CRM.$, CRM._));