How to make an Ajax request in Joomla Component - ajax

This a screen shot of what I get when I call my ajax request:
How do I run only the task, without printing the whole page? This is my ajax call:
$.ajax
({
type: "POST",
url: "index.php?option=com_similar&task=abc",
data: {
id: id,
name: name,
similar_id: similar_id,
},
cache: false,
success: function(html)
{
$("#flash").fadeOut("slow");
$("#content"+similar_id).html(html);
}
});
});
$(".close").click(function()
{
$("#votebox").slideUp("slow");
});
});

Don't go with exit or die, Joomla! has it's nice way of dealing with this stuff.
The answers below are tested in Joomla! 2.5 & 3 (for 1.5. may work as well).
General
Your URL for the task needs to look like this:
index.php?option=com_similar&task=abc&format=raw
You than create the controller which will use the view, let's say Abc, which will contain the file view.raw.html (identical to a normal view file).
Below you have the code for generate a raw HTML response:
/controller.php
public function abc()
{
// Set view
JRequest::setVar('view', 'Abc');
parent::display();
}
/views/abc/view.raw.php
<?php
defined('_JEXEC') or die;
jimport('joomla.application.component.view');
class SimilarViewAbc extends JView
{
function display($tpl = null)
{
parent::display($tpl);
}
}
/views/abc/tmpl/default.php
<?php
echo "Hello World from /views/abc/tmpl/default.php";
Note: This is the solution I would use if I had to return HTML (it's cleaner and follows Joomla logic). For returning simple JSON data, see below how to put everything in the controller.
If you make your Ajax request to a subcontroller, like:
index.php?option=com_similar&controller=abc&format=raw
Than your subcontroller name (for the raw view) needs to be abc.raw.php.
This means also that you will / may have 2 subcontrollers named Abc.
If you return JSON, it may make sense to use format=json and abc.json.php. In Joomla 2.5. I had some issues getting this option to work (somehow the output was corrupted), so I used raw.
If you need to generate a valid JSON response, check out the docs page Generating JSON output
// We assume that the whatver you do was a success.
$response = array("success" => true);
// You can also return something like:
$response = array("success" => false, "error"=> "Could not find ...");
// Get the document object.
$document = JFactory::getDocument();
// Set the MIME type for JSON output.
$document->setMimeEncoding('application/json');
// Change the suggested filename.
JResponse::setHeader('Content-Disposition','attachment;filename="result.json"');
echo json_encode($response);
You would generally put this code in the controller (you will call a model which will return the data you encode - a very common scenario). If you need to take it further, you can also create a JSON view (view.json.php), similar with the raw example.
Security
Now that the Ajax request is working, don't close the page yet. Read below.
Don't forget to check for request forgeries. JSession::checkToken() come in handy here. Read the documentation on How to add CSRF anti-spoofing to forms
Multilingual sites
It may happen that if you don't send the language name in the request, Joomla won't translate the language strings you want.
Consider appending somehow the lang param to your request (like &lang=de).
New in Joomla 3.2! - Joomla! Ajax Interface
Joomla now provides a lightweight way to handle Ajax request in a plugin or module. You may want to use the Joomla! Ajax Interface if you don't have already a component or if you need to make requests from a module your already have.

If you just want to include the response output in some HTML element, append format=raw to your URL as mentioned above. Then you could have a controller function like this:
function abc(){
//... handle the request, read variables, whatever
print "this is what I want to place in my html";
}
The AJAX response will output everything you printed / echoed in the controller.

Related

simple json response with cakephp

I trying to pass some json to a controller in cakePHP 2.5 and returning it again just to make sure it is all going through fine.
However I getting no response content back. Just a 200 success. From reading the docs I am under the impression that if I pass some json then the responseHandler will the return json as the response.
Not sure what I am missing.
Data being passed
var neworderSer = $(this).sortable("serialize");
which gives
item[]=4&item[]=3&item[]=6&item[]=5&item[]=7
appController.php
public $components = array(
'DebugKit.Toolbar',
'Search.Prg',
'Session',
'Auth',
'Session',
'RequestHandler'
);
index.ctp
$.ajax({
url: "/btstadmin/pages/reorder",
type: "post",
dataType:"json",
data: neworderSer,
success: function(feedback) {
notify('Reordered pages');
},
error: function(e) {
notify('Reordered pages failed', {
status: 'error'
});
}
});
PagesController.php
public function reorder() {
$this->request->onlyAllow('ajax');
$data = $this->request->data;
$this->autoRender = false;
$this->set('_serialize', 'data');
}
UPDATE:
I have now added the following to the routes.php
Router::parseExtensions('json', 'xml');
and I have updated my controller to
$data = $this->request->data;
$this->set("status", "OK");
$this->set("message", "You are good");
$this->set("content", $data);
$this->set("_serialize", array("status", "message", "content"));
All now works perfectly.
A proper Accept header or an extension should to be supplied
In order for the request handler to be able to pick the correct view, you need to either send the appropriate Accept header (application/json), or supply an extension, in your case .json. And in order for extensions to be recognized at all, extension parsing needs to be enabled.
See http://book.cakephp.org/...views.html#enabling-data-views-in-your-application
The view only serializes view vars
The JSON view only auto-serializes view variables, and from the code you are showing it doesn't look like you'd ever set a view variable named data.
See http://book.cakephp.org/...views.html#using-data-views-with-the-serialize-key
The view needs to be rendered
You shouldn't disable auto rendering unless you have a good reason, and in your case also finally invoke Controller:render() manually. Currently your action will not even try to render anything at all.
CakeRequest::onlyAllow() is for HTTP methods
CakeRequest::onlyAllow() (which btw is deprecated as of CakePHP 2.5) is for specifying the allowed HTTP methods, ie GET, POST, PUT, etc. While using any of the available detectors like for example ajax will work, you probably shouldn't rely on it.
Long story short
Your reorder() method should look more like this:
public function reorder() {
if(!$this->request->is('ajax')) {
throw new BadRequestException();
}
$this->set('data', $this->request->data);
$this->set('_serialize', 'data');
}
And finally, in case you don't want/can't use the Accept header, you need to append the .json extension to the URL of the AJAX request:
url: "/btstadmin/pages/reorder.json"
and consequently enable extension parsing in your routes.php like:
Router::parseExtensions('json');
ps
See Cakephp REST API remove the necessity of .format for ways to use the JSON view without using extensions.
Output your json data
public function reorder() {
$this->request->onlyAllow('ajax');
$data = $this->request->data;
$this->autoRender = false;
$this->set('_serialize', 'data');
echo json_encode($data);
}

How to Remove/Register Suffix on Laravel Route?

EDIT: See below for my current problem. The top portion is a previous issue that I've solved but is somewhat related
I need to modify the input values passed to my controller before it actually gets there. I am building a web app that I want to be able to support multiple request input types (JSON and XML initially). I want to be able to catch the input BEFORE it goes to my restful controller, and modify it into an appropriate StdClass object.
I can't, for the life of me, figure out how to intercept and modify that input. Help?
For example, I'd like to be able to have filters like this:
Route::filter('json', function()
{
//modify input here into common PHP object format
});
Route::filter('xml', function()
{
//modify input here into common PHP object format
});
Route::filter('other', function()
{
//modify input here into common PHP object format
});
Route::when('*.json', 'json'); //Any route with '.json' appended uses json filter
Route::when('*.xml', 'xml'); //Any route with '.json' appended uses json filter
Route::when('*.other', 'other'); //Any route with '.json' appended uses json filter
Right now I'm simply doing a Input::isJson() check in my controller function, followed by the code below - note that this is a bit of a simplification of my code.
$data = Input::all();
$objs = array();
foreach($data as $key => $content)
{
$objs[$key] = json_decode($content);
}
EDIT: I've actually solved this, but have another issue now. Here's how I solved it:
Route::filter('json', function()
{
$new_input = array();
if (Input::isJson())
{
foreach(Input::all() as $key => $content)
{
//Do any input modification needed here
//Save it in $new_input
}
Input::replace($new_input);
}
else
{
return "Input provided was not JSON";
}
});
Route::when('*.json', 'json'); //Any route with '.json' appended uses json filter
The issue I have now is this: The path that the Router attempts to go to after the filter, contains .json from the input URI. The only option I've seen for solving this is to replace Input::replace($new_input) with
$new_path = str_replace('.json', '', Request::path());
Redirect::to($new_path)->withInput($new_input);
This however leads to 2 issues. Firstly I can't get it to redirect with a POST request - it's always a GET request. Second, the data being passed in is being flashed to the session - I'd rather have it available via the Input class as it would be with Input::replace().
Any suggestions on how to solve this?
I managed to solve the second issue as well - but it involved a lot of extra work and poking around... I'm not sure if it's the best solution, but it allows for suffixing routes similar to how you would prefix them.
Here's the github commit for how I solved it:
https://github.com/pcockwell/AuToDo/commit/dd269e756156f1e316825f4da3bfdd6930bd2e85
In particular, you should be looking at:
app/config/app.php
app/lib/autodo/src/Autodo/Routing/RouteCompiler.php
app/lib/autodo/src/Autodo/Routing/Router.php
app/lib/autodo/src/Autodo/Routing/RoutingServiceProvider.php
app/routes.php
composer.json
After making these modifications, I needed to run composer dumpautoload and php artisan optimize. The rest of those files are just validation for my data models and the result of running those 2 commands.
I didn't split the commit up because I'd been working on it for several hours and just wanted it in.
I'm going to hopefully look to extend the suffix tool to allow an array of suffixes so that any match will proceed. For example,
Route::group(array('suffix' => array('.json', '.xml', 'some_other_url_suffix')), function()
{
// Controller for base API function.
Route::controller('api', 'ApiController');
});
And this would ideally accept any call matching
{base_url}/api/{method}{/{v1?}/{v2?}/{v3?}/{v4?}/{v5?}?}{suffix}`
Where:
base_url is the domain base url
method is a function defined in ApiController
{/{v1?}/{v2?}/{v3?}/{v4?}/{v5?}?} is a series of up to 5 optional variables as are added when registering a controller with Route::controller()
suffix is one of the values in the suffix array passed to Route::group()
This example in particular should accept all of the following (assuming localhost is the base url, and the methods available are getMethod1($str1 = null, $str2 = null) and postMethod2()):
GET request to localhost/api/method1.json
GET request to localhost/api/method1.xml
GET request to localhost/api/method1some_other_url_suffix
POST request to localhost/api/method2.json
POST request to localhost/api/method2.xml
POST request to localhost/api/method2some_other_url_suffix
GET request to localhost/api/method1/hello/world.json
GET request to localhost/api/method1/hello/world.xml
GET request to localhost/api/method1/hello/worldsome_other_url_suffix
The last three requests would pass $str1 = 'hello' and $str2 = 'world' to getMethod1 as parameters.
EDIT: The changes to allow multiple suffixes was fairly easy. Commit located below (please make sure you get BOTH commit changes to get this working):
https://github.com/pcockwell/AuToDo/commit/864187981a436b60868aa420f7d212aaff1d3dfe
Eventually, I'm also hoping to submit this to the laravel/framework project.

Using the return json code

I'me trying to use ajax with php, I have the follogin script in php :
<?php
// this file get the POST infor sent by a AJAX request and will return the value is succesful.
$price['name'] = "Called";
$price['Wheel'] = 75.25;
$price['Tire'] = 50.00;
echo json_encode($price);
?>
and I'm calling this code from my main page in the following way :
$.post("ajax/profileMod.php", {
'lname':lname,
'fname':fname,
'mname':mname,
'language':language,
'title':title,
'ptype':ptype,
'vip':vip,
'vreason':vreason
})
// Retreive the data from the php script
.done(function(data) {
// php code : echo json_encode(array("name"=>"Called!"));
alert(data);
}, "json");
// Stop original behavior
return false;
});
The returniong result from the alert is the following test :
{"name":"Called",Wheel":75.25,"Tire":50}
How can I change this result so I may use it in the following way in javascript EX:
alert(myresult['Name']) ; Would give me "Called".
So I basicly would like a associative array in javascript, but I read somewhere on this forum that you can't have associative array in Javascript, only object...
Please help!
Pass "json" as the last parameter to .post() to tell jQuery to parse the response as JSON.
(or, fix your server to return the correct Content-Type of application/json, and jQuery should do that automatically)
You will then get a Javascript object, allowing you to write
alert(result.name);

Debugging Ajax requests in a Symfony environment

Not sure if SFDebug is any help in this situation. I am making an ajax post using jQuery. Which retrieves JSON data in my action URL and then makes a call to the Model method that executes the action. The part until my action URL, and the jQuery call to it work fine. With the data transmitted from the client to the server well received and no errors being made.
It is the part where it calls the method on the Model that is failing. My jQuery method looks like this:
$.post(url, jsonData, function(servermsg) { console.log(servermsg); }) ;
My server action is like this
public function executeMyAjaxRequest(sfWebRequest $request)
{
if($request->isXmlHttpRequest())
{
// process whatever
$servermsg = Doctrine_Core::getTable('table')->addDataToTable($dataArray);
return $this->renderText($servermsg);
}
return false;
}
The method of concern in the Table.class.php file looks like this:
public function addDataToTable($dataArray)
{
// process $dataArray and retrieve the necessary data
$data = new Data();
$data->field = $dataArray['field'];
.
.
.
$data->save();
return $data->id ;
}
The method fails up here in the model, when renderText in the action is returned and logged into the console, it returns the HTMl for SFDEBUG. Which indicates that it failed.
If this was not an Ajax call, I could debug it by seeing what the model method spat out, but this is a little tedious with Ajax in the mix.
Not looking for exact answers here, but more on how I can approach debugging ajax requests in a symfony environment, so if there are suggestions on how I can debug this, that would be great.
You must send cookie with session ide key via ajax
(Assuming you have XDEBUG configured on the server)
In order to trigger a debug session by an AJAX request you have to somehow make that request to send additional URL parameter XDEBUG_SESSION_START=1. For your example:
$.post(url + '?XDEBUG_SESSION_START=1', jsonData, function(servermsg) { console.log(servermsg); }) ;
You can also trigger it via cookie, but appending URL parameter usually easier.

Make Wordpress Ajax calls work with global variables to reduce database queries

I posted this earlier on wordpress.stackexchange.com. However, never got a reply. Hence, trying my luck here.
I am hereby providing a detailed description of what I need and what I have done for this issue of mine. I am open to any workable solution around what I have done or maybe new suggestions.
I need to make use of user data that is retrieved using the following:
$user_data = get_user_by('login', get_query_var('user_login'));
The above code uses the username passed as a query_var in the URL. All works until here.
I make use of the above code in several Ajax callbacks (handled by admin-ajax.php) on single page load. Since, the site is targeted as a high volume site. All these Ajax requests lead to several database query for the same data. So the obvious idea to save some database queries is to pass the data to a global variable like below:
$_GLOBALS['user_data'] = get_user_by('login', get_query_var('user_login'));
And then use the same in the Ajax callbacks. Here's problem. None of the Ajax callback functions see the global $user_data variable. Before you ask, yes I have declared the global inside callback as well.
So, the obvious answer would be: why not use wp_localize_script and pass the $user_data to the Ajax callback via javascript like bellow:
In PHP:
wp_localize_script('jquery', 'ajaxVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'user_data' => $user_data));
In Javascript:
jQuery.ajax({
url: ajaxVars.ajaxurl,
type:'POST',
async: false,
cache: false,
timeout: 10000,
data: 'action=ajax_callback&user_data=' + ajaxVars.user_data,
success: function(value) {
alert(value);
},
error: function() {
alert(error);
}
});
However, this poses two questions:
Can an object that get_user_by('login', get_query_var('user_login')); returns be handled by wp_localize_script()?
If the answer to above question is yes, then would it not pose a security threat since the object would contain sensitive user information?
To overcome the global variable being not available to Ajax callbacks, I declared it directly in functions.php (without wrapping it inside a function). However, get_query_var('user_login') does not return any data when used directly inside functions.php making this futile exercise (You have to add it inside a function and call it via an action).
So, the question remains: how do I stop making $user_data = get_user_by('login', get_query_var('user_login')); calls for every Ajax request? Or is there a way I could get get_query_var('user_login') to work inside functions.php directly (without wrapping it inside a function) or a workaround?
Or maybe some completely new out of the box thinking?
All these Ajax requests lead to several database query for the same
data. So the obvious idea to save some database queries is to pass the
data to a global variable like below:
$_GLOBALS['user_data'] = get_user_by('login', get_query_var('user_login'));
And then use the same in the Ajax callbacks.
Each request that your application receives, AJAX or otherwise, lives completely in isolation: the code handling the requests does not share any state between them (besides whatever is persisted to a database). A global (or constant, or property, or variable, or anything) you define in one request will never be available to subsequent requests unless you store it somewhere.
There are a number of approaches to reducing the number of queries these requests are creating. One would be to retrieve the required user data on page load and pass it to subsequent requests. E.g.:
var user = 'someUser';
$.get('user-data.php?user=' + user, function(user_data) {
$.ajax('some-endpoint.php', {
type: 'POST',
data: { user: user_data },
success: function() { /* ... */ }
});
$.ajax('some-other-endpoint.php', {
type: 'POST',
data: { user: user_data },
success: function() { /* ... */ }
});
});
Alternatively, if it's the currently logged in user you're working with you can write their details to a JavaScript object on initial page load for use later.
var userData = <?php get_currentuserinfo(); echo json_encode($current_user); ?>;
Another option would be to ensure that the get_user_by results were being cached, either by Wordpress, MySQL or some other caching layer. That way it doesn't particularly matter how many times your code calls the method.
In general if lots of your endpoints are sharing functionality, you could probably stand to refactor some of that code.

Resources