Where can I save my PHP-Files for AJAX?

The best way is to create an ajax folder inside the content folder with an empty text file in it and then create an ajax.php template in templates. That way you can easily access all Kirby functions.

That worked. Thanks!

@texnixe When creating a new page, it might be accessible by Google or other crawlers. Just to be sure it’s not indexed we should force it to have a header error code 404. What is the “correct” way of setting that to this ajax-page?

You can use the toolkit api http://getkirby.com/docs/toolkit/api#header send the header if the request is not an ajax request http://getkirby.com/docs/cheatsheet/request/ajax

Thank you! I added a new related question here:

http://forum.getkirby.com/t/how-do-i-set-up-an-ajax-page/1079

You can access Kirby’s API by including this at the top of the PHP file:

<?php
define('DS', DIRECTORY_SEPARATOR);
require($_SERVER["DOCUMENT_ROOT"] . DS . 'kirby' . DS . 'bootstrap.php');
$kirby = kirby();
$site = $kirby->site();

// do your thing here

Using this, you can keep the PHP file anywhere on the site - no need to clutter up your content and templates folder with something that is neither content nor a template.

1 Like

Hi,

Finally I tried your suggestion and it works, but $site->url() gives the wrong URL.

It is

echo site()->url(); // = http://server/subfolder/assets/ajax/ajax.php

but it should be

echo site()->url(); // = http://server/subfolder

Is it possible to fix this?

Oh and I had to replace $_SERVER["DOCUMENT_ROOT"] with '..' . DS . '..' to make it work on my local test server …

I came to another problem. It seems like I can’t use the snippet() function on these pages …


Edit: The snippet() works but the $site variable inside of it isn’t recognized …

<?php

define('DS', DIRECTORY_SEPARATOR);

require( '..' . DS . '..' . DS . 'kirby' . DS . 'bootstrap.php');

$kirby = kirby();
$site = kirby()->site();
$ajax = kirby()->request()->ajax();


if (!$ajax) {
    echo "<html lang='en'>";

    snippet('head');

// …

and the erronous line in head.php:

<title><?php echo preg_replace('/<.*?>/','',$site->title()) ?></title>

What if you pass the $site variable to the snippet?

snippet('head', array('site' => $site));

Why not just use a plugin and create a function or class for it? At least that’s what I did when I created my AJAX contact form. I used it in conjunction with a custom kirby route that triggers the function in the plugin that handles the AJAX request.

@texnixe: Yes that wrked. But unfortunately the manually created site object has a wring url()

@Targoran: Because I don’t know how :wink: Would be great if you write an extended answer on how you did it.

Okay, I’ll try to explain :smile:

Here’s the javascript that triggers my AJAX script.
I’m handing over the serialized form data and call a custom Kirby route.

$('#form').on('submit', function(e) {
  e.preventDefault();
  var data = $(this).serialize();
  $.ajax({
    type: "POST",
    dataType: "json",
    url: 'api/form/validate',
    data: data,
    success: function(data) {
      // your logic here
    },
  });
});

My custom Kirby route inside site/config.php looks like this:

c::set('routes', array(
  array(
    'pattern' => 'api/form/validate',
    'method' => 'POST',
    'action' => function() {
      $data = validateForm(kirby()->request()->data());
      return response::json($data);
    }
  )
));

The router now passes the form data to the validateForm function which will handle it.

For the validateForm function I created a plugin which lives inside the site/plugins directory.
Here’s the code (stripped down to the essentials)

function validateForm($data) {

  $errors = array();

  if(empty(get('name'))) {
    $errors[] = 'name';
  }
  ...

  if(empty($errors)) {
    $email = email($params);  // send email
  }

  if($email->send()) {
    $send = true;
  } else {
    $emailError = $email->error()->message();
  }

  $successMessage = 'E-Mail successfully sent';

  $data = array(
    'errors' => $errors,
    'successMessage' => $successMessage
  );

  return $data;
}

So the function gets the data, checks if the fields are valid/filled out and then either sends an email or returns an array of errors back to the javascript which then can be displayed in the form. Roundtrip done!

I hope this might come in handy! If anything is unclear, ask away.

9 Likes

Thats looks like a nice solution. I’ll check ist out for my project :slight_smile:

Just wanted to say thanks to @Targoran for the code snippets above, super useful to see it laid out w/ routing and such.

1 Like

Hi folks, did you get this to work? I tried @Targoran’s solution and it seems to mostly work. Except I can’t user the $pages variable. Any Idea why? Here’s my current code for testing (it doesn’t really do much, but it should work even like this):

JS

$('#home .content-tag').on('click', function(){

	event.preventDefault();

	$.ajax({
		type: 'POST',
		url: 'ajaxfunctions/loadcontentbytag',
		dataType: 'json',
	})
		.done(function(response) {

			for (var i = 0; i < response.length; i++) {
				console.log(response[i]);
			};
		})
		.fail(function(x,e) {
		    if (x.status==0) {
		        console.log('You are offline!!\n Please Check Your Network.');
		    } else if(x.status==404) {
		        console.log('Requested URL not found.');
		    } else if(x.status==500) {
		        console.log('Internal Server Error.');
		    } else if(e=='parsererror') {
		        console.log('Error.\nParsing JSON Request failed.');
		    } else if(e=='timeout'){
		        console.log('Request Time out.');
		    } else {
		        console.log('Unknow Error.\n'+x.responseText);
		    }
		});
});

Config:

c::set('routes', array(
  array(
    'pattern' => 'ajaxfunctions/loadcontentbytag',
    'method' => 'POST',
    'action' => function() {
      $data = loadContentByTag(kirby()->request()->data());
      return response::json($data);
    }
  )
));

PHP:

<?php

    function loadContentByTag($data){

        header('Content-type: application/json; charset=utf-8');

        $response = array('test1', 'test2');

        $articles = (string)kirby()->$pages->findByURI('work')->children()->visible()->flip();

        array_push($response, $articles);

        return $response;

    }

?>

It has to be:

pages()->findByURI('work')->children()->visible()->flip();

Or simpler:

page('work')->children()->visible()->flip();

What are you trying to do with the string conversion BTW?

Thanks! That worked. Where are these things documented?

The string conversion was only to see that it worked.

I have another problem now though. I’m getting these errors in the console:

Error.
Parsing JSON Request failed.

[The HTML I'm trying to send is output here]

<br />
<b>Warning</b>:  Cannot modify header information - headers already sent by (output started at /Users/gburning/Websites/portfolio2014/kirby/toolkit/lib/tpl.php:26) in <b>/Users/gburning/Websites/portfolio2014/kirby/toolkit/lib/response.php</b> on line <b>64</b><br />
<br />
<b>Warning</b>:  Cannot modify header information - headers already sent by (output started at /Users/gburning/Websites/portfolio2014/kirby/toolkit/lib/tpl.php:26) in <b>/Users/gburning/Websites/portfolio2014/kirby/toolkit/lib/response.php</b> on line <b>65</b><br />

What do these warnings mean?

Modified PHP:

<?php

    function loadContentByTag($data){

            header('Content-type: application/json; charset=utf-8');

            $contenttag = explode('tag:', $data['tagURL']);

        $response = array();

        $articles = page('work')->children()->visible()->flip();

        if($URLtag = urldecode($contenttag[1])) : 

            $articles = $articles->filterBy('tags', $URLtag, ',');

        endif;

        foreach($articles as $article):

            $content = snippet('article', array('article' => $article));
            array_push($response, $content);

        endforeach; 

        return $response;
    }
?>

Maybe this should be its own topic? My apologies if that’s the case.

All Kirby helpers are documented in the cheat sheet.

The error at the beginning is output by your client side JavaScript code. The warnings at the end mean that the content type header couldn’t be set because there was output before you returned your response object. The issue is in this line, it has to be:

$content = snippet('article', array('article' => $article), true);

The additional true at the end will make the snippet() helper return the HTML code instead of printing it directly.

There may also be some problems introduced by using the header PHP method.

Kirby’s response::json() method takes care of setting appropriate headers, so calling header in the function that returns data to your route/action may be sending another set of headers unnecessarily.

Removing that line should take care of the other “Cannot modify header information” errors you have. (@lukasbestle is correct that the snippet function should be passed true as the last argument to prevent it echoing to the buffer— that will prevent the other header error!)

Setting the header manually shouldn’t be an issue, but you are right that it is not necessary because Kirby sets the header as well.

Removing the header line won’t solve the issue however. The issue is not caused by printing a header, but by printing body text. The error is generated when setting a header, but the body text is always the reason for it. So if you remove the header line, Kirby still won’t be able to set it.
Long story short: Body text is the root cause.