The problem
I want to be able to print JavaScript objects to the page, a la wp_localize_script
, with a faithful representation of the original object not only when it comes to values, WordPress function does that wonderfully, but when it comes to functions too.
To be clear this
$data = array('callback' => 'function(arg){console.log("hello there");}');
wp_localize_script( $handle, 'myObject', $data );
will produce this
var myObject = {callback: "function(arg){console.log("hello there");}"};
where the function is quoted and will hence, require the dreaded JavaScript eval
function to be used restarting the whole thing from the PHP version like
$data = array('callback' => 'console.log("hello there");');
wp_localize_script( $handle, 'myObject', $data );
and then
(eval(myObject.callback))();
my grunt
based setup has the following to say about the use of eval [caption id=“attachment_939” align=“aligncenter” width=“557”] I get it: no eval.[/caption] and furthermore it forces the definition of variables outside of the function signature and the following bad code smell.
The result
Given the $data
object above I want the following to be printed to the page
var myObject = {"callback": function(arg){console.log("hello there");}};
to be able to do something like
myObject.callback();
My solution
I’ve looked into the Zend framework solution to the problem for a guide but found the solution too convoluted to be applicable in my context and simply came up with an homebrew solution of mine: the JsObject class
<?php
namespace tad\utils;
class JsObject
{
protected $in;
protected $out;
public function toObject($arr)
{
if (!is_array($arr)) {
throw new \InvalidArgumentException("Argument must be an array", 1);
}
$out = json_encode($arr);
// remove quotes around functions
$out = preg_replace("/(\"\\s*)(function.*?})(\")/ui", "$2", $out);
// remove quote escaping
$out = preg_replace("/(\\\\+(\"|'))/ui", "\"", $out);
return $out;
}
public static function on($arr)
{
$instance = new self();
$instance->setIn($arr);
return $instance;
}
public function getOut()
{
return $this->out;
}
public function setIn($value)
{
$this->out = $this->toObject($value);
$this->in = $value;
return $this;
}
public function printOut($objectName, $echo = true, $doNotWrap = false)
{
$prefix = '<script type="text/javascript">' . "\n\t" . '//<![CDATA[' . "\n\t\t";
$var = sprintf('var %s = %s;', $objectName, $this->out);
$postfix = "\n\t" . '//]]' . "\n" . '</script>';
$out = '';
if ($doNotWrap) {
$out = $var;
} else {
$out = $prefix . $var . $postfix;
}
if (!$echo) {
return $out;
}
echo $out;
}
public function localize($handle, $objectName)
{
global $wp_scripts;
$out = $this->printOut($objectName, false, true);
if (!$wp_scripts) {
return;
}
$wp_scripts->add_data($handle, 'data', $out);
}
}
which will allow me to write code like
$data = array('callback' => 'console.log("hello there");');
// $handle is the script the object is localized for
JsObject::on($data)->localize($handle, 'myObject');
and have the wanted result of
<script type="text/javascript">
<![CDATA[
var myObject = {"callback":function(arg){console.log("hello there");}};
]]>
</script>
printed in the head of the page. The localize
method will interact with WordPress but any other method will work in any PHP environment. [caption id=“attachment_940” align=“aligncenter” width=“1024”] JsObject[/caption]