Skip to content

Instantly share code, notes, and snippets.

@larscwallin
Created November 17, 2011 11:32
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save larscwallin/1372956 to your computer and use it in GitHub Desktop.
Save larscwallin/1372956 to your computer and use it in GitHub Desktop.
SIMPLX Widgeteer 0.8.5
SIMPLX Widgeteer 0.8.5
UPDATE 120117 10:04
Fixed a bug in the templateNSPlaceholder method thanks to Keith Baker :)
NEW FEAUTURES
If you use namespaced placeholders, you can now reference the first object in any list (array). This was not possible
before. This is going to be even easier later on as you are going to be able to query for objects using regex etc.
UPDATE 120115 16:59
Changed so that the default chunkMatchingSelector is "", which is more likely to be the case.
UPDATE 120114 02:57
Updated the reset concept again as i had not considered the problem
enough. Now the reference should be correctly set to the root of the
traversal history stack for every new placeholder in the iteration.
UPDATE 120113 00:04
Found that i didn't reset the array iterator when going through namespaced placeholders. Fixed but untested.
UPDATE 111120 13:26
NEW FEATURES:
Added event more debug info to make it easy to see the parsing progress.
UPDATE 111119 15:41
NEW FEATURES:
Added plenty of debug info to make it easy to see the parsing progress.
UPDATE 111117 16:56
NEW FEATURES:
Added Chunks and Snippet call :)
1 Dataset
3 Templates
1 Snippet call
Added Chunk caching by using a static class variable.
I have added simple support for peeking and grabbing values which are located in the nested structure INSIDE the current object.
Example:
Lets say that we are parsing this contact object,
---------------------------------------------------------------
{
"objecttypename":"contact",
"firstname":"Lisa",
"lastname":"Johnson",
"phone":"+46 555 131415",
"email":"lisa@internet.com",
"address":[
{
"objecttypename":"address",
"type":"home",
"name":"My Little House",
"street":"That Way 123",
"geodata":{
"longitude":"-70.044636",
"latitude":"+42.689060"
}
}
]
}
---------------------------------------------------------------------
You can now do this to get the "longitude" while at the root level of the object,
[[+address.geodata.longitude]]
:D
UPCOMING
Conditional peek and grab using the same method as the Controller. This will give you regexp support etc.
<?php
/*
Used to load an external class definition.
Ditched this approach to make it simple to package the snippet using the PacMan addon.
Below the class definition you find the actual snippet code...
*/
//require_once($modx->config['base_path']."assets/snippets/simplx/simplx_widgeteer.php");
if(!class_exists('simplx_widgeteer')){
class simplx_widgeteer{
public $debugmode = true;
public $dataSet;
public $dataSetUrl;
public $dataSetArray;
public $dataSetRoot;
public $useChunkMatching = true;
public $chunkMatchingSelector = '';
public $staticChunkName = '';
public $chunkPrefix = '';
public $chunkMatchRoot = false;
public $preprocessor = '';
private $iterator = 0;
private $traversalStack = array();
private $traversalObjectStack = array();
private $traversalContext = '';
// Only one instance per request.
private static $chunkCache = array();
public function preprocess($dSet,$preprocessor=null){
global $modx;
if ($preprocessor) {
$dSet = $modx->runSnippet($preprocessor,array('dataSet'=>$dSet));
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: preprocess(), Done processing. Dataset now contains "'.$dSet.'".');
}
return $dSet;
}
public function loadDataSet($dSet){
global $modx;
$dset;
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: loadDataSet(), Dataset contains "'.$dSet.'".');
if($this->preprocessor){
$dSet = $this->preprocess($dSet,$this->preprocessor);
$this->dataSet = $dSet;
}
$this->dataSetArray = $this->decode($dSet);
if(is_array($this->dataSetArray)){
return true;
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'Widgeteer: loadDataSet(): Exception, Unable to decode the dataset.');
return false;
}
//$this->cacheDataSet($dSet);
}
public function loadCachedDataSet($dataSetKey){
global $modx;
$dSet = $modx->cacheManager->get($dataSetKey);
if ($dSet) {
return $dSet;
}else{
return '';
}
}
public function cacheDataSet($dSet){
global $modx;
// For now we just cache remote dataSets...
if ($this->dataSetUrl != '') {
$dataSetLocator = urlencode($this->dataSetUrl);
$modx->cacheManager->set(('dataSet.'.$dataSetLocator),$this->dataSetArray);
}
}
public function loadDataSource($dSourceURL){
global $modx;
$result = false;
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: loadDataSource(), Fetching from URL "'.$dSourceURL.'".');
if ($dSourceURL != "") {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $dSourceURL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$this->dataSetUrl = $dSourceURL;
if ($output != "") {
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: loadDataSource(), Got the following output "'.$output.'".<br/><br/>');
$result = $this->loadDataSet($output);
if($result){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: loadDataSource(), loadDataSet() returned "true".');
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'Widgeteer: loadDataSource(): Exception, loadDataSet() returned "false". Aborting.');
}
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'Widgeteer: loadDataSource(): Exception, no valid output from "'.$dSourceURL.'".');
}
}
}
public function setDataRoot($elName){
$this->dataSetRoot = $elName;
$this->dataSetArray = $this->dataSetArray[$this->dataSetRoot];
}
public function decode($json){
return json_decode($json,true);
}
public function encode($object){
return json_encode($object);
}
function parse(&$obj=null){
global $modx;
$result = '';
if(!isset($obj)){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse() Got no initial argument.');
if($this->dataSetArray){
$obj = &$this->dataSetArray;
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'Widgeteer: parse(): Exception, Missing valid dataset. Aborting.');
return false;
}
}else{
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse() starting with argument "'.json_encode($obj).'".');
}
if($this->dataSetRoot != ''){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), Checking existance of dataSetRoot "'.$this->dataSetRoot.'".');
/*
if(array_key_exists($this->dataSetRoot,$this->dataSetArray)){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), dataSetRoot "'.$this->dataSetRoot.'" was found in the dataset.');
}else{
$modx->log(modX::LOG_LEVEL_ERROR, 'Widgeteer: parse(): Exception, dataSetRoot "'.$this->dataSetRoot.'" was NOT found in the dataset. Aborting.');
return false;
}
*/
$context = $this->dataSetRoot;
}
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,'');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), Starting recursive parse.');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), -------------------------------------');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,'');
foreach ($obj as $key => &$val) {
switch($this->typeCheck($val)){
case "list":
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), This item ("'.$key.'") is a list ([]). Calling parseList().');
$result = $this->parseList($val,$key);
break;
case "object":
if(!$this->dataSetRoot){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), This item ("'.$key.'") is an object ({}). Calling parseObject().');
$result = $this->parseObject($val,$key);
}
break;
case "simple":
if(!$this->dataSetRoot){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), This item ("'.$key.'") is a simple type (string or number). Calling parseSimpleType().');
$result = $this->parseSimpleType($val,$key);
}
break;
default :
break;
}
$obj[$key] = $result;
}
$result = implode(' ',$obj);
// If this is the last render call we have the option to wrap the result in the
// rootChunk.
if($this->chunkMatchRoot == 'true'){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), Chunk matching root element.');
$rootChunk = $modx->getChunk(($this->chunkPrefix.$this->dataSetRoot));
if($rootChunk != ''){
$result = str_replace('[[+content]]',$result,$rootChunk);
}else{
}
}
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parse(), Done parsing. Returning "'.$result.'".');
return $result;
}
function parseObject(&$obj,$context){
global $modx;
$result = '';
/*
FIX
Add a reference to the actual, untemplated, object.
Now the stack contains templated data
*/
// Add the object to the stack
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,
'
Widgeteer: parseobject() adding object "'.$context.'" to stack "'.json_encode($obj).'".
');
$this->traversalObjectStack[$context] = $obj;
foreach ($obj as $key => &$val) {
switch($this->typeCheck($val)){
case 'list':
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseObject(), This item ("'.$key.'") is a list ([]). Calling parseList().');
$result = $this->parseList($val,$key);
break;
case 'object':
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseObject(), This item ("'.$key.'") is an object ({}). Calling parseObject().');
$result = $this->parseObject($val,$key);
break;
case 'simple':
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseObject(), This item ("'.$key.'") is a simple type (string or number). Calling parseSimpleType().');
$result = $this->parseSimpleType($val,$key);
break;
default:
break;
}
$obj[$key] = $result;
}
$result = $this->template($obj,$context);
//if($debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseobject() popping object "'.$context.'" from stack.');
// Pop it from the stack
//array_pop($this->traversalObjectStack);
return $result;
}
function parseList(&$list,&$context){
global $modx;
$result = '';
$iterator = 0;
foreach ($list as &$index) {
switch($this->typeCheck($index)){
case 'list':
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseList(), This item ("'.$context.'") is a list ([]). Calling parseList().');
$result = $this->parseList($index,$iterator);
break;
case 'object':
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseList(), This item ("'.$context.'") is an object ({}). Calling parseObject().');
$result = $this->parseObject($index,$context);
break;
case 'simple':
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseList(), This item ("'.$context.'") is a simple type (string or number). Calling parseSimpleType().');
$result = $this->parseSimpleType($index,$context);
break;
default:
break;
}
$list[$iterator] = $result;
$iterator++;
}
$result = implode(' ',$list);
return $result;
}
function parseSimpleType(&$value,&$context){
global $modx;
$prefContext = ($this->chunkPrefix.$context);
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseSimpleType(), This item ("'.$context.'") contains value "'.$value.'".');
if(!array_key_exists($prefContext,self::$chunkCache)){
self::$chunkCache[$prefContext] = $modx->getChunk($prefContext);
$chunk = self::$chunkCache[$prefContext];
}else{
$chunk = self::$chunkCache[$prefContext];
}
if($chunk){
$result = str_replace('[[+value]]',$value,$chunk);
return $result;
}else{
return $value;
}
}
function parseFieldPointer(&$type,&$context){
global $modx;
$chunk = $modx->getChunk(($this->chunkPrefix.$context));
if($chunk != ''){
$result = str_replace('[[+value]]',$type,$chunk);
return $result;
}else{
return $type;
}
}
function template(&$collection,$tmplName) {
global $modx;
$res = null;
$tempVar;
if ($this->useChunkMatching) {
// Get the current chunkMatchingSelector key from the $collection list.
// This is used later to choose which Chunk to use as template.
if(array_key_exists($this->chunkMatchingSelector,$collection)){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), chunkMatchingSelector "'.$this->chunkMatchingSelector.'" was found in collection "'.$tmplName.'".');
$chunkName = $collection[$this->chunkMatchingSelector];
}else{
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), chunkMatchingSelector "'.$this->chunkMatchingSelector.'" was NOT found in collection "'.$tmplName.'".');
$chunkName = '';
}
if ($chunkName === '') {
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), Chunk name is "".');
/*
If nothing was returned from the assignment above we have found no selector. We have to
use another way to match the current collection in the $collection list.
The way that json is structured it is very likely that the parent key is the name of the
object type in the collection.
Example
{
"contact":[
{
"name":"Mini Me",
"shoesize":{"eu":"23"}
},
{
"name":"Big Dude",
"shoesize":{"eu":"49"}
}
]
}
In the example above its implied that each item in the "contact" collection is of typ... contact :)
Similarly the "shoesize" property is a complex value that would be best matched to the key "shoesize".
*/
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), It is very likely that the parent key is the name of the object type. Setting $chunkName to "'.$tmplName.'"');
$chunkName = $tmplName;
}else{
}
if($chunkName != ''){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), parsing Chunk "'.$chunkName.'".');
//$tempVar = $modx->parseChunk(($this->chunkPrefix.''.$chunkName), $collection, '[[+', ']]');
$tempVar = $this->parseChunk(($this->chunkPrefix.''.$chunkName), $collection, '[[+', ']]');
$res .= $tempVar;
}else{
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), $chunkName is still empty.');
}
}else{
//$tempVar .= $modx->parseChunk($this->staticChunkName, $collection, '[[+', ']]');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), Chunk matching is turned off. Using static Chunk "'.$this->staticChunkName.'".');
$tempVar .= $this->parseChunk($this->staticChunkName, $collection, '[[+', ']]');
$res .= $tempVar;
}
if(!$res){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), parseChunk() returned an invalid result. Setting the template result to "".');
$res = '';
}else{
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: template(), parseChunk() returned a valid result. Calling templateNSPlaceholders() to find and template namespaced tags.');
$res = $this->templateNSPlaceholders($res);
}
return $res;
}
function parseChunk($chunkName, $chunkArr, $prefix='[[+', $suffix=']]'){
global $modx;
$chunk='';
if(!array_key_exists($chunkName,self::$chunkCache)){
self::$chunkCache[$chunkName] = $modx->getChunk($chunkName);
$chunk = self::$chunkCache[$chunkName];
}else{
$chunk = self::$chunkCache[$chunkName];
}
if (!empty($chunk) || $chunk === '0') {
if(is_array($chunkArr)) {
reset($chunkArr);
while (list($key, $value)= each($chunkArr)) {
$chunk= str_replace($prefix.$key.$suffix, $value, $chunk);
}
}
}
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: parseChunk(), Done parsing, returning "'.$chunk.'".');
return $chunk;
}
// Templates namespaced placeholders
function templateNSPlaceholders($chunk){
global $modx;
$collection = null;
$currentType = '';
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: Starting templateNSPlaceholders(). Checking "'.$chunk.'"');
// Find any unparsed placeholders
$nsSeparator = '.';
$forwardSeparator = '-';
$backSeparator = '+';
$patternSeparator = '?';
$placeholders;
$pattern = "/\[\[\+[^\]]*\]\]/";
preg_match_all($pattern, $chunk, $placeholders);
if(!is_array($placeholders) || count($placeholders[0])<=0){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templateNSPlaceholders(), there where no placeholders in this chunk. Returning what we got.');
return $chunk;
}
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templateNSPlaceholders(), we got placeholders "'.json_encode($placeholders).'"');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
// Set local references to the traversal stack (history stack). Local refs should speed up access.
$stackRootPointer = &$this->traversalObjectStack;
$pointer = &$stackRootPointer;
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() The traversal stack has the following items: "'.json_encode($pointer).'"');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() Going into the placeholders foreach.');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
// The actual result of the preg_match is in the first index of the $placeholders array.
$placeholders = $placeholders[0];
foreach($placeholders as $placeholder){
$value = '';
// Make a local copy for performance impr.
$current = $placeholder;
// Only parse the placeholder if it has a separator
if(!strpos($current,$nsSeparator)===false){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() the current placeholder is: "'.$current.'"');
// Remove the tags
$path = str_replace(array('[[+',']]'), '', $current);
// Explode the current placeholder using the namespace separator
$parts = explode($nsSeparator,$path);
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() After the cleanup the placeholder looks like this: "'.json_encode($parts).'" .');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() Looping through the parts of the current placeholder.');
// Iteration counter
$i = 1;
// Lets loop through all parts in the namespace array.
foreach($parts as $part){
switch($part){
case '<':
break;
case '>':
break;
case '?':
break;
default:
/*
Ok, lets look in the current array position if we have a match for the namespace part.
Right now we only support cronological paths so if the part is not found, its no
use iterating on, so we simply return the chunk we got.
*/
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() Rotation nr "'.$i.'".');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() The current part is "'.$part.'".');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() Lets start looking in the stack at our present position.');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() The current pointer in the stack contains: "'.json_encode($pointer).'".');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
// Get a pointer to the current value
$value = &$pointer[$part];
if(!isset($value)){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() The current position in the stack had no "'.$part.'" reference. This means that the path is invalid and we can set value to "".');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
unset($value);
$value = '';
}
/*
Got something! Lets see if its a string/num/bool, or an array...
*/
// If we still have an array, we havent reached the target
$currentType = $this->typeCheck($value);
if($currentType!=='simple'){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() We got an array back from the current stack position "'.json_encode($value).'".');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() We got an array back from the current stack position "'.json_encode($value).'".');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
// Increment the i counter which is just used for debug purposes.
$i++;
// unset the pointer so that we dont screw with the history stack
unset($pointer);
// If we got an array, we need to step into it to get something usefull.
if($currentType==='list'){
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() * Current stack item is an array.');
$pointer = &$value[0];
}else{
// Redirect the pointer ref to the new, current position in the traversal history stack
$pointer = &$value;
}
// unset the value ref so that we dont screw with the history stack
unset($value);
}else{
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() The current position returned the string "'.$value.'". Lets keep it for templating.');
}
}
}
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() done with parsing this placeholder. Lets replace the it with this value "'.$value.'"');
// Replace the original placeholder with the retrieved value.
$chunk = str_replace(('[[+'.$path.']]'),$value,$chunk);
// unset the pointer so that we dont screw with the history stack
unset($pointer);
// Backup to the beginning of the stack so that the next placeholder can reference the complete history.
$pointer = $stackRootPointer;
}
}
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() done with parsing all placeholders.');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG, 'Widgeteer: templatePlaceholders() returning "'.$chunk.'"');
if($this->debugmode) $modx->log(modX::LOG_LEVEL_DEBUG,' ');
return $chunk;
}
// Facilitates Chunk caching
function getChunkTemplate($templateName){
if(array_key_exists($templateName,self::$chunkCache)){
return self::$chunkCache[$templateName];
}else{
self::$chunkCache[$templateName] &= $modx->getChunk($templateName);
return self::$chunkCache[$templateName];
}
}
// Facilitates Chunk processing using cached objects
function parseChunkTemplate($templateName,&$collection){
if(array_key_exists($templateName,self::$chunkCache)){
return $modx->parseChunk(($this->chunkPrefix.''.$chunkName), $collection, '[[+', ']]');
}else{
return $modx->getChunk($templateName);
}
}
// Utility function to check type of Array
function is_assoc (&$arr) {
try{
return (is_array($arr) && (!count($arr) || count(array_filter(array_keys($arr),'is_string')) == count($arr)));
}catch(Exception $e){
return false;
}
}
function typeCheck(&$var){
$val = '';
if(is_array($var)){
if ($this->is_assoc($var)) {
$val = 'object';
}else{
$val = 'list';
}
}else{
$val = 'simple';
}
return $val;
}
}
}else{
}
/*
----------------------------------------------------------------------------------------------
Below is the actual snippet code which sets defaults, validates the input, instatiates the
Widgeteer object and runs the logic...
*/
$dataSourceUrl = isset($dataSourceUrl) ? $dataSourceUrl : 'null';
$dataSourceUrl = isset($dataSetUrl) ? $dataSetUrl : $dataSourceUrl; //New interface parameter to mend naming consistency issue.
$staticChunkName = isset($staticChunkName) ? $staticChunkName : 'null';
$dataSet = isset($dataSet) ? ($dataSet) : 'null';
$useChunkMatching = isset($useChunkMatching) ? $useChunkMatching : true;
$chunkMatchingSelector = isset($chunkMatchingSelector) ? $chunkMatchingSelector : '';
$dataSetRoot = isset($dataSetRoot) ? $dataSetRoot : 'null';
$chunkMatchRoot = isset($chunkMatchRoot) ? $chunkMatchRoot : false;
$chunkPrefix = isset($chunkPrefix) ? $chunkPrefix : '';
$dataSet = str_replace(array('|xq|','|xe|','|xa|'),array('?','=','&') , $dataSet);
$preprocessor = isset($preprocessor) ? $preprocessor : '';
$debugmode = isset($debugmode) ? $debugmode : true;
if($debugmode){
$modx->setLogLevel(modX::LOG_LEVEL_DEBUG);
}
if($dataSourceUrl == 'null' && $dataSet == 'null'){
print '{"result":[{"objecttypename":"exception","errorcode"="0","message":"The dataSet parameter is empty."}]}';
}else{
$w = new simplx_widgeteer();
$w->debugmode = $debugmode;
/*
PHP bug perhaps? $useChunkMatching evaluates as true even if its false!?
I have to "switch poles" in order to get the right effect...
*/
if($useChunkMatching && $staticChunkName != 'null'){
$w->useChunkMatching = false;
$w->staticChunkName = $staticChunkName;
}else{
$w->useChunkMatching = true;
$w->chunkMatchingSelector = $chunkMatchingSelector;
}
$w->chunkMatchRoot = $chunkMatchRoot;
$w->chunkPrefix = $chunkPrefix;
$w->preprocessor = $preprocessor;
if($dataSourceUrl != 'null'){
$dataSourceUrl = urldecode($dataSourceUrl);
$w->loadDataSource($dataSourceUrl);
}else{
$w->loadDataSet(utf8_encode($dataSet));
}
if($dataSetRoot != 'null'){
$w->setDataRoot($dataSetRoot);
}
return $w->parse();
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> SIMPLX Widgeteer </title>
<!--
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>
<style>
body {
font-family:sans-serif;
background-color:#fff;
font-size:13px;
}
p {width:600px;margin-left:10px;}
h1 {font-size:18px;}
h2 {font-size:17px;}
h3 {font-size:16px;margin-top:30px;padding-top:10px;border-top:1px dashed #555;}
h4 {font-size:13px;color:#555;margin-left:10px;}
#widgeteer_examples{
width:800px;
margin-left:50px;
}
.feed-example {
height:200px;
width:90%;
}
</style>
</head>
<body>
<div id="widgeteer_examples">
<h1>Playing with the SIMPLX Widgeteer</h1>
<h3>http://feeds.delicious.com/v2/json/tag/modxcms</h3>
<div>
<h4>The feed:</h4>
<p>First of we have this nice "atomish" feed from delicious. This feed is located at "http://feeds.delicious.com/v2/json/tag/modxcms" and returns a list like this:</p>
<pre>
[
{
"u":"http:\/\/modxcms.com\/forums\/index.php?topic=2794.0",
"d":"[Module] Backing up\/Moving MODx Sites",
"t":["modxcms","modx"],
"dt":"2010-11-03T04:16:37Z",
"n":"",
"a":"Grantox"
},
...
]
</pre>
<h4>This is the call in the template:</h4>
<pre>
&#91;&#91;simplx_widgeteer?
&dataSetUrl=`http://feeds.delicious.com/v2/json/tag/modxcms`
&useChunkMatching=`false`
&staticChunkName=`widgeteer_template_delicious_item`
&#93;&#93;
</pre>
<p>This first Widgeteer call does not use chunkMatching as this JSON syntax does not supply any specific type identifier for items in the list.</p>
<p>The parameter "staticChunkName" specifies which Modx Chunk ("widgeteer_template_deliciousitem" in this case) that will be used as template for each item in the list.</p>
<h4>Here is the Chunk template "widgeteer_template_deliciousitem"</h4>
<pre>
&lt;li&gt;
&lt;b&gt;
&lt;a href="&#91;&#91;+u&#93;&#93;"&gt;&#91;&#91;+d&#93;&#93;&lt;/a&gt;
&lt;/b&gt;
&lt;br/&gt;
Added &#91;&#91;+dt&#93;&#93;, by &#91;&#91;+a&#93;&#93;
&lt;/li&gt;
</pre>
<h4>And here is the live result :)</h4>
<ul>
[[!simplx_widgeteer?
&dataSetUrl=`http://feeds.delicious.com/v2/json/tag/modxcms`
&useChunkMatching=`false`
&staticChunkName=`widgeteer_template_delicious_item`
]]
</ul>
</div>
<h3>Modx Chunk "widgeteer_dataset_contactlist"</h3>
<div>
<p>
This second example uses a Modx Chunk as dataSet. Note that as we are using a static set of data and not an url we use the "dataSet" argument.
</p>
<p>
The "&dataSetRoot" parameter tells the Widgeteer to use the JSON attribute "contacts" as root for parsing. The explanation can be found in the dataSet syntax:
</p>
<pre>
{
"objecttypename":"contactlist",
"contacts":[
{
"objecttypename":"contact",
"firstname":"Lars"
...
},
...
]
}
</pre>
<p>As you can see above the actual list of contacts is wrapped in a parent object of type "list" in this case. This is a very common practice, and indeed a direct result of object orientation where objects often have collections of other objects. This example also uses so called chunkMatching which is a really neat feature which automatically tries to apply a matching template for all JSON objects in the dataSet.</p>
<p>Matching is done by by comparing a JSON attribute value to a Modx Chunk name. The JSON attribute to use for matching is specified with the "chunkMatchingSelector" paramenter. Note that the "chunkPrefix" will affect matching. In the following example each value in the "objecttypename" attribute will be matched to Chunks with names beginning with "widgeteer_template_". To be more specific, the "$widgeteer_dataset_contactlist" Chunk contains,</p>
<pre>
{
"objecttypename":"contact",
"firstname":"Lars",
...
}
</pre>
<p>
and so each of the JSON objects in this particular list will be matched with a Chunk named "widgeteer_template_contact". A dataSet can contain a mix of any number of different object types, all of which will be matched to appropriate template Chunks in the way just described.
</p>
<h4>This is the call in the template:</h4>
<pre>
&#91;&#91;!simplx_widgeteer?
&dataSet=`&#91;&#91;$widgeteer_dataset_contactlist&#93;&#93;`
&dataSetRoot=`contacts`
&useChunkMatching=`true`
&chunkMatchRoot=`true`
&chunkPrefix=`widgeteer_template_contactlist_`
&chunkMatchingSelector=`objecttypename`
&#93;&#93;
</pre>
<h4>And here is the live result :)</h4>
[[!simplx_widgeteer?
&dataSet=`[[$widgeteer_dataset_contactlist]]`
&dataSetRoot=`list`
&useChunkMatching=`true`
&chunkMatchRoot=`true`
&chunkPrefix=`widgeteer_template_contactlist_`
&chunkMatchingSelector=`objecttypename`
]]
</div>
</div>
<h3>Modx Chunk "widgeteer_dataset_youtube"</h3>
<p>
This gdata feed proved to be quite a challange for the Widgeteer; it actually
caused me to re-write the parser ;)
</p>
<p>
The feed structure is, in my opinion, vastly over worked. Anyway, here's a very simple example which extracts and templates very basic information from the feed.
</p>
<p>
The new version of Widgeteer simplifies templating of complex feeds like this vastly! Take a look
at the <i>single</i> template at the end which we use to output our results.
</p>
<h4>The feed structure</h4>
<p>
Note that this is the data for <i>one</i> singe YouTube video:
</p>
<textarea class="feed-example">
{
"id":{
"$t":"http://gdata.youtube.com/feeds/api/videos/n0X5WCmyokw"
},
"published":{
"$t":"2012-01-15T21:19:11.000Z"
},
"updated":{
"$t":"2012-01-16T15:39:48.000Z"
},
"category":[
{
"scheme":"http://schemas.google.com/g/2005#kind",
"term":"http://gdata.youtube.com/schemas/2007#video"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/categories.cat",
"term":"Nonprofit",
"label":"Nonprofits & Activism"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"Gardenslayer"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"why"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"is"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"reddit"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"offline"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"sopa"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"pipa"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"anonymous"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"webmasters"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"message around"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"the"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"world"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"legion"
},
{
"scheme":"http://gdata.youtube.com/schemas/2007/keywords.cat",
"term":"720phighdefenition"
}
],
"title":{
"$t":"A call to action for webmasters around the world",
"type":"text"
},
"content":{
"$t":"TO ADMINISTRATORS OF WEBSITES OVER THE WORLD On January 24, 2012, the Senate will be voting about the PROTECT IP Act, also known as PIPA. This legislation can be used to effectively censor any website on the internet that accepts user content, regardless of whether they are actually infringing on copyrights in any way, shape, or form. On January 18, 2012, Congress will once again discuss the Stop Online Piracy Act. SOPA not only allows a court to order the blocking of a website through measures such as DNS blocking or other 'appropriate measures', but also allows a private party to cut off funding to a website without court involvement. Both of these proposals are incredibly dangerous to the internet and to the freedom of speech of individuals. The blocking mechanisms that are defined (such as DNS blocking) are so trivial that any dedicated or tech-savvy 'pirate' will easily circumvent them, but other sites that have nothing to do with 'piracy' will suffer; as their users are often not computer literate enough to bypass SOPA. Ironically this legislation does not affect the people it is targeting. Even if you don't live in the United States, you will be affected by this legislation, as much of the core internet infrastructure is located in the US. Apart from the technical blocking measures, another serious problem is the ability of a private entity to shut down the money flow of a website without any involvement of a court. By making payment providers and advertisement \u003cb\u003e...\u003c/b\u003e",
"type":"text"
},
"link":[
{
"rel":"alternate",
"type":"text/html",
"href":"http://www.youtube.com/watch?v=n0X5WCmyokw&feature=youtube_gdata"
},
{
"rel":"http://gdata.youtube.com/schemas/2007#video.responses",
"type":"application/atom+xml",
"href":"http://gdata.youtube.com/feeds/api/videos/n0X5WCmyokw/responses"
},
{
"rel":"http://gdata.youtube.com/schemas/2007#video.related",
"type":"application/atom+xml",
"href":"http://gdata.youtube.com/feeds/api/videos/n0X5WCmyokw/related"
},
{
"rel":"http://gdata.youtube.com/schemas/2007#mobile",
"type":"text/html",
"href":"http://m.youtube.com/details?v=n0X5WCmyokw"
},
{
"rel":"self",
"type":"application/atom+xml",
"href":"http://gdata.youtube.com/feeds/api/videos/n0X5WCmyokw"
}
],
"author":[
{
"name":{
"$t":"GardenslayerComm"
},
"uri":{
"$t":"http://gdata.youtube.com/feeds/api/users/gardenslayercomm"
}
}
],
"gd$comments":{
"gd$feedLink":{
"rel":"http://gdata.youtube.com/schemas/2007#comments",
"href":"http://gdata.youtube.com/feeds/api/videos/n0X5WCmyokw/comments",
"countHint":9
}
},
"yt$hd":{
},
"media$group":{
"media$category":[
{
"$t":"Nonprofit",
"label":"Nonprofits & Activism",
"scheme":"http://gdata.youtube.com/schemas/2007/categories.cat"
}
],
"media$content":[
{
"url":"http://www.youtube.com/v/n0X5WCmyokw?version=3&f=videos&app=youtube_gdata",
"type":"application/x-shockwave-flash",
"medium":"video",
"isDefault":"true",
"expression":"full",
"duration":179,
"yt$format":5
},
{
"url":"rtsp://v7.cache1.c.youtube.com/CiILENy73wIaGQlMorIpWPlFnxMYDSANFEgGUgZ2aWRlb3MM/0/0/0/video.3gp",
"type":"video/3gpp",
"medium":"video",
"expression":"full",
"duration":179,
"yt$format":1
},
{
"url":"rtsp://v7.cache6.c.youtube.com/CiILENy73wIaGQlMorIpWPlFnxMYESARFEgGUgZ2aWRlb3MM/0/0/0/video.3gp",
"type":"video/3gpp",
"medium":"video",
"expression":"full",
"duration":179,
"yt$format":6
}
],
"media$description":{
"$t":"TO ADMINISTRATORS OF WEBSITES OVER THE WORLD On January 24, 2012, the Senate will be voting about the PROTECT IP Act, also known as PIPA. This legislation can be used to effectively censor any website on the internet that accepts user content, regardless of whether they are actually infringing on copyrights in any way, shape, or form. On January 18, 2012, Congress will once again discuss the Stop Online Piracy Act. SOPA not only allows a court to order the blocking of a website through measures such as DNS blocking or other 'appropriate measures', but also allows a private party to cut off funding to a website without court involvement. Both of these proposals are incredibly dangerous to the internet and to the freedom of speech of individuals. The blocking mechanisms that are defined (such as DNS blocking) are so trivial that any dedicated or tech-savvy 'pirate' will easily circumvent them, but other sites that have nothing to do with 'piracy' will suffer; as their users are often not computer literate enough to bypass SOPA. Ironically this legislation does not affect the people it is targeting. Even if you don't live in the United States, you will be affected by this legislation, as much of the core internet infrastructure is located in the US. Apart from the technical blocking measures, another serious problem is the ability of a private entity to shut down the money flow of a website without any involvement of a court. By making payment providers and advertisement \u003cb\u003e...\u003c/b\u003e",
"type":"plain"
},
"media$keywords":{
"$t":"Gardenslayer, why, is, reddit, offline, sopa, pipa, anonymous, webmasters, message around, the, world, legion, 720phighdefenition"
},
"media$player":[
{
"url":"http://www.youtube.com/watch?v=n0X5WCmyokw&feature=youtube_gdata_player"
}
],
"media$thumbnail":[
{
"url":"http://i.ytimg.com/vi/n0X5WCmyokw/0.jpg",
"height":360,
"width":480,
"time":"00:01:29.500"
},
{
"url":"http://i.ytimg.com/vi/n0X5WCmyokw/1.jpg",
"height":90,
"width":120,
"time":"00:00:44.750"
},
{
"url":"http://i.ytimg.com/vi/n0X5WCmyokw/2.jpg",
"height":90,
"width":120,
"time":"00:01:29.500"
},
{
"url":"http://i.ytimg.com/vi/n0X5WCmyokw/3.jpg",
"height":90,
"width":120,
"time":"00:02:14.250"
}
],
"media$title":{
"$t":"A call to action for webmasters around the world",
"type":"plain"
},
"yt$duration":{
"seconds":"179"
}
},
"gd$rating":{
"average":5.0,
"max":5,
"min":1,
"numRaters":64,
"rel":"http://schemas.google.com/g/2005#overall"
},
"yt$statistics":{
"favoriteCount":"12",
"viewCount":"1779"
}
}
</textarea>
<h4>This is the Chunk template:</h4>
<pre>
&lt;p class="youtube_feed_item"&gt;
&lt;h5&gt;&#91;&#91;+media$group.media$title.$t&#93;&#93;&lt;/h5&gt;
&lt;a target="_blank" href="&#91;&#91;+media$group.media$content.url&#93;&#93;"&gt;
&lt;img src="&#91;&#91;+media$group.media$thumbnail.url&#93;&#93;"/&gt;
&lt;/a&gt;
&lt;br/&gt;
Published:
&#91;&#91;+published.$t&#93;&#93;
&lt;/p&gt;
</pre>
<h4>This is the call in the template:</h4>
<pre>
&#91;&#91;!simplx_widgeteer?
&dataSetUrl=`http://gdata.youtube.com/feeds/api/videos?max-results=5&alt=json&q=modx&orderby=published`
&useChunkMatching=`true`
&chunkMatchRoot=`false`
&chunkPrefix=`widgeteer_template_yt_`
&#93;&#93;
</pre>
<h4>And here is the live result :)</h4>
<div>
[[!simplx_widgeteer?
&dataSetUrl=`http://gdata.youtube.com/feeds/api/videos?max-results=5&alt=json&q=modx&orderby=published`
&dataSetRoot=`feed`
&chunkPrefix=`widgeteer_template_yt_`
]]
</div>
<div>
<h3>Modx Chunk "widgeteer_dataset_form"</h3>
<p>
Turning json representations of objects into forms is very handy!
</p>
<pre>
{
"user":{
"id":"1970411",
"name":"Lars C Wallin",
"sneakers":["Tiger","Adiddas","Nike","DC","Paul Frank"]
}
}
</pre>
<h4>The Chunk templates:</h4>
<p>
This example uses two templates, one for the main form element and one for the collection of sneakers.
<br/>
To get the Widgeteer to iterate and template a list of objects, you always need a Chunk to template the
values or objects which are present in the collection.
<br/>
In this case we have a one collection for each user: "sneakers". This is just a simple list of strings, each
representing a brand of sneakers.
</p>
<b>&nbsp;&nbsp;&nbsp;&nbsp;The sneakers template</b>
<pre>
&lt;option value="&#91;&#91;+value&#93;&#93;"&gt;&#91;&#91;+value&#93;&#93;&lt;/option&gt;
</pre>
<b>&nbsp;&nbsp;&nbsp;&nbsp;The user template</b>
<pre>
&lt;form&gt;
&lt;label for=&quot;name&quot;&gt;Name:&lt;/label&gt;
&lt;br/&gt;
&lt;input id=&quot;name&quot; type=&quot;text&quot; value=&quot;[[+name]]&quot;/&gt;
&lt;br/&gt;
&lt;label for=&quot;id&quot;&gt;Id:&lt;/label&gt;
&lt;br/&gt;
&lt;input id=&quot;id&quot; type=&quot;text&quot; value=&quot;[[+id]]&quot;/&gt;
&lt;br/&gt;
&lt;label for=&quot;id&quot;&gt;Sneakers:&lt;/label&gt;
&lt;br/&gt;
&lt;select&gt;
&#91;&#91;+sneakers&#93;&#93;
&lt;/select&gt;
&lt;/form&gt;
</pre>
<h4>This is the call in the template:</h4>
<pre>
&#91;&#91;!simplx_widgeteer?
&dataSet=`&#91;&#91;$widgeteer_dataset_form&#93;&#93;`
&chunkPrefix=`widgeteer_template_user_`
&#93;&#93;
</pre>
<h4>And here is the live result :)</h4>
[[!simplx_widgeteer?
&dataSet=`[[$widgeteer_dataset_user]]`
&chunkPrefix=`widgeteer_template_form_`
]]
</div>
</body>
</html>
{"list":{
"contacts":[
{
"objecttypename":"contact",
"firstname":"Lars",
"lastname":"Wallin",
"phone":"+46 555 155555",
"email":"lars@internet.com",
"address":[
{
"type":"home",
"name":"My Home",
"street":"My Way 7",
"geodata":{
"longitude":"-74.044636",
"latitude":"+40.689060",
"sealevel":{"m":"1000"}
},
"geodata2":{
"longitude":"-72.044636",
"latitude":"+41.689060"
}
},
{
"type":"away",
"name":"My Summer House",
"street":"Back wood 45"
}
]
},
{
"objecttypename":"contact",
"firstname":"Lisa",
"lastname":"Johnson",
"phone":"+46 555 131415",
"email":"lisa@internet.com",
"address":[
{
"objecttypename":"address",
"type":"home",
"name":"My Little House",
"street":"That Way 123",
"geodata":{
"longitude":"-70.044636",
"latitude":"+42.689060"
},
"geodata2":{
"longitude":"-71.044636",
"latitude":"+12.689060"
}
}
]
},
{
"objecttypename":"contact",
"firstname":"Peter",
"lastname":"Swift",
"phone":"+46 555 757677",
"email":"peter@internet.com",
"address":[
{
"objecttypename":"address",
"type":"home",
"name":"Any Bench",
"street":"Central Park",
"geodata":{
"longitude":"-71.044636",
"latitude":"+43.689060"
}
}
]
}
]
}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment