2

Updated Post

I have a solution for that problem which works with me so far (only tested on Firefox). My main goal was to dynamically update a Flot graph with Ajax. I also need to dynamically update the axis styles and you can do this by the tickFormatter option. To avoid the problems I previously had, I simply insert the desired return string in the PHP array and JSON encode that array. After returned to the Ajax function I create a new function using the javascript Function() constructor (Link). I hope this helps someone.

Ajax

$.ajax({
    url: 'someControllerWhichCreatesJSONdata',
    data: 'some Value',
    type: 'post',
    cache: false,
    datatype: 'json'
})
.success(function(data) {
    var datas = data[0];    // fetch data array
    var options = data[1];  // fetch options array
    var xReturn = options.xaxes[0].tickFormatter; // fetch return string
    var yReturn = options.yaxes[0].tickFormatter;        
    var xFunc = new Function("val", "axis", xReturn); // create function
    var yFunc = new Function("val", "axis", yReturn);
    options.xaxes[0].tickFormatter = xFunc; // update tickFormatter with function
    options.yaxes[0].tickFormatter = yFunc;

    $.plot($("#yw0"), datas, options);
})
.error(function(data) {
    $("#error").html(data.responseText); // show error in separate div
});

PHP array

$options = array(
    'legend' => array(
        'position' => 'nw',
        'show' => true,
        'margin' => 10,
        'backgroundOpacity' => 0.5
    ),
    'grid' => array(
        'clickable' => true,
        'hoverable' => true
    ),
    'pan' => array('interactive' => true),
    'zoom' => array('interactive' => true),
    'axisLabels' => array('show' => true),
    'xaxes' => array(
        array(
            'axisLabel' => 'someUnit',
            'tickFormatter' => 'return "foo bar"' // enter whatever return value you need
        )
    ),
    'yaxes' => array(
        array(
            'axisLabel' => 'someUnit',
            'tickFormatter' => 'return "foo bar"'
        )
    )
);

I skip the data array as it looks similar. Both arrays get combined and JSON encoded.

public function actionSomeControllerWhichCreatesJSONdata() {
   $returnArray = array($data, $options);
   echo json_encode($returnArray);
   return true;
}

The resulting array looks like the one I've posted in the bottom of this post.

Original Post

I'm trying for hours now, but I can't get this to work.

I have an Ajax request which gets a JSON object as return value on success. This works fine as long as I don't use a JS function in my JSON array. So my JSON array looks as follows (after json_encode):

[{
  "data": [{
    {1,2},
    {3,4},
  }],
  "function": "function(){return \"foo bar\";}"
}]

In order to get rid of the "'s in the function string. I use str_replace and replace the quoted string with an unquoted one. This then looks like so:

[{
  "data": [{
    {1,2},
    {3,4},
  }],
  "function": function(){return "foo bar";}
}]

The plain json_encode works fine and my Ajax function reads it as a JSON object. After I replace the string it turns out that the return value is no longer a JSON object but a plain text string. I've tried to parse the string again with jquery.parseJSON() but this results in the syntax error:

SyntaxError: JSON.parse: unexpected keyword at line 1 column 396 of the JSON data

Ajax function:

$.ajax({
    url: 'someControllerWhichCreatesJSONdata',
    data: 'some Value',
    type: 'post',
    cache: false,
    datatype: 'json' // or text when trying to parse
})
.success(function(data) {
    console.log(data);
    var dat = jQuery.parseJSON(data);  // trying to parse (only when dataType: "text")
    var datas = dat[0];                // or data[0] when datType = json
    var options = dat[1];              // ---
    $.plot($("#yw0"), datas, options); // trying to update a FLOT window
})
.error(function(data) {
    console.log("error");
    $("#error").html(data.responseText); // show error in separate div
});

So when using this function and setting the return type to "json", it produces an error. If the return type is "text" and I try to parse the string I get the error above. Is there any solution for this problem or am I doing something completely wrong?

Thank's for your help!

UPDATE Sorry of course my JSON data is not valid! As I said, my JSON object gets created by json_encode which I guess should get the syntax right. The plain array after son_encode looks like:

[[{
   "data":[[0.0042612,0.0042612]],
   "label":"WISE.W3: Cutri et. al 2012",
   "lines":{"show":false},
   "points":{"show":true,"radius":3}}],
  {
   "legend": {
        "position":"nw",
        "show":true,
        "margin":10,
        "backgroundOpacity":0.5},
   "grid": {
        "clickable":true,
        "hoverable":true},
   "pan":{"interactive":true},
   "zoom":{"interactive":true},
   "axisLabels":{"show":true},
   "axisLabel": {
        "unit":"Jy",
        "id":null,
        "name":null},
   "tickFormatter":"$tickFormatter$",
   "position":"right"
}]

and after str_replace I get

[[{
   "data":[[0.0042612,0.0042612]],
   "label":"WISE.W3: Cutri et. al 2012",
   "lines":{"show":false},
   "points":{"show":true,"radius":3}}],
  {
   "legend": {
       "position":"nw",
       "show":true,
       "margin":10,
       "backgroundOpacity":0.5},
   "grid":{"clickable":true,"hoverable":true},
   "pan":{"interactive":true},
   "zoom":{"interactive":true},
   "axisLabels":{"show":true},
   "axisLabel":{"unit":"Jy","id":null,"name":null},
   "tickFormatter":function(val, axis){return val.toExponential(2)},
   "position":"right"
 }]
Flo Ragossnig
  • 1,341
  • 14
  • 38
  • 2
    Your data isn't valid JSON, nor is it valid javascript, you have objects without keys – adeneo Jan 04 '15 at 14:06
  • 1
    Specifically `{{1,2},{3,4},}` <- is invalid – adeneo Jan 04 '15 at 14:07
  • 2
    Just to make it clear: a JSON file is not the same as a JavaScript object literal (the former is simply a text data format). There's a reason why `json_encode` places it in a string. – Qantas 94 Heavy Jan 04 '15 at 14:17

2 Answers2

3

Adeno pointed it out already, your JSON syntax is not valid. Please try to validate your JSON with a linter, like http://jsonformatter.curiousconcept.com/

This is a bit better:

[{"data":[["1","2"],["3","4"]],"function":"function(){return \"foo bar\";}"}]

And the other thing is: you should not manually deserialize the JSON. All newer versions of jQuery will automatically deserialize JSON based on the response's content-type header.

You already set dataType to json for the ajax request. The response is JSON and will be de-serialized. So you can directly use

.success: function(response) {
    console.log(response.data);
    console.log(response.function);
}

Answer for the questions/issue from the comments:

Now you created invalid JSON again, because "tickFormatter" : function(val, axis){return val.toExponential(2)}, misses the quotes around the function string.

  • Before you insert the string with str_replace(), you need to take care of escaping and quoting. You may need a little helper function, which properly escapes a string - to be valid JSON.
  • you need to use str_replace() correctly: not replacing the outer quotes, just the inner $tickFormatter$.

Wait.. i will provide an example:

    // we already have an array, which is JSON encoded
    // now you want to insert additional JSON content.
    // therefore we use a token replace approach.

    // valid JSON - with $token$

    $json = '[{"data":[["1","2"],["3","4"]],"tickFormatter":"$tickFormatter$"}]';

    // the (future) javascript function is a string.
    // it's not properly escaped or quoted to be JSON
    // this allows for copy and pasting JS into PHP
    $unescaped_functionString = 'function(){return "foo bar";}';

    // in order escapre properly, we need a json escaping helper

    /**
     * @param $value
     * @return mixed
     */
    function escapeJsonString($value) { # list from www.json.org: (\b backspace, \f formfeed)
        $escapers = array("\\", "/", "\"", "\n", "\r", "\t", "\x08", "\x0c");
        $replacements = array("\\\\", "\\/", "\\\"", "\\n", "\\r", "\\t", "\\f", "\\b");
        $result = str_replace($escapers, $replacements, $value);
        return $result;
    }

    // then we escape the function string

    $functionString = escapeJsonString($unescaped_functionString);

    // now its ready to be inserted into the existing JSON,
    // where token $tickFormatter$ is defined
    // the token must be in single quotes. you replace just the $token$ and not "$token$".

    $json = str_replace('$tickFormatter$', $functionString, $json);

    echo $json;

    // The result is valid JSON again:
    // [{"data":[["1","2"],["3","4"]],"tickFormatter":"function(){return \"foo bar\";}"}]

Next step: The ajax request is made, the json data is transferred to the client and you want to execute the function you passed in.

So the new question is "How to call/exec a JavaScript function from a string - without using eval?"

In general tunneling is a bad practice, see: Is it valid to define functions in JSON results?

Anyway, there a different ways to do this:

Referencing: http://everythingfrontend.com/posts/studying-javascript-eval.html

  • eval() - evil, but works.
  • setTimeout()

    setTimeout(" function ", 0); 
    
  • new Function()

    var codeToExecute = "My.Namespace.functionName()";
    var tmpFunc = new Function(codeToExecute);
    tmpFunc();
    
  • document.write

    document.write('<script> JS </script>')
    
  • data URI

    var s = document.createElement('script');
    s.src = 'data:text/javascript,' + encodeURIComponent('alert("lorem ipsum")')
    document.body.appendChild(s);
    
Community
  • 1
  • 1
Jens A. Koch
  • 39,862
  • 13
  • 113
  • 141
  • Thanks for your help! Is there a way to dynamically update the function content? – Flo Ragossnig Jan 04 '15 at 16:58
  • You could insert the function string, when you generate the array in PHP and then json_enconde that. Or you can insert a placeholder, like %function-template% and then use str_replace() on the JSON string to insert your function string. `["function":"%function-template%"}]` becomes `["function":"xxx"]` – Jens A. Koch Jan 04 '15 at 17:52
  • This doesn't really solve the problem as if I simply insert the function string and later json_encode the array, it will not execute the function as it is still just a string. If I insert a placeholder and str_replace it it will create a parse error when returned to the Ajax function. – Flo Ragossnig Jan 04 '15 at 22:50
  • Ok, let's see. Puh.. need more space to explain. I will append my answer. – Jens A. Koch Jan 05 '15 at 10:54
  • 1
    Thank's for the answer! I used new Function(). – Flo Ragossnig Jan 06 '15 at 11:01
0

JSON cannot contain functions like this. You can however use Javascript to inject the function into the DOM, so what you want to do is skip the str_replace part and simply create a <script type="text/javascript"></script> element in the DOM and insert the data.function property into there.

Sven
  • 5,155
  • 29
  • 53