Some advice when using jquery.ajax()

Suki

  • Posts: 59
Some advice when using jquery.ajax()
« on March 15th, 2013, 01:42 AM »Last edited on March 15th, 2013, 01:50 AM
OK, I found a nice script that pretty much handles mentions in an user friendly way:  @username (it is better than what I had: {username}) the script is: h**p://ichord.github.com/At.js/

The downside is, it requires a previously declared javaScript array for the cript to search the names on, I'm currently using a func to query the database and returns a simple string like {1:'username', 2:'another username'} where the key is the users ID.

Then I pass this string to a JavaScript var by printing the actual string to be handled by the mention script:

Code: [Select]
$context['html_headers'] .= '
<script type="text/javascript"><!-- // --><![CDATA[
var breezeUsers = '. $tools->userMention() .';
// ]]></script>';

This works well, except that it would be a nightmare to print the string if a forum has a huge userbase, of course the query is limited to is_activated and posts > 10 but still it can grow to huge proportions.

What I was thinking is creating a simple SMF action, this action will return the string as a json object, I already had a class that handles all my ajax requests and returns the data as json so adding this wouldn't be an issue, the issue is that no matter how I fetch the string, the mention script doesn't recognize the string as a valid javascript array.

if I sent the data as a json object and use jQuery.parseJSON() on it it screws up the string.

I also tried to serve the response as a JavaScript file since I read that $ajax.() can call and execute javaScript files, however that didn't work either as $ajax.() didn't call the file (console.log told me that the file was fetched and it has a 200 status).

I wanted to use an ajax call because I can control the server response, I can use  some cached entry as response and more importantly, I won't need to print a huge array.

The benefits of using this particular mention script is that it allows me to inject exactly what I want, it is triggered by typing @ or any other character but it can insert back to the textarea whatever you want, I set it up to inject this: @(username, ID)  when an user selects a name from the names list now I have a nice and delimited string that can be parsed and converted to a link easily. No extra queries are needed :)

So, any help handling ajax request would be highly appreciated :) I just basically need a way to retrieve a valid javascript array/object via ajax but all my attempts had failed miserably. Oh, the mention script does support ajax calls, I followed all the exmaples on its doc section but it results in the same errors as my attempts, it doesn't recognize the serve response as a valid array.

live627

  • Should five per cent appear too small / Be thankful I don't take it all / 'Cause I'm the taxman, yeah I'm the taxman
  • Posts: 1,670
A confident man keeps quiet.whereas a frightened man keeps talking, hiding his fear.

Suki

  • Posts: 59

live627

  • Should five per cent appear too small / Be thankful I don't take it all / 'Cause I'm the taxman, yeah I'm the taxman
  • Posts: 1,670

Arantor

  • As powerful as possible, as complex as necessary.
  • Posts: 14,278
Re: Some advice when using jquery.ajax()
« Reply #4, on March 15th, 2013, 04:24 AM »
More importantly, what's the Content-Type of the returned value?

IIRC jQuery.ajax() sniffs the Content-Type header to judge whether it's JSON or XML or whatever and if it isn't set to something indicating JSON, it assumes it's XML instead.
When we unite against a common enemy that attacks our ethos, it nurtures group solidarity. Trolls are sensational, yes, but we keep everyone honest. | Game Memorial

Nao

  • Dadman with a boy
  • Posts: 16,080
Re: Some advice when using jquery.ajax()
« Reply #5, on March 15th, 2013, 12:22 PM »
Don't have time to read it all... Hopefully I didn't misunderstand the question ;)

- Make sure you're not using jQuery 2.0 beta 1 (beta 2 is out, with its own problems too), as it's not correctly parsing data on the end (although it's mostly a problem with XML, I wouldn't trust that version with anything),
- Data received from $.ajax normally doesn't require parsing,
- Also ensure that you're sending the proper headers.

This is part of my code for Wedge... I've simplified it for your needs.

Code: [Select]
<?php
function returnAjax($json)
{
clean_output();
header('Content-Type: application/json; charset=UTF-8');
echo str_replace('\\/''/'json_encode($json));
obExit(false);
}

Don't remember if clean_output() is part of Wedge or SMF, but it's not the point anyway ;)

Suki

  • Posts: 59
Re: Some advice when using jquery.ajax()
« Reply #6, on March 15th, 2013, 02:44 PM »
OK, thanks for the replies, I'm using 1.9.1

This is the method I'm using to sent the response from the server: https://gist.github.com/MissAllSunday/5169914

I'll give this another try today, is json the more appropriate way to send the data back?   I think the problem is that the server is sending this: "{1:'Suki', 2:'manzanita', }" instead of this: {1:'Suki', 2:'manzanita', } thus the mention script treats it like a string.

The mention script fires a bunch of callbacks that I cannot disable, this callbacks expect the data to be a valid js object/array

Nao

  • Dadman with a boy
  • Posts: 16,080
Re: Some advice when using jquery.ajax()
« Reply #7, on March 15th, 2013, 04:07 PM »
Yes, JSON is best. I've been through Hell and back again, and JSON is better than XML, however you view it. It's not better than a plain text response, but if you need multiple entries, obviously it's better :)
So, your server is returning double quotes around the string..? Very odd. I don't see anything that would cause that in your code. (You may want to use the more common 'echo' instead of 'print', though, but it's just a personal preference...)
Also, I'm surprised it would return an extra ',' before the closing }. I mean, this is something that would make it break in IE6 (more recent browsers don't have problems with it though.) And because JSON was created after IE6's release, I'd say they accounted for that. So... I don't really see how json_encode() could generate that.
You could also use a fallback json_encode, I removed that particular code from the sample I posted above to make it simpler for you, but what I do is test for function_exists('json_encode') (which has a 99.99% chance of being enabled in our setups), and if not found, it includes the PEAR version of json_encode, which works extremely well, albeit very slowly.
There's also this quick one: http://snipplr.com/view/13911.22389/
Which I fine-tuned into this:
Code: [Select]
function json_encode_fallback($v)
{
if ($v == null)
return 'null';

if (is_array($v))
{
// Non-associative array..?
if (!count($v) || array_keys($v) === range(0, count($v) - 1))
return '[' . join(',', array_map(__FUNCTION__, $v)) . ']';

foreach ($v as $k => $val)
$v[$k] = call_user_func(__FUNCTION__, $k) . ':' . call_user_func(__FUNCTION__, $val);

return '{' . join(',', $v) . '}';
}

return '"' . addslashes(preg_replace('/(\n|\r|\t)/i', '', strval($v))) . '"';
}

Please note, however, that I ended up using PEAR as fallback, instead of that, because it didn't generate exactly the same code, so it might have compatibility issues with jQuery's parser, too. You're never too careful...

Suki

  • Posts: 59
Re: Some advice when using jquery.ajax()
« Reply #8, on March 15th, 2013, 07:59 PM »Last edited on March 15th, 2013, 08:30 PM
OK, I uploaded the latest branch with the mention script implementation to my site, the url for fetching the json object is this one: http://missallsunday.com/index.php?action=breezeajax;sa=usersmention  strangely enough, all the browsers I tested this url are receiving a php file, this is the only request that is sending a php file as a response as all the others are sending a json response. Could be a cache issue?, a server issue? im on LiteSpeed and sometimes it takes a while to pick up new pages specially if those pages are generated dynamically.

This is the code I'm currently using to send the ajax request and also firing the mention script:

Code: [Select]
jQuery(function(){

var breezeUsers = jQuery.ajax({
type: 'GET',
url: smf_scripturl + '?action=breezeajax;sa=usersmention',
data: '',
cache: false,
dataType: 'json',
success: function(html)
{
return html.data;
}
});
console.log(breezeUsers);
data = jQuery.map(breezeUsers, function(value, i) {
return {'id':i, 'name':value,};
});

jQuery('#content').atwho("@",{
tpl: "<li data-value='(${name}, $msg286996)'>${name} <small>${name}</small></li>",
'data': data,
'choose': "data-value",
'callbacks': false,
});
});

Now, both Opera drangonfly and firebug are telling me this error: name.toLowerCase is not a function  and points out to the jquery.atwho.js file at line 6 in this particular code:  https://gist.github.com/MissAllSunday/5172154

