Skip to content

Instantly share code, notes, and snippets.

@larscwallin
Created August 4, 2011 12:28
Show Gist options
  • Save larscwallin/1125061 to your computer and use it in GitHub Desktop.
Save larscwallin/1125061 to your computer and use it in GitHub Desktop.
SIMPLX RPC Snippet - Version 0.6.7
<script type="text/javascript">
var simplx = new Object();
simplx.jsonrpc = (function(){
var response = null;
var self = this;
this.url="";
this.host="";
this.path="";
this.pageid="";
this.parametername="";
this.rpc={
jsonrpc:"2.0",
id:"1",
method:"",
params:{}
};
this.callback=null;
this.errorcallback=null;
this.init = function(options){
// Legacy properties to be removed
self.url = options.url || (document.location.href.substring(0,document.location.href.indexOf("/",7)+1));
self.pageid = ("?id=" + options.pageid) || "";
//-----------------------
console.log(self.url);
self.host = options.host || self.url;
self.service = options.service || self.pageid;
self.parametername = options.parametername || "";
self.rpc= options.rpc || self.rpc;
self.callback=options.callback || self.callback;
self.errorcallback=options.errorcallback || self.errorcallback;
};
this.execute = function(){
var data = "";
var completeUrl = (self.host + "" + self.service);
var rpc = JSON.stringify(self.rpc);
response = $.ajax({
type:"POST",
url:completeUrl,
data:rpc,
contentType:"application/json-rpc",
success:function(data){
if(data){
try{
data = JSON.parse(data);
}catch(e){
// The data might just have been cast to Array already as the response type is application/json
}
if(!data.error){
self.callback(data);
return true;
}else{
if(self.errorcallback){
self.errorcallback(data);
}
return false;
}
}
}
});
}
});
</script>
<?php
/*
SIMPLX RPC - by Lars C Wallin :)
Version 0.6.7 - 120125
* WHATS THIS SNIPPET ANYWAY?
SIMPLX RPC is a remote method invocation Snippet which makes it possible to call the MODx API from the client browser using XMLHTTP or similar. This means that you can fetch (for now...) data from ModX without reloading the page. This means more interactive, dynamic, responsive, personal web sites :)
The RPC (Remote Procedure Call) capability has been missing for too long in MODx i think. Well it turned out to be a real piece of cake to code it (much thanks to the simplicity of the ModX API but also thanks to the built in support for json in PHP). So here you are ;)
* WHY DID I MAKE THE SNIPPET?
Well because it was the simplest and most plug-n-play way to do it. Using Modx Connectors would in some cases be a better options, but it takes some reading and hacking to get it working.
This Snippet is a really clean and easy to use solution for your ajax needs :)
Also, what you get out-of-the-box by making it a Snippet is the ability to have access control over the remote api using the built in ModX security (as the remote service is just a document in ModX).
* INSTALLING
Real easy. Get it using the Package Management, or, just paste the Snippet code in a new Snippet and name it SIMPLX_RPC. Then make a new template and paste in only the empty call to this snippet WITH CACHING TURNED OFF ([[!SIMPLX_RPC]]).
The Snippet will now read the HTTP GET/POST and do all the magic, including returning json data.
* USING THE SNIPPET
All modern languages, not only limited to web oriented ones, support json. To start with, as i guess you will be using the
SIMPLX RPC for web stuff first of all, I would suggest using the terrific JQuery tmpl plugin.
Example of rpc call:
http://www.myurl.com/?id=1&simplxrpc={"id":"1","method":"modx.getpageinfo","params":{"pageid":"3","property":"pagetitle"}"}
I get back:
{"jsonrpc":"2.0","id":"1","result":{"pagetitle":"Illustration","pageid":"3","typename":"document"}}
Note that the "?id=1" part is really the id of the document (resource) in ModX to which you assigned the template with the Snippet call.
--! Note that all MODx specific parameters such as names of parameters and Snippets are CASE SENSITIVE !---
As of version 0.5 the Snippet supports what i call "Snippet Matching". This makes it very easy to map json-rpc calls to Snippets by name. Look at the following example:
*/
/*
Set the response Content-Type to application/json
*/
header('Content-Type: application/json-rpc');
function getRequestObject($debugmode){
global $modx;
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'POST':
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC.getRequestObject(): HTTP Request was POST.');
// If the POST collection appears to be empty, get the raw body.
if(!$_POST){
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC.getRequestObject(): The POST collection was not set. This most likely means that the JSON data is embedded in the HTTP body.');
$post_body = file_get_contents('php://input');
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC.getRequestObject(): The HTTP body contains "'.$post_body.'".');
$_POST['jsonrpc'] = $post_body;
}
return $_POST;
break;
case 'GET':
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC.getRequestObject(): HTTP Request was GET.'); if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC.getRequestObject(): The HTTP body contains "'.$post_body.'".');
return $_GET;
break;
default:
return null;
break;
}
}
$debugmode = isset($debugmode) ? $debugmode: true;
if($debugmode){
$modx->setLogLevel(modX::LOG_LEVEL_DEBUG);
}
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Starting processing.');
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Getting the HTTP Request object.');
/*
Get the request object POST/GET
*/
$request_object = getRequestObject($debugmode);
$message = '';
if(!$request_object){
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: Error getting SimplxRequest object.');
return false;
}
/*
Check request validity
*/
if(!$request_object){
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"code": -32600, "message": "The received JSON is not a valid JSON-RPC Request."}, "id":null}.');
print '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "The received JSON is not a valid JSON-RPC Request."}, "id":null}';
die();
}
/*
Init the ass. arrays with the Request data. This data is expected to conform to the JSON-RPC spec.
If
*/
if($simplx_rpc_parameter_name != '' && (stripos($_SERVER['CONTENT_TYPE'], 'application/json-rpc') > 0)){
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Using simplx_rpc_parameter_name "'.$simplx_rpc_parameter_name.'" to look up JSON RPC message.');
$message = $request_object[$simplx_rpc_parameter_name];
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: JSON-RPC message contains "'.$message.'".');
if($message){
$simplx_rpc = json_decode($message,true);
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error. Unable to find the JSON-RPC message."}, "id": null}.');
print '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error. Unable to find the JSON-RPC message."}, "id": null}';
die();
}
}else{
$message = $request_object['jsonrpc'];
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: JSON-RPC message contains "'.$message.'".');
if($message){
$simplx_rpc = json_decode($message,true);
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error. Unable to find the JSON-RPC message."}, "id": null}.');
print '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error. Unable to find the JSON-RPC message."}, "id": null}';
die();
}
}
/*
We have to check if we really could decode the string.
*/
if(!$simplx_rpc){
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error. Unable to decode JSON-RPC message."}, "id": null}.');
print '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error. Unable to decode JSON-RPC message."}, "id": null}';
die();
}
/*
Check so that we really have an json-rpc request
*/
if(!$simplx_rpc["jsonrpc"]){
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"jsonrpc": "2.0", "error": {"code": -32600, "message": "The received JSON is not a valid JSON-RPC Request."}, "id": "'.$simplx_rpc["id"].'"}.');
print '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "The received JSON is not a valid JSON-RPC Request."}, "id": "'.$simplx_rpc["id"].'"}';
die();
}
$simplx_rpc_params = $simplx_rpc["params"];
$simplx_rpc_response = array();
$simplx_rpc_method_result = array();
/*
Set neccessary, mandatory, values
*/
$simplx_rpc_response["jsonrpc"] = "2.0";
$simplx_rpc_response["id"] = isset($simplx_rpc["id"]) ? $simplx_rpc["id"] : "";
/*
If the "usesnippetmatching" get parameter is not set we use the default property set.
*/
$simplx_rpc_use_snippet_matching = isset($request_object["usesnippetmatching"]) ? $request_object["usesnippetmatching"] : $simplx_rpc_use_snippet_matching;
/*
From version 0.5 simplx_rpc can match the method name to Snippets automagically.
This requires either,
- that the default property "simplx_rpc_use_snippet_matching" is set to 1
- Or that the usesnippetmatching url parameter is set to 1
*/
if($simplx_rpc_use_snippet_matching < 1){
}else{
/*
Snippet matching is turned on. Lets see if we can match the method name to a snippet
*/
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Method/Snippet to call "'.$simplx_rpc["method"].'".');
if($modx->getObject('modSnippet',array('name'=>$simplx_rpc["method"]))){
try{
$snippet_output = $modx->runSnippet($simplx_rpc["method"],$simplx_rpc_params);
if($snippet_output === true){
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Method/Snippet "'.$simplx_rpc["method"].'" returned true.');
/*
True was returned from the Snippet call.
*/
$simplx_rpc_response["result"] = 'false';
print json_encode($simplx_rpc_response);
}elseif($snippet_output === false){
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Method/Snippet "'.$simplx_rpc["method"].'" returned false.');
/*
False was returned from the Snippet call.
*/
$simplx_rpc_response["result"] = 'false';
print json_encode($simplx_rpc_response);
}else{
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Method/Snippet "'.$simplx_rpc["method"].'" returned "'.$snippet_output.'".');
/*
Ok seems that we got something to show.
First we need to check if we got a json object representation as output. If so
we need to decode it back to an object to get a correct json syntax final response.
*/
$snippet_output_temp = json_decode($snippet_output);
/*
If $snippet_output_temp is null, the decoding did not work and we can assume we have
a simple text output.
*/
$snippet_output = is_null($snippet_output_temp) ? $snippet_output : $snippet_output_temp;
$simplx_rpc_response["result"] = $snippet_output;
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: Sending response "'.json_encode($simplx_rpc_response).'".');
print json_encode($simplx_rpc_response);
}
}catch(Exception $e){
/*
Oops something bugged out when making the runSnippet call. The php Exception object is
sent as result.
*/
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"jsonrpc": "2.0", "error": {"code": -32603, "message": "'.$e->getMessage().'"}, "id": "'.$simplx_rpc_response["id"].'"}');
print '{"jsonrpc": "2.0", "error": {"code": -32603, "message": "'.$e->getMessage().'"}, "id": "'.$simplx_rpc_response["id"].'"}';
}
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'SIMPLX_RPC: {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Procedure not found."}, "id": "'.$simplx_rpc_response["id"].'"}');
print '{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Procedure not found."}, "id": "'.$simplx_rpc_response["id"].'"}';
}
}
/*
Abort the MODx Request handling. By doing this we prevent MODx from forcing the Content-Type header to text/html.
This can be avoided also by using the Resource specific settings.
*/
if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'SIMPLX_RPC: All done :) Abort the MODx Request handling. By doing this we prevent MODx from forcing the Content-Type header to "text/html".');
die();
/*
These are the error codes defined in the JSON-RPC standard
-32700 Parse error. Invalid JSON. An error occurred on the server while parsing the JSON text.
-32600 Invalid Request. The received JSON is not a valid JSON-RPC Request.
-32601 Method not found. The requested remote-procedure does not exist / is not available.
-32602 Invalid params. Invalid method parameters.
-32603 Internal error. Internal JSON-RPC error.
-32099..-32000 Server error. Reserved for implementation-defined server-errors.
*/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> SIMPLX RPC </title>
<meta name="Generator" content="EditPlus">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<!--
Below i load all the nice jQuery scripts plus Doug Crockfords json2.js
-->
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/smoothness/jquery-ui.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js"></script>
<!-- script src="https://github.com/douglascrockford/JSON-js/raw/master/json2.js"></script -->
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.8/jquery-ui.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
<!--
This is the simplxRPC object to help you along your way. You dont need to use this,
a simplx json-rpc call is just a json string sent as a querystring.
But i thought you might find it convenient with a wrapper :)
-->
[[$simplx.jsonrpc.js]]
<script>
/*
jQuery will execute this function when it considers the document to be fully
loaded.
*/
$(document).ready(function(){
// Set up the template object
$.template("modDocument", $("#modDocumentTemplate"));
// This function is sent as a callback parameter in the config.
function parseResponse(jsobject){
try{
$.tmpl("modDocument",jsobject.result)
.appendTo("#rpc_result" );
$('#json_source').html(JSON.stringify(jsobject));
}catch(e){
// Probably empty response...
alert('Exception ' + e.description);
}
// Lets show the result
$('#accordion').accordion('activate',0);
}
// This function is sent as a error callback parameter in the config.
function parseErrorResponse(jsobject){
try{
$.tmpl("modDocument",jsobject.result)
.appendTo("#rpc_result" );
$('#json_source').html(JSON.stringify(jsobject));
}catch(e){
// Probably empty response...
alert('Exception ' + e.description);
}
// Lets show the json response/error message
$('#accordion').accordion('activate',3);
}
// Here is a config object to set up the rpc call.
var rpcOptions = {
"host":"http://mysite.com/",
"service":"?id=2",
"rpc":{
"jsonrpc":"2.0",
"id":"1",
"method":"getResources",
"params":{
"parents":"0",
"tpl":"getResources_json_item",
"tplFirst":"getResources_json_first",
"tplLast":"getResources_json_last",
"outputSeparator":","
}
}};
// Bind the click event of the button to an anonymous function.
$('#apply_changes').click(function(){
$("#rpc_result").html('');
// Parse the value in the "query_source" text area. This returns a json object.
rpcOptions = JSON.parse($('#query_source').val());
// Set up the jQuery template using the value of the "template_source" text area.
$.template( "modDocument", $('#template_source').val());
// Create and init the simplx.jsonrpc helper object.
simplxRPCObject = new simplx.jsonrpc();
simplxRPCObject.init(rpcOptions); // Call the init method passing the rpcOptions json
simplxRPCObject.callback = parseResponse; // Assign the function reference for success responses
simplxRPCObject.errorcallback=parseErrorResponse; // Assign the function reference for error responses
simplxRPCObject.execute(); // Now lets fire away the query.
});
// Initial set up of the text areas and template
$('#query_source').html(JSON.stringify(rpcOptions));
$('#template_source').html($('#modDocumentTemplate').html());
// Set up the jQuery "console" dialog
$('#result_dialog').dialog(
{
title: 'Example RPC call result',
width:400,
height:500,
position:'right'
});
$('#accordion').accordion();
$('#accordion').accordion('activate',1);
});
</script>
<style>
body {
font-family:sans-serif;
background-color:#eee;
font-size:12px;
}
#content {
width:650px;
margin-left:15%;
}
#result_dialog{
font-size:11px;
width:400px;
}
#result_dialog textarea {
width:100%;
height:200px;
}
#dialog_descr
{
padding:5px;
}
</style>
</head>
<body>
<!-- The result of the call is rendered here -->
<div id="result_dialog">
<div id="dialog_descr">
Hi! I am a super fancy interactive console! Read through the docs and play with me afterwards :)
</div>
<div id="accordion">
<h3><a href="#">Rendered Result</a></h3>
<div id="rpc_result"></div>
<h3><a href="#">Query Config</a></h3>
<div><textarea id="query_source"></textarea>
<!-- <br/>
Use Snippet Matching?
<br/>
<input type="checkbox" value="on"/>
-->
</div>
<h3><a href="#">Template</a></h3>
<div><textarea id="template_source"></textarea></div>
<h3><a href="#">JSON Result</a></h3>
<div><textarea disabled id="json_source"></textarea></div>
</div>
<hr/ noshade>
<input type="button" id="apply_changes" value="Apply"/>
</div>
<div id="content">
<h1>Welcome to the SIMPLX RPC how-to!</h1>
<p class="justifyleft"><span class="php-comment">SIMPLX RPC - by Lars C Wallin </span><br />
<span class="php-comment">Version 0.6.7 - 110912</span><br /><br />
<p><strong>WHATS THIS SNIPPET ANYWAY?</strong></p>
<p>SIMPLX RPC is a remote method invocation Snippet which makes it possible to call the ModX API from the client browser using XMLHTTP or similar. This means that you can fetch (for now...) data from ModX without reloading the page. This means more interactive, dynamic, responsive, personal web sites :)</p>
<p>The RPC (Remote Procedure Call) capability has been missing for too long in ModX i think. Well it turned out to be a real piece of cake to code it (much thanks to the simplicity of the ModX API but also thanks to the built in support for json in PHP). So here you are ;)</p>
<br/>
<p><strong>WHY DID I MAKE THE SNIPPET?</strong></p>
<p>Well because it was the simplest and most plug-n-play way to do it. Using Modx Connectors/Processors would in some cases be a better options, but it takes some reading and hacking to get it working.&nbsp;</p>
<p>This Snippet is a really clean and easy to use solution for your ajax needs :)</p>
<p>Also, what you get out-of-the-box by making it a Snippet is the ability to have access control over the remote api using the built in ModX security (as the remote service is just a document in ModX).</p>
<ul>
</ul>
<br/>
<p><strong><span style="font-weight: normal;">&nbsp;</span></strong><strong><strong>INSTALLING</strong></strong></p>
<p>Real easy. Get it using the Package Management (recommended!), or, &nbsp;just paste the Snippet code in a new Snippet and name it SIMPLX_RPC. Then make a new template and paste in only the empty call to this snippet WITH CACHING TURNED OFF.</p>
<p>The Snippet will now read the HTTP GET/POST request and do all the magic, including returning json data.</p>
<p><em>To get the Snippet working please, </em></p>
<ul>
<li>create one page which uses the SIMPLX RPC template. This is the page which will act as a access point for all RPC calls. Remember to set appropriate access permissions for this page.&nbsp;<br /><br /></li>
<li>create another page using the SIMPLX RPC EXAMPLE template. This example template has some really nice sample/debug features!<br /><br /></li>
<li>Test out the SIMPLX_RPC_EXAMPLE Snippet which shows you how to query getResources. &nbsp;</li>
</ul>
<br/>
<p><strong>THE CONSOLE</strong></p>
<p>The console on the example page is really cool :D&nbsp;</p>
<p>It lets you make test calls to your site and try out templating etc.&nbsp;</p>
<ul>
<li><em>The "Query Config" tab</em> <br />
Shows the current json config object. To start using your MODx site to get "live" data, set these params to match your setup:
<br/>host (probably your site's base url) ,
<br/>service (path to the Resource which uses the SIMPLX_RPC template) and
<br/>rpc (the actual json-rpc method call), where method is your Snippet and params is the Snippets params.
<br />
<br/>
On my site my live test-example Query looking like this:
<br/>
<pre>
{
"host":"http://myhostname.com/",
"service":"?id=2",
"rpc":{
"jsonrpc":"2.0",
"id":"1",
"method":"simplx_rpc_example",
"params":{
"parents":"0"
}
}
}
</pre>
<br /><br/></li>
<li><em>The "Template" tab</em> <br />contains the jQuery Template html code. This can be updated and viewed "live".<br /><br /></li>
<li><em>The "JSON Result" tab</em> <br />shows the current response from the server.</li>
</ul>
<br/>
<p><strong>JSON-RPC ERROR CODES</strong></p>
<p>
<b>Example</b>
<pre>
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "The received JSON is not a valid JSON-RPC Request."
},
"id": "1"
}
</pre>
</p>
<p>
<b>These are the error codes defined in the JSON-RPC standard</b>
<br/>
-32700 Parse error. Invalid JSON. An error occurred on the server while parsing the JSON text.
<br/>
-32600 Invalid Request. The received JSON is not a valid JSON-RPC Request.
<br/>
-32601 Method not found. The requested remote-procedure does not exist / is not available.
<br/>
-32602 Invalid params. Invalid method parameters.
<br/>
-32603 Internal error. Internal JSON-RPC error.
<br/>
-32099..-32000 Server error. Reserved for implementation-defined server-errors.
</p>
<br/>
<br/>
<br/>
</div>
<!-- Here comes the jQuery template. (http://api.jquery.com/jquery.tmpl/) -->
<div id="modDocumentTemplate" style="display:none;" type="text/x-jquery-tmpl">
<div id='{id}'>
#${pageid}
<br/>
<b>${pagetitle}</b>
<br/>
${description}
<br/>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment