Entering and Evaluating Magic Data Expressions Inside Blocks

If all you need is to evaluate Magic Data tokens embedded into the text of a block, please see Integrating Magic Data Tokens with other blocks. It is all about what you want to do and is far simpler. In fact, if you have not read it yet, read it now as a primer before continuing here!

This note is about deeper integration of Magic Data within blocks. Situations where a Magic Data expression is entered directly in the block edit interface and is then evaluated to control some aspect of the blocks behaviour. To help you get the idea, here are a few examples

  • In Universal Content Puller, you can edit a magic data expression to determine whether or not the block is shown. You can do something like that with permissions, but this goes further because with Magic Data you can use all sorts of site information to make that decision. When the block is rendered, the Magic Data expression is evaluated and if it returns false, the rendering stops there with no output. If it returns true, rendering of the view is completed.
  • In Front End File Uploader, you can opt to use a Magic Data expression to determine who will have access to the upload popup. As with UCP, the Magic Data expression is evaluated when the block is rendered, and the uploader functionality provided if the expression returns true.
  • Also in Front End File Uploader, you can opt to use a Magic Data expression to decide where a file is uploaded to. This time the Magic Data expression is evaluated within the tool that handles the upload, returning a File ID or a list of File Set IDs. The upload is then placed in that File ID or into the File Set IDs.
  • Integrating with Front End File Uploader is Front End Fileset Tools (currently in the PRB), using Magic Data in a similar way to provide a complete front end solution for managing a fileset.
  • In Sorcerer's Map the entire block interface is built on Magic Data expressions. Magic Data expressions are evaluated when the block is rendered to determine the location for the map, the zoom level, the map title and caption.
  • Similarly in Sorcerer's Gallery, Magic Data expressions are evaluated when the block is rendered to the files to show in the gallery. A range of block templates then enable the files to be shown as thumbnail galleries with lightboxes, sliders, slideshows and more.
  • Uber List uses Magic Data to build a list of anything on your site: users, pages, eCommerce products, customers and more. Each list item then uses a stack as a template where Magic Data is used more conventionally to fill in the details of each item. Many of the lists on this site are built using Uber List.
  • Blocks by AJAX provides ajax loading and reloading of blocks and stacks. Used with a form and a Magic Data template, the ajax loaded block or stack can evaluate magic data to provide ajax interactions responding to user inputs, all with no php or JavaScript programming.

Entering Magic Data Expressions in a Block interface

Providing a data entry field for Magic Data expressions is the easy part. In the database db.xml you should allow an X or X2 field. You never know how complex an expression your users will want to write. In essence, this could be a simple text field or text area. However, you can make it much easier for your users by using a Magic Data Symbol Area. This is like a text area, but with built in symbol tester.

50-_edit_location.pngUsing it is very similar to using any of the core concrete5 extended form controls, like the assets helper. In an edit dialog you start by loading the helper directly from the Magic Data package:

$mdsth = Loader::helper('md_symbol_area', 'jl_magic_data');

Then to create a text area with built in symbol tester, within the block edit form you use the helper to render the text area:

echo $mdsth -> render( 'my_dom_id_name',
    'input_and_db_name',
    $input_and_db_name,
    t('Enter Magic Data symbols here and they will be evaluated to provide my own special behaviour for this block')
);

The first parameter is a string providing an overall wrapper id. Like any html element id, it must be unique within a dialog and within a page.

The second parameter is a string with the name used for the form element. For convenience, this is usually best made the same as the field name in the db.xml. That way, you can let concrete5 look after saving the result and avoid having to provide a save() method in the block controller (unless other parameters require it).

The third parameter is a php variable containing initial data for the text area and will usually be the php variable set by concrete5 when it extracts the field from the db.xml, so the same literal text as the second parameter, but with a $ in front.

You could get away with just those three parameters and let default values take care of everything else. However, you usually would use the fourth parameter to provide a snippet of explanatory text like "Enter Magic Data symbols here and they will be evaluated to provide a File Set ID."

There are a whole bunch of remaining parameters that can further customise the helper. The defaults are good for most block edit interfaces, so to keep things simple I will not be explaining them further here.

That's it! You now have a Magic Data symbol edit area in your block edit dialog. You can have as many such areas in an edit dialog as you like. Because the symbol tester can take up a fair bit of space with its diagnostics, it is usually best to have a fairly wide block dialog and allow a bit of height, 800 x 600 works well, but you can use other dimensions. If there are more than one in a dialog, I usually find it clearest to place them in separate tabs or accordion sections.

Evaluating Magic Data Expressions directly

Having provided your block edit dialog with entry fields for Magic Data expressions, the other part of the process is to evaluate those expressions.