I'm not good with JS but as far as I know, filter is a callback (the script has many callbacks) that gets triggered by default and I don't understand javascript enough to see where the problem is.

I don't know if the problem is generated by my initial object or if the problem is somewhere else, if I hardcoded the var:

var breezeUers = {"1":"Suki","2":"manzanita"};

and pass that to the mention script it does work as intended that is why I assume the issue is on the data that is been sent from the browser or in the way I'm handling that data and pass it over to the mention script.

live627

  • Should five per cent appear too small / Be thankful I don't take it all / 'Cause I'm the taxman, yeah I'm the taxman
  • Posts: 1,670

Suki

  • Posts: 59

Nao

  • Dadman with a boy
  • Posts: 16,080
Re: Some advice when using jquery.ajax()
« Reply #11, on March 16th, 2013, 11:40 PM »
To avoid the toLowerCase crash, use...
if (name.toLowerCase && name.toLowerCase().indexOf(query) >= 0) {

Or, if you like 'clean code' that doesn't compress as well:
if ('toLowerCase' in name && name.toLowerCase().indexOf(query) >= 0) {

If name is not a string but a DOM element, get its class name first, or whatever text is associated with it, like $(name).attr('class').toLowerCase().

All in all, we have a problem: you're a rookie with JavaScript, and I'm close to what you could call an expert in that area.... But I don't have access to your full code and/or environment, and can't even begin with the basics. Thus, it'd take more time for me to fix the bug. And unless you're willing to pay, I don't really see any reasons to devote more than a few minutes to your problem. Don't take it wrong: I don't mind helping fellow developers, but as I understand it, you're writing a mod for SMF, it's competition, and I'd be shooting myself in the foot if there wasn't any compensation in it.
So I guess this is all I can do for you for now... :^^;:

Oracle

  • Posts: 78

Suki

  • Posts: 59

Nao

  • Dadman with a boy
  • Posts: 16,080
Re: Some advice when using jquery.ajax()
« Reply #14, on April 7th, 2013, 07:21 PM »
That was Suki's last post, so I guess she wasn't too thankful. Fair enough ;)

While I'm at it... One of the things I've been working on recently, is a rewrite to get rid of Xml.template.php. In order to do this, I wanted to have returnAjax handle things more gracefully, and in a more instinctive way. However, this requires a (small) rewrite that makes it sound like one of these hastily made PHP 'hacks', which I'm not sure we want in Wedge... There is, however, such a 'convenient function' in Wedge already, it's add_js(), where you can call multiple parameters and they'll be concatenated, so that existing inline JS can easily be transformed to use the function. It's neat, but it's a bit hackish.

So, right now, what returnAjax does is... Takes two parameters... A string and a type... Determines the type... If it's empty, it'll use a text/plain header, otherwise it'll use a MIME type that matches what we asked for, between text, XML and JSON.
Then it initializes the lot, and prints the string, and returns, making sure to bypass the template system but still go through ob_sessrewrite.

That's all good.

Except that if I want to send XML, it gets ugly quickly. The first thing I determined, is that since all XML responses have the <?xml...> header stuff in their content, I could add it automatically on the fly. Which I'm doing. But then I was thinking, doing this is ugly...

Code: [Select]
returnAjax('
<we>
  <something />
</we>', 'xml');

Mostly because the 'xml' is at the end. I don't know, I just don't feel like doing that...
So, I wanted to refactor it to:

Code: [Select]
returnAjax('xml', '
<we>
  <something />
</we>');

And consider that if we have only one parameter, it's a text string, and use $type as the string. And if we have two parameters, $type is the type, and the second parameter is the string. It suddenly makes it a hack...
Also, another thing I wanted to look into, was do things as in add_js, i.e. multiple parameters are concatenated into a single string, allowing for easy conversion of existing echos.
But then it means that the ($type, $string = '') hack is no longer possible, because I could very well be doing returnAjax('xml', ' is something I hate'), and thus Wedge would consider this to be a XML string, instead of a text string split in two... Yes, it's unlikely to happen, but it just feels wrong.

So... Do I just leave the function as it is right now, or... Do something else..?