I'm working with CakePHP 1.3.7 and I'm trying to do the following:
On a given page, the user can click a link (or image, or button, doesn't matter) that passes a parameter which is saved into a database. BUT, all this, without refreshing the page.
I've been doing some research and I believe I need to use AJAX as well to acomplish this. However, I can't find the a good example/explanation on how to do it.
I think that the idea is to create the link using AJAX, which calls the controller/action that would receive the variable as a parameter and performs the operation to save it in its corresponding field/table of the DB.
Does anyone have a small example of what I want to do? Or maybe point me to some tutorial that explains it... Thanks so much in advance!
EDIT
Well, thank you guys for your replies. THey're not working directly, but I think I'm getting closer to what I want. Here's what i'm doing now:
I have this code in my view:
<div id="prev"><a>click me</a></div>
<div id="message_board"> </div>
I call this JS file:
$(document).ready(function () {
$("#prev").click(function(event) {
$.ajax({data:{name:"John",id:"100"}, dataType:"html", success:function (data, textStatus) {$("#message_board").html(data);}, type:"post", url:"\/galleries\/add"});
return false;
});
});
And my add action in my galleries controller looks like:
function add() {
$this->autoRender = false;
if($this->RequestHandler->isAjax()) {
echo "<h2>Hello</h2>";
print_r($this->data);
$this->layout = 'ajax';
if(!empty($this->data)) {
$fields = array('phone' => 8, 'modified' => false);
$this->User->id = 6;
$this->User->save($fields, false, array('phone'));
}
}
}
When clicking on the '#prev' element, I get a response from the add action, I know because the text 'Hello' is printed inside #message_board. And it does this without refreshing the page, which is why I need. My problem is that I can't make the $.ajax() function to send any data, when it gets to the controller the $this->data is empty, so it never goes inside the if that saves the info to the database (right now it's saving just an easy thing, but I will want it to save the data that comes from the view).
Can anyone see what am I doing wrong? How can I send the data to the controller?
CakePHP does not matter, most of the code you would need for this would be at clientside. Implementing AJAX by yourself is a pain in the $, so you really want to use a library; currently the most popular is probably jQuery. There's a bunch of examples on their AJAX page: http://api.jquery.com/jQuery.ajax/
So, assuming you have something like this in the document:
<form id="s">
<input id="q"/>
<input type="submit" href="Search!"/>
</form>
<div id="r"/>
you can put this in the JavaScript:
$('#s').submit(function(evt) {
evt.preventDefault();
$.ajax({
url: 'foo.php',
data: {
query: $('#q').val()
},
success: function(data) {
$('#r').html(data);
}
});
return false;
});
Then your foo.php only needs to return the fragment HTML that would go into the div#r.
EDIT: I forgot to stop the submit :( Thanks to #Leo for the correction.
EDIT: I can see what your confusion is about. You will not get a data. I haven't worked with CakePHP, but I assume $this->data is what you'd get from $_REQUEST['data']? You don't get that on the server. data is a hash of what is getting submitted; you will directly get the $_REQUEST['name'] and $_REQUEST['id'] (which, I assume, translate into CakePHP as $this->name and $this->id).
You need to add
$('#s').submit(function(evt) {
evt.preventDefault();
To prevent a page refresh, as in Amadans answer just refer to your controller/ action in the url variable
$('#s').submit(function(evt) {
$.ajax({
url: '/patients/search/',
data: {
query: $('#q').val()
},
success: function(data) {
$('#r').html(data);
}
In the patients/add controller action make sure you return a valid result ( in json is good )
Related
I would like to be able to keep the parameter it in url after the click.
When I'm on the offline page I have this url:
http://localhost:3000/feliway-coupon?src_org=166
And I would like to have the same one once connected. However, the parameters do not remain. And that gives this once connected :
http://localhost:3000/feliway-coupon
And I would just like this parameter (src_org=166) to remain
HTML:
<button ng-click="$ctrl.onConnect()">connect</button>
JS:
onConnect() {
const search = this.$location.search().src_org
if (search === '166') {
this.$location.state().search('src_org', '166')
}
}
I wanted to know if $location could solve this problem? Or by another method but without using ui-router or ng-route.
I create a function in CakePHP controller class to process AJAX request. But I found different result when using CakePHP function and simple (Non-MVC) PHP code. My problem is when it goes to submit() process. When using CakePHP function it returns blank page.
Here is the AJAX request code that I use:
var userdata = {username : $("#UserUsername").val()};
$.ajax({
type:'POST',
url: 'http://localhost/mycakephp/users/login',
data: userdata,
success: function(data){
if(data==0){
alert("empty");
} else {
$("#UserLoginForm").submit();
}
}
});
But when I pointed the url to external site like this:
url: 'http://localhost/test/test.php'
Which test.php is a simple PHP code to handle request, submit process works fine.
Sorry, but your code looks bizarre to me: There you have a login form, you send using ajax your username calling the Controller "UsersController" / function "login", and when you have an answer from this function, then you submit again another form ("#UserLoginForm") and send it again to the controller? You are sending two forms! Most probably this is not what you want. Correct me if I am wrong.
I guess what you want is just submit the UserLoginForm and wait for an answer, whatever it is OK or NotOK, and finally show this result.
You have two options to do this: either CakePHP way or your own ajax code using JQuery. I like to use both, depending on the user experience I want to get.
1. CakePHP way
Include JS Helper in the controler using the public $helpers = array('Js'); and then create a Form in the view, using the submit from the JsHelper.
View
<?php
echo $this->Form->create();
echo $this->Form->input('username');
echo $this->Form->input('whatever');
// use the Js submit from JsHelper
echo $this->Js->submit('Send', array(
'url'=> array('controller' => 'users', 'action' => 'login'),
'update' => '#update_ajax_div',
));
echo $this->Form->end();
// Next line is actually very important in order
// to print the automatically created ajax code from JsHelper.
echo $this->Js->writeBuffer();
?>
<div id="update_ajax_div">
this will be overwritten after submit.
</div>
Controller
In the controller all the data in the form will be sent in the $this->data[] array. For example $this->data['Users'] if this is the Model being used in the Form->create().
The code is pretty standard, except the layout, which must be set to ajax.
public function login(){
// print the data being sent
$dataFromAjaxLink = $this->data[];
$v = var_export($dataFromAjaxLink, true);
$this->log("Ajax log: ".$v, 'debug');
if(isset($this->data['User'])){
$user_name = $this->data['User']['username'];
...
// do your stuff
}
// the answer must be ajax layout
$this->layout = "ajax";
// I like to use elements when using ajax, it keeps the folders clean
// in this example /app/View/Elements/display_ajax_result.ctp
$this->render('/elements/display_ajax_result');
}
The content of this element will be printed in the div "update_ajax_div"
2. Pure Ajax way
The second version is done by manually typing ajax code in <script> tags. This solution gives you more freedom, allowing you to do more complex stuff, but the code is not so clean!
The following JQuery code must be inside some button/div/whatever event...
$(document).ready(function(){
$(".someButton").click(function() {
// create the json data to be sent
var username = $(....).val();
var dataString = ...
// call ajax
$.ajax({
type: "POST",
// never type full paths as in your example. They are sources of errors!
url: "<?php echo $this->Html->url(array(
"controller" => "users",
"action" => "login")); ?>,
data: dataString,
cache: false,
success: function(html){
$("#update_ajax_div").html(html);
}
});
...
The controller is the same as the CakePHP way. Remember to add ajax layout.
I hope it helps you.
According to your comment, if you use the SecurityComponent, you could disable POST validation on the login action. In your UsersController, add this:
function beforeFilter()
{
parent :: beforeFilter();
switch($this->request->params['action'])
{
case 'login':
$this->Security->validatePost = false;
$this->Security->csrfCheck = false;
break;
}
}
But off course you loose the value added by the SecurityComponent on the login action.
I've noticed that some web sites offer Ajax-ian search that refreshes the URL and displays the GET params used, for example:
someapp.com/search/Tokyo?price_min=80&price_max=300
As a result of an Ajax GET request.
I want to know how can I accomplish this by using Backbone.js, I understand that by using backbone's push state this may be possible, am I right?
How could I define a route like that (let's say the same case, scoped to /search) for a Place model for example?
Where would I do this? in a Router or in a Model?
I appreciate all the answers regarding this topic. And I apologize in advance for not providing any code, I usually do, but this exercise will be a proof of concept I'd like to make, and I hope backbone is the right tool for the job.
Thank you!
This is a working example of the solution - jsfiddle.net/avrelian/dGr8Y/, except that jsFiddle does not allow Backbone.history.navigate method to function properly.
Suppose, we have a button
<input class="fetch-button" type="button" value="Fetch" />
and a handler
$('.fetch-button').click(function() {
Backbone.history.navigate('posts/?author=martin', true);
});
This is our collection of posts
var Posts = Backbone.Collection.extend({
url: 'api/posts'
});
This is our Router with a custom parameter extractor
var Router = Backbone.Router.extend({
routes: {
'posts/?:filters': 'filterPosts'
},
filterPosts: function(filters){
posts.fetch({data: $.param(filters)});
},
_extractParameters: function(route, fragment) {
var result = route.exec(fragment).slice(1);
result.unshift(deparam(result[result.length-1]));
return result.slice(0,-1);
}
});
It is simplified $.deparam analog. You could use your own instead.
var deparam = function(paramString){
var result = {};
if( ! paramString){
return result;
}
$.each(paramString.split('&'), function(index, value){
if(value){
var param = value.split('=');
result[param[0]] = param[1];
}
});
return result;
};
And finally, instantiation of posts collection and router object
var posts = new Posts;
var router = new Router;
Backbone.history.start();
When a user clicks on the button address bar changes to myapp.com/s/#posts?author=martin. Please, note the sign #. We use a hash query string. Of course, you can use push state, but it is not widespread yet.
Has anyone found a way to intercept the default Form::postLink() forms with Jquery?
I would like the form to work without JS (therefore the postLink).
But with JS enabled I want to intercept the post and call it via AJAX.
<?php echo $this->Form->postLink('Delete', array('action'=>'delete', $prospect['Prospect']['id']), array('class'=>'postLink', 'escape'=>false), __('Sure you want to delete # %s?', $prospect['Prospect']['id'])); ?>
generates:
<form action="/admin/prospects/delete/4f61ce95-2b6c-4009-9b89-07e852b0caef" name="post_4f648f773923b" id="post_4f648f773923b" style="display:none;" method="post">
<input type="hidden" name="_method" value="POST"/>
</form>
<a href="#" class="postLink" onclick="if (confirm('Sure you want to delete # 4f61ce95-2b6c-4009-9b89-07e852b0caef?')) { document.post_4f648f773923b.submit(); } event.returnValue = false; return false;">
Delete
</a>
The main problem is that the js is placed inline here. Therefore always triggers even if I try to intercept the click event (or the post event - tried that too):
<script>
$(document).ready(function() {
$('table.list a.postLink').click(function(e) {
e.preventDefault();
alert('Handler for .submit() called.');
// TODO: do ajax request here and return false
return false;
});
});
</script>
So in the end the form always submits normally and redirects - either ignoring any ajax call (catching the form submit) or posting/redirecting regardless of an ajax call just made (catching the click event).
I would like to delete this record via AJAX and - if successful - just remove that table row from DOM. It would be great if one doesn't have to modify all 300+ "delete buttons" in the application for it, though.
If everything fails I could probably still override the FormHelper (extend it and alias it). But I was hoping on a less invasive solution here.
I know this is old, but for any of those searching:
You need to first remove the 'onclick' attribute added to the delete
link.
Then, you add a .click function to the delete link
You need the url (which can be hardcoded or retrieved from the form, which is always the prev element in cakephp Form->postLink
Here is the code:
$(function(){
$('a:contains("Delete")').removeAttr('onclick');
$('a:contains("Delete")').click(function(e){
e.preventDefault();
var form = $(this).prev();
url = $(form).attr("action");
$.post(url);
return false;
});
});
jymboche - what a genius
why didnt i think of it myself?
Well, here is the modified answer of yours:
$(document).ready(function() {
$('table.list a.postLink').removeAttr('onclick');
$('table.list a.postLink').click(function(e) {
e.preventDefault();
if (!confirm('<?php echo __('Sure?')?>')) {
return false;
}
var form = $(this).prev();
var url = $(form).attr("action");
var tr = $(this).closest('tr');
url = url + '.json';
$.post(url).success(function(res) {
if (res.error) {
alert(res.error);
return false;
}
tr.fadeOut(200);
}).error(function() {
alert("Error");
})
return false;
});
});
This is for future reference only.
I will still accept your answer as you gave me the right idea.
jymboche's solution helped me to get to a working answer, but it didn't work fully for me, due to some security cake plugins that I have installed.
Here's what worked for me:
$(function(){
$('a:contains("Delete")').removeAttr('onclick');
$('a:contains("Delete")').click(function(e){
e.preventDefault();
var form = $(this).prev();
var url = $(form).attr("action");
$.ajax({
type: 'POST',
cache: false,
url: url,
data: $(form).serialize(),
success: function(msg) {
// do any extra calls you need to refresh the page
}
});
return false;
});
});
Just an idea, haven't tested it:
<?php echo $this->Form->postLink('Delete', array('action'=>'delete', $prospect['Prospect']['id']), array('class'=>'postLink', 'onclick' => false, 'escape'=>false), __('Sure you want to delete # %s?', $prospect['Prospect']['id'])); ?>
Couple of issues with the current solutions...
First and foremost, the postLink method requires Javascript to be enabled. From Cake's doco:
"Creates an HTML link, but access the url using method POST. Requires javascript to be enabled in browser."
If javascript is disabled, you just get the exact same output - a hidden form, and a link that tries to submit that form with Javascript (but fails, since JS is turned off).
Second, although it will work, it seems weird to use the postLink method, and then use Javacript to undo precisely all the magic that the postLink method creates.
If you want a non-javascript friendly solution, all you have to do is create a regular form that points to the delete method, and put a link below it like this:
<?php echo $this->Form->create('DefaultAvailability', array('url' array('action' => 'delete', $myRecord['ModelName']['id'])));?>
<?php echo $this->Form->end('Delete (no JS)');?>
<a href='#'>Delete (With JS, use AJAX)</a>
Then, for cases when there's no javascript, hide the link below, and the form will work as normal.
For cases when there is javascript, hide the form, and use javascript to make your delete link trigger a submit on your delete form, and handle that submit with ajax.
I've translated the previous jQuery version to Mootools
//We start with foreach, to select all ajax buttons on the page
$$('.ajaxbutton').each(function(item,index){
//remove onClick
item.removeProperty('onclick');
//attach click event
item.addEvent('click', function(e){
//stop click (in my case the form is inside another link)
e.stop();
var form = item.getPrevious();
url = form.get('action');
new Request({
url: url,//url
method: 'post',//method
onSuccess: function(responseText){
//This is not required, we are dimming the button on success
item.getParents()[0].tween('opacity',0.5)
}
}).send();
return false;
});
})
I have an Ext.Panel with a listener set to 'afterrender'. The callback function is a small ajax code which checks an url, grabs it's contents and add it to the panel. Problem is, the content does not get insterted. If I use the same insert code right above the ajax call, it works. Here's my callback function:
Not working:
function afterrenderCallback () {
// This does not work
var logPanel = Ext.getCmp('aP_ServerLogs');
Ext.Ajax.request({
url: AP_ROOT_URL + '/index.php?r=server/logs',
success: function (r) {
logPanel.add({
html: 'dummy html i don\'t care about the response'
});
}
});
}
Working:
function afterrenderCallback () {
// This does work
var logPanel = Ext.getCmp('aP_ServerLogs');
logPanel.add({
html: 'dummy html i don\'t care about the response'
});
}
You might need to call doLayout() on the panel. However check out Ext.Updater:
http://dev.sencha.com/deploy/dev/docs/?class=Ext.Updater
Panels have this automatically such as:
var panel = new Ext.Panel({
});
panel.body.load(...);
panel.body.update(...);
I'd suspect the callback isn't getting called. You could add a failure case with a simple alert call to check it's not going down that path.
However probably better, similar to what #Lloyd said, you should look at the autoLoad config property.
The autoLoad config is what you want, as mentioned. I wanted to add that doing a logPanel.add({...}) just to insert markup is not appropriate, even though it "works". There is no reason to nest a panel within a panel for this. If you are loading HTML content like this you'd preferably do logPanel.body.update('content');.
As #bmoeskau says, the autoLoad config is what we need. It took me quite a while to find the correct syntax though. So here is an example on how to define such a panel with ajax content:
Ext.define('MyApp.view.aP_ServerLogs', {
extend : 'Ext.panel.Panel',
id: 'aP_ServerLogs',
loader: {
url: AP_ROOT_URL + '/index.php?r=server/logs',
autoLoad: true
}
});