First you need to decide when a set of symbols need to be evaluated. On block save? In the on_page_view event handler? In the controller view() method? In the block view.php or other template? As a callback to the block controller? In an AJAX action handler?

All of these are equally feasible. My preference is to leave it as late in the rendering process as possible while still having the expression evaluated in the right context.

With that decision made, we want to directly execute an entire variable as a Magic Data expression. This is a little different than processing a text area for embedded tokens. You could always wrap an expression to make it look like a token and render it with a symbols helper, but that would be less efficient and would not enable fine control of the context.

To make it easy, we have another standard set of helper code you can include in your package or site root helpers directory. This returns a php string or array and is also more efficient because it doesn’t need to parse text for tokens.

To load and use it:

$mdeh = Loader::helper('magic_data_expression', 'your_package_name');
$result = $mdeh->evaluate($your_expression);

Bear in mind that the result could be null, a single value, or a JSON encoded list of results, so if any of that is relevant to the place you are using the expression within your block, you need to make appropriate checks.

While a token embedded in text, if incorrect, will just result in faulty text displayed, an expression used to control the behaviour of a block could result in a more serious issue. So where possible you should also add sanity checks to the value(s) returned by evaluating an expression to make sure it is safe to use further, then provide alternate behaviour or a sensible default.

class MagicDataExpressionHelper {

 	static $md_pkg;
 	static $token_replacement;

 	public function __construct($keep=true){
 		MagicDataExpressionHelper::$md_pkg  = Package::getByHandle('jl_magic_data');
		if (is_object(MagicDataExpressionHelper::$md_pkg)){
			Loader::library('magic_data/token_replacement', 'jl_magic_data');
			MagicDataExpressionHelper::$token_replacement = new MagicDataTokenReplacement();
 		}
 	}

 	public function confirm_installed(){
 		return is_object(MagicDataExpressionHelper::$token_replacement);
  	}

 	public function evaluate( $expression,

 					// need to set the orginating cID if in an ajax handler from a page!
 					$cid=null,

 					// should be safe to leave this null
 					$uid=null,

					// some dummy values in case the relevant expressions are used
 					$original_parameter = "#Dummy Original Parameter#",
 					$replacement_text = "#Dummy Replacement Text#"

 					){

 		// early return if
 		if (empty(MagicDataExpressionHelper::$token_replacement)){
 			return;
 		}

		if (empty($cid)){
			$page = Page::getCurrentPage();
			if (is_object($page) && !$page -> isError()){
				$cid = $page -> getCollectionID();
			}
		}
		if (empty($uid)){
			$u = new User();
			if (is_object($u)){
				$uid = $u -> getUserID();
			}
		}

		$symbol_list = MagicDataExpressionHelper::$token_replacement -> parse_token_to_symbol_list($expression);

		/*
		Evaluate symbols. cID and uID set the evaluation context
		*/
		Loader::library('magic_data/symbol_evaluation', 'jl_magic_data');
		$symbol_model_controller = new MagicDataSymbolEvaluation( $symbol_list,
													$original_parameter,
													$replacement_text,
													$cid,
													$uid,
													null,
													$expression
													);
		$result = $symbol_model_controller->evaluate();

		/*
		If the result is an array, return it as JSON
		Its up to whatever follows to know that and
		cope with it.
		*/
		if(is_array($result)){
			$json = Loader::helper('json');
			return $json -> encode($result);
		}

		return $result;

 	}
}

The first part is identical to the symbols helper. It even includes a confirm_installed() method you can use to safely check if Magic Data is enabled. A good place to use this is in a block edit dialog before loading the helper for the Magic Data Symbol Area directly from Magic Data.

If Magic Data is not available, you may want to simply flag an error, or you may want to provide a limited fallback behaviour.

The actual evaluate() method uses libraries within Magic Data to split the expression into a list of tokens and then process that list of tokens.

Most of the time, you can safely just provide the $expression parameter and leave the rest to default. In general, you want the collection ID and user ID to be taken from the current context. In an AJAX handler, you may want to set the collection ID to be that of the originating page.

The other parameters are really only of use within very specialised situations, like within the Symbols Tester, where you can evaluate a symbol in different user contexts, or within the On Block Load extension to Magic Data.

An alternative to the above code is to simply return the $result without any JSON encodeing and then adapt the calling code accordingly. Either approach is perfectly safe to use as long as you know which you are using and stay consistent to it!

The code on this page is free to use for any developer wanting to integrate with Magic Data.

If you Magic Data enable a marketplace package, please include 'Magic Data' in the keywords so it can easily be found in searches.

Last updated: over a year ago