AJAX

Uray Web Library (UWeb)

Uray M. János © 2018-2024

AJAX is a technique that allows the webpage to refresh parts of itself from the server asynchronously, without reloading the full page. It stands for Asynchronous JavaScript and XML (though UWeb does not use XML).

UWeb provides tools to make AJAX easy and efficient to use. With these tools, there is no need to write any JavaScript code to use AJAX, the only programming language needed is PHP. Furthermore, the same PHP code that generated the page initially can later replace parts of it by AJAX. This avoids code duplication without sacrificing page load time, because the initial page can still be constructed entirely on the server. See the Overview and the examples for more details.

Note that certain understanding of the basics of UWeb, such as generating a page, actions, identifiers, forms is recommended before deeper study of this documentation page.

Note: this documentation page describes both client-side and server-side functionalities of UWeb. The latters, i.e. the PHP functions and classes are mostly defined in ajax.php (except where noted), but this file is automatically included when an AJAX query runs, so it is rarely needed to explicitly include this file.

Contents

Contents

1. Overview

1.1. Minimal example

1.2. Example: refreshing

2. AJAX query

2.1. q:query action

2.2. ajax_query() (JS)

2.3. is_ajax_query() (PHP)

2.4. get_ajax_query() (PHP)

3. AJAX data

3.1. Form data

3.2. Query arguments

3.3. data-param

3.4. data-global

3.5. Error handling

3.5.1. input_error() (PHP)

3.5.2. ajax_param_int() (PHP)

3.5.3. ajax_param_error() (PHP)

3.5.4. class ajax_parameter_error

3.5.5. class ajax_query_error

4. AJAX commands

4.1. <!replace>

4.2. <!replace-element>

4.3. <!append>

4.4. <!remove>

4.5. <!hide>

4.6. <!show>

4.7. <!toggle>

4.8. <!popup>

4.9. <!mark>

4.10. <!dropdown>

4.11. <!close>

4.12. <!scroll>

4.13. <!scroll-center>

4.14. <!focus>

4.15. <!download>

4.16. <!fire>

4.17. <!redirect>

4.18. <!eval>

4.19. Custom AJAX commands

4.20. send_command() (PHP)

5. Components in AJAX

5.1. Example

5.2. data-component

5.3. find_component() (JS)

1. Overview

The most important concepts of AJAX in UWeb are the following:

The AJAX functionality of UWeb requires the following setup:

See the examples below.

1.1. Minimal example

Here is a minimal example that shows how AJAX works with UWeb. It demonstrates that no JavaScript needs to be written, and that a single PHP file can both generate the page and handle AJAX queries. (Compare this with the introductory example of generating a page.)

<?php
require_once "uweb/page.php";
require_once "uweb/active.php";
class example extends basic_page {
   function title () { return "Example"; }
   function css () { return "uweb/all.css"; }
   function js ()  { return "uweb/all.js"; }
   function content () {
      echo "<div data-id='box'></div>";
      echo "<p> ",button("Do AJAX", "q:do-ajax")," </p>";
   }
   function q_do_ajax () {
      echo "<!append id='box'>";
      echo "<p> This line was inserted by AJAX. </p>";
   }
}
execute(new example);

This example displays a button ("Do AJAX") with the action q:do-ajax, which instructs UWeb to start an AJAX query when the button is pressed, and call the method q_do_ajax() on the page object (see AJAX query). That method uses the AJAX command <!append> to insert a new line of text into the <div> above the button.

To run this example (and others below), a functional web server is needed with PHP support. The simplest way to set up one is to create a local server, which can e.g. be done by the command-line interface of PHP:
php -S localhost:8000
which creates a local PHP web server for development. Then, copy the example to a PHP file (e.g. example.php) in the directory where the PHP server was started, and put (or symlink) UWeb's directory next to this file as uweb/. Then, this file can be opened in a web browser, e.g.:
http://localhost:8000/example.php

1.2. Example: refreshing

The following example demonstrates that the same piece of PHP code can be used to generate the page and to refresh parts of the page with AJAX.

<?php
require_once "uweb/page.php";
require_once "uweb/active.php";
class example extends basic_page {
   function title () { return "Example"; }
   function css () { return "uweb/all.css"; }
   function js ()  { return "uweb/all.js"; }
   function content () {
      echo "<div data-id='content'>";
      $this->generate_content();
      echo "</div>";
      echo "<p> ",button("Refresh", "q:refresh")," </p>";
   }
   function q_refresh () {
      echo "<!replace id='content'>";
      $this->generate_content();
   }
   private function generate_content () {
      echo "<p> Data from the server: ",rand(1, 100)," </p>";
   }
}
execute(new example);

This example creates a page with some generated content (generate_content()) and a button which updates that part of the page.

The point is that the method generate_content() is used by both the page-generating code (content()) and the AJAX query (q_refresh()). It could be a complicated method that loads lots of data from a database and prints them in nicely formatted HTML. There could be many such generator methods generating different parts of the page, and different AJAX queries would refresh only one of those parts.

This page is efficient for two reasons. First, because it uses AJAX to refresh parts of the page, which is faster than reloading the whole page. Second, because when it is initially loaded from the server, the whole content is already there, and no JavaScript or AJAX is needed to construct it. This is the opposite of how many websites on the Internet work today: they use AJAX also for the initial content, which means that the server first sends an empty page, then the browser requests the JavaScript file, which then downloads the data from the server, and use it to dynamically construct the page content. This requires three round trips between the client and the server, while the above example with UWeb requires only one. Also, if the JavaScript fails to run for some reason (e.g. the browser blocks it), the above page still loads perfectly. UWeb's JavaScript starts only in the background, not affecting the initial loading and rendering of the page, and it is only needed for later actions, such as AJAX queries.

2. AJAX query

An AJAX query is an asynchronous request to the server to execute some code.

In UWeb, there are two ways to run an AJAX query. In most cases, the recommended way is the q:query action, because it can be used without writing any JavaScript. However, if the webpage already has its own JavaScript code, then, in some cases, it might be more convenient to use the function ajax_query().

2.1. q:query action

An UWeb action starting with q: executes an AJAX query. It can have two forms: q:query or q:query(args). This action will call the corresponding method q_query() on the PHP page object or component object responsible for generating that part of the page, passing the arguments if given. In the action string, dashes (-) will be converted to underscores (_), e.g. the action q:some-query will call the method q_some_query().

For example, the button generated by the PHP code
echo button("Button", "q:some-query");
i.e. <button data-action='q:some-query'>Button</button>,
will run an AJAX query, and execute the PHP method q_some_query() on the appropriate PHP object.

2.2. ajax_query() (JS)
function ajax_query (elem, query, params = null);

Execute an AJAX query from JavaScript.

elem (optional): the HTML element where this AJAX query originates from, or null if none. If this is given, then the AJAX query will collect information in the same way as if it were fired from this element by a q:query action, e.g. form data will be collected in the same way, components will be considered in the same way etc.

query: the AJAX query to execute on the server. It is basically the name of the method to execute on the PHP page object, but without the prefix q_, i.e. ajax_query(elem, "xyz") has the same effect as firing data-action='q:xyz' on elem: they both execute the method q_xyz(). This query string can also contain the following:

params (optional): extra POST parameters passed to the server. It is an object with name-value pairs, or null.

2.3. is_ajax_query() (PHP)
function is_ajax_query (); // page.php

Return true if the currently running PHP code was invoked by an AJAX query (from UWeb), or false if it is generating a page.

2.4. get_ajax_query() (PHP)
function get_ajax_query ();

Return the currently executed AJAX query, or null if the PHP code is not running an AJAX query.

The returned string is the query action without the prefix "q:", and it may contain the same elements as the query parameter of ajax_query().

3. AJAX data

This sections lists various ways in which data can be sent to the server in an AJAX query.

The most common methods include using a form and passing arguments to the query method.

Warning: regardless of which method is used, it is essential that any data coming from the client must be validated on the server. See Error handling for more information.

3.1. Form data

If the query action was fired within an UWeb form, then all data in the form will be sent to the server in POST parameters.

More precisely, all elements in the form that have the data-name attribute will be collected, and their values will be sent in POST parameters under the keys defined by data-name. The values will be queried as described at get_value().

If the form has JavaScript checks (e.g. data-check), and one of them fails, then the AJAX query will not be executed.

The following example shows how to send form data to the server, and how to process it.

<?php
require_once "uweb/page.php";
require_once "uweb/active.php";
require_once "uweb/params.php";
class example extends basic_page {
   function title () { return "Example"; }
   function css () { return "uweb/all.css"; }
   function js ()  { return "uweb/all.js"; }
   function content () {
      echo "<div data-form='form' data-action='q:save' data-highlight>";
      echo "<p> First text: ",textbox("text1")," </p>";
      echo "<p> Second text: ",textbox("text2", "Something")," </p>";
      echo "<p> ",label(checkbox("bool"), "Is it true?")," </p>";
      echo "<p> ",submit_button("Save", ["data-id" => "save-button"])," </p>";
      echo "</div>";
   }
   function q_save () {
      $file = fopen("database.txt", "w");
      fwrite($file, post_param_str_opt("text1")."\n");
      fwrite($file, post_param_str_opt("text2")."\n");
      if (post_param_bool("bool"))
         fwrite($file, "It is true.\n");
      fclose($file);
      echo "<!mark id='form:save-button' type='success'>";
      echo "Saved ",filesize("database.txt")," bytes.";
   }
}
execute(new example);

The above page displays a form with two textboxes, a checkbox and a submit button. When the button activates the action of the form (q:save), the query method (q_save()) will receive the form data in POST parameters: $_POST["text1"], $_POST["text2"] and $_POST["bool"] (the latter being either "0" or "1").

For security (and convenience), instead of using $_POST[...] directly, it is recommended to use the wrappers defined in params.php (post_param_int() etc.), which check for errors automatically and return the parameter in the requested type. See Error handling for more information.

3.2. Query arguments

If the query action contains arguments, i.e. it is of the form
q:query(args),
then they will be passed to the appropriate query method q_query().

Query arguments can be useful to implement multiple different actions by the same query method, or to remember parameters from the previous executions of the PHP script.

args: a comma-separated list of arguments, which will be passed to the method as strings. Neither the whole string nor any argument is allowed to be empty.

For example, data-action='q:method(2,red)' will call q_method("2", "red") on the appropriate object.

Like for any data coming from the client, the arguments must be validated by the server (see Error handling). For query arguments, it is best done by the functions ajax_param_error() and ajax_param_int(). For example:

function q_method ($number, $color) {
   ajax_param_int($number, 0); // $number is now an int
   if ($number > 100) ajax_param_error(0);
   switch ($color) {
      case "red":   /* handle red */   break;
      case "green": /* handle green */ break;
      case "blue":  /* handle blue */  break;
      default: ajax_param_error(1);
   }
}

The above query will work correctly when it is invoked by e.g. the query action q:method(2,red), but it will fail for any of the following: q:method(5,notcolor), q:method(-2,green), q:method(200,blue) or q:method(notint,red), and it will not be invoked at all for q:method(,blue), q:method(3,) or q:method().

3.3. data-param
data-param='name1=value1;name2=value2;...'

This UWeb attribute provides a simple way to send additional parameters in an AJAX query without (or in addition to) a form.

The value of data-param is a list of name=value pairs separated by semicolons (;). These values will be sent to the server in addition to any other data that is sent in other ways (e.g. form data).

For example, if a button that invokes an AJAX query has data-param='x=2;yz=apple', then the server-side PHP code will see $_POST["x"] == 2 and $_POST["yz"] == "apple".

Requirement: $has_param if the website generates its own JavaScript

3.4. data-global
data-global='global-id?'

This UWeb attribute defines a group of data that will always be submitted by any AJAX query on the page, regardless of where they are and which form they are in, if any.

The data-global element contains data in the same way as a form does, i.e. elements with the data-name attribute, with corresponding values (e.g. by data-value, see get_value()). Or, if only a single piece of data is needed, the data-global element itself can have the data-name and the value.

global-id (optional): if given, the data-name names will be prefixed by this name, separated by a colon, as in global-id:name. If this attribute is empty, then the names will be sent directly.

Examples:

<!-- Example #1: single value, accessible as $_POST["x"] in the server-side PHP code -->
<div style='display:none' data-global data-name='x' data-value='12'></div>
<!-- Example #2: multiple values, accessible as $_POST["number"] and $_POST["color"] -->
<div style='display:none' data-global>
   <span data-name='number' data-value='2'></span>
   <span data-name='color' data-value='red'></span>
</div>
<!-- Example #3: multiple values with a group id,
     accessible as $_POST["global:number"] and $_POST["global:color"] -->
<div style='display:none' data-global='global'>
   <span data-name='number' data-value='2'></span>
   <span data-name='color' data-value='red'></span>
</div>

Note: hidden_params() can be used to generate the above HTML codes.

Requirement: $has_global if the website generates its own JavaScript

3.5. Error handling

It is essential that any data coming from the client must be validated on the server. The JavaScript checks (e.g. data-check) are only for the convenience of the user, but a secure server cannot assume anything about the incoming data, nor whether the client has actually run UWeb's JavaScript code (without modification, or at all). By the design of the web, the user has complete control over what happens on the client side of a webpage.

In UWeb, error handling can be done either in an explicit and user-friendly way, or in a mostly automatic but less user-friendly way, depending on whether a user with good intentions is likely to run into that error. For example, if the user entered an invalid value in a textbox, it should be reported in a user-friendly way. But if the invalid data comes from the hidden parts of the page generated by the same PHP code (e.g. query arguments or data-param), then a detailed, user-friendly error message is not very useful to the user, because it is either the result of a bug in the PHP code, or of a malicious interaction from the client. The same is true if there were already checks like data-check on the client, because then the user-friendly error handling was already done on the client, and the server-side checks are only for security.

User-friendly error messages can be created e.g. in the following ways:

More automatic but less user-friendly error handling can be done e.g. in the following ways:

3.5.1. input_error() (PHP)
function input_error ($errors, $form = null);

Print error messages in popup marks next to form input elements. This function should be called from an AJAX query method. It sends the errors to the client using the AJAX command <!mark>, and then terminates the PHP script.

$errors: associative array of errors. The keys are the identifiers (e.g. data-name or data-id) of the form elements for which the errors are displayed, and the values are the error messages in HTML.

$form: the identifier (data-form attribute) of the form. The default value is the form where the current query originates from. If there is no such form, then $form must be specificed.

For example:

<?php
require_once "uweb/page.php";
require_once "uweb/active.php";
require_once "uweb/params.php";
class example extends basic_page {
   function title () { return "Example"; }
   function css () { return "uweb/all.css"; }
   function js ()  { return "uweb/all.js"; }
   function content () {
      echo "<div data-form='form' data-action='q:send'>";
      echo "<p> Name: ",textbox("name")," </p>";
      echo "<p> E-mail address: ",input("email", "email")," </p>";
      echo "<p> ",submit_button("Send")," </p>";
      echo "</div>";
   }
   function q_send () {
      $name = post_param_str_opt("name");
      $email = post_param_str_opt("email");
      $errors = [];
      if ($name == null) {
         $errors["name"] = "Missing";
      } else if (strlen($name) > 100) {
         $errors["name"] = "Too long";
      }
      if ($email == null) {
         $errors["email"] = "Missing";
      } else if (!check_email_address($email)) {
         $errors["email"] = "Invalid";
      }
      if ($errors) input_error($errors);
      // ... process the data ...
      echo "<!popup>";
      echo "<p> Thank you! </p>";
      echo popup_ok();
   }
}
execute(new example);

Note that the example uses post_param_str_opt(), not post_param_str() (opt stands for optional), so that a missing value can also be reported by input_error(), which is more user-friendly than the exception thrown by post_param_str().

3.5.2. ajax_param_int() (PHP)
function ajax_param_int (&$value, $idx = null);

Convert an AJAX query parameter to an integer, or report error if it cannot be done.

$value: a string that contains an unsigned integer value, and it will be converted to an integer with the same value.

$idx (optional): the zero-based index of the parameter, for error reporting.

Exception: ajax_parameter_error if $value is not an unsigned integer.

See the example at Query arguments.

3.5.3. ajax_param_error() (PHP)
function ajax_param_error ($idx = null);

Throw an exception of ajax_parameter_error, indicating that an AJAX query parameter is invalid or missing.

$idx (optional): the zero-based index of the parameter, for error reporting.

See the example at Query arguments.

3.5.4. class ajax_parameter_error

class ajax_parameter_errorinternal_errorbasic_errorException

This PHP exception class is thrown by UWeb if an AJAX query parameter is invalid, e.g. by ajax_param_error() or ajax_param_int().

3.5.5. class ajax_query_error

class ajax_query_errorinternal_errorbasic_errorException

This PHP exception class is thrown by UWeb if an AJAX query action is invalid, i.e. the appropriate query method (q_*()) is missing, the component is missing, or the syntax of the query string is invalid.

4. AJAX commands

The server that executes an AJAX query, i.e. a q_query() method, can send data back to the client using AJAX commands, which are HTML-like tags of the form <!command>, potentially followed by HTML content, and they can modify the page or execute certain actions on the client side.

The output of the query (printed by e.g. echo), if not empty, must have the following structure:

<!command1 attr='value' ...> ...content1...
<!command2 attr='value' ...> ...content2...
...

For example:

<!replace id='some-elem'>
<p> New content of the element </p>
<!hide id='elem-to-hide'>
<!mark type='success' id='button'>
Query successfully completed.

The following subsections document all commands supported by UWeb. It is also possible to define custom commands.

Syntactically, an AJAX command has the following parts:

Many AJAX commands act on a specific HTML element of the page. This element is specified by the command's id='...' attribute, which refers to the HTML id attribute of the element.

Note: if the output of the query starts with anything else than an AJAX command, then UWeb treats it as an error in the PHP code, and displays an error message to the user.

4.1. <!replace>
<!replace id='elem'> new content

This AJAX command replaces the content of an element with the given new HTML content.

The element itself, including its attributes, will remain intact, only its inner content is replaced.

4.2. <!replace-element>
<!replace-element id='elem'> new HTML element

This AJAX command replaces an HTML element with the given new element.

The content of this command is exactly one HTML element (on the top level), i.e. there can be no text or element before or after it (only inside it).

4.3. <!append>
<!append id='elem'> new content

This AJAX command appends the given new HTML content to the end of the element.

4.4. <!remove>
<!remove id='elem'>

This AJAX command removes the given HTML element from the page.

4.5. <!hide>
<!hide id='elem'>

This AJAX command hides the given HTML element, by adding the CSS class off to the element (see data-action='hide(id)').

4.6. <!show>
<!show id='elem'>

This AJAX command shows the given HTML element, by removing the CSS class off set by <!hide> above.

4.7. <!toggle>
<!toggle id='elem'>

This AJAX command toggles the visibility of the given HTML element, i.e. hides it (like <!hide>) if it is visible, and shows it (like <!show>) otherwise.

4.8. <!popup>
<!popup> popup content

This AJAX command opens a popup dialog box with the given content. See show_popup() for more information.

Optional attributes for <!popup>:

Example (PHP):

echo "<!popup class='small'>";
echo "<p> This is some information. </p>";
echo popup_ok();
4.9. <!mark>
<!mark id='elem'> mark content

This AJAX command displays a little popup mark with the given content, next to the element with the given id. See show_mark() for more information.

Optionally, the following attribute can be given for <!mark>:

type='type': the type of the mark, e.g. error, success. See show_mark() for more information.

4.10. <!dropdown>
<!dropdown id='elem'> new content

This AJAX command opens a dropdown box next to the element with the given id. See show_dropdown() for more information.

Optionally, it supports the attributes type='type' and dir='dir', which have the same meaning as the parameters of show_dropdown() with the same name.

Requirements: $has_dropdown (JS) and $has_dropdown (CSS) if the website generates its own JavaScript and/or CSS

4.11. <!close>
<!close id='elem'>

This AJAX command closes the popup box, dropdown box or mark that contains the given HTML element.

See also data-action='close'.

4.12. <!scroll>
<!scroll id='elem'>

This AJAX command scrolls the view vertically to the given HTML element. If it is already fully visible, nothing happens.

This is equivalent to calling scroll_to() with that element and with no mode.

4.13. <!scroll-center>
<!scroll-center id='elem'>

This AJAX command scrolls the view vertically to the center of the given HTML element.

This is equivalent to calling scroll_to() with that element and with mode == "center".

Requirement: $has_scroll_modes if the website generates its own JavaScript

4.14. <!focus>
<!focus id='elem'>

This AJAX command gives the focus to the given HTML element, i.e. make it receive subsequent keyboard events.

This is equivalent to calling the JavaScript function focus() on that element.

4.15. <!download>
<!download filename='filename'> file content

This AJAX command asks the user to save a new file on the computer, with the given content.

filename='filename': the suggested filename for the user.

Requirement: $has_save_file if the website generates its own JavaScript

4.16. <!fire>
<!fire data-action='action'>

This AJAX command fires an action in the same way as the data-action attribute for elements, except that no HTML element (button etc.) is involved in this command.

This command accepts the same actions as the data-action attribute for elements, except that any reference to "the fired element" in the descriptions must be ignored, i.e. identifiers that are "relative to the fired element" must be instead given as full identifiers, and any function that would get "the fired element" gets null instead.

4.17. <!redirect>
<!redirect> URL

This AJAX command redirects the browser to the given URL (which can be absolute or relative).

Note: it is recommended to use the higher-level PHP function redirect() instead.

4.18. <!eval>
<!eval> JavaScript code

This AJAX command simply evaluates the given JavaScript code.

4.19. Custom AJAX commands

The webpage can define new AJAX commands in addition to the built-in ones described above, by defining a JavaScript function in one of the following forms:

function r_command (resp);
function r_command (resp, elem);

command: the name of the <!command>, with dashes (-) converted to underscores (_), e.g. the command <!some-command> can be defined by the function r_some_command().

resp: object containing information about the command:

elem: the HTML element referred to by the id attribute of the command. If the function expects a second parameter (elem), then the command is required to have the id attribute, and it must refer to the id of an existing HTML element on the page, otherwise UWeb displays an error message. Therefore, the function can assume that elem is always a valid HTML element.

4.20. send_command() (PHP)
function send_command ($command); // page.php

Insert an AJAX command (<!command>) at the end of a query.

$command: string that contains zero or more AJAX commands.

The point of this function is that it works both in an AJAX query and when generating the page. In the former case, it is equivalent to 'echo $command;' at the end of the query, but when generating the page, it causes the client to execute these commands right after loading the page, without actually performing any AJAX query.

Requirement: $has_send_command if the website generates its own JavaScript

5. Components in AJAX

It is not necessary that all AJAX queries of a page are handled by the page object. On larger webpages, it makes sense to delegate certain parts of the page to different objects called components. UWeb makes this convenient by allowing AJAX queries to be handled by the same component that generated that part of the page.

For more information about components in general, see Basic page: Components.

To let UWeb know which query action originated from which component, if any, and so which object's q_query() method is to be called, the following rules must be followed:

See the example below.

5.1. Example

The following example demonstrates the usage of components with AJAX queries. It shows a page with two components, which are implemented by the same class (part), but they have different codes (part1 and part2). Both components and the page itself have AJAX queries.

<?php
require_once "uweb/page.php";
require_once "uweb/active.php";
class example extends basic_page {
   function title () { return "Example"; }
   function css () { return "uweb/all.css"; }
   function js ()  { return "uweb/all.js"; }
   function content () {
      $this->comp_part1()->content();
      echo "<p> Page content. ",button("AJAX query", "q:query")," </p>";
      $this->comp_part2()->content();
   }
   function q_query () {
      echo "<!popup>";
      echo "<p> This is the AJAX query of the page object. </p>";
      echo popup_ok();
   }
   function comp_part1 () { return new part($this, "part1"); }
   function comp_part2 () { return new part($this, "part2"); }
}
class part extends component {
   function content () {
      echo "<div data-component='$this->code' data-id='$this->code' class='framed-box'>";
      echo "<p> Component '$this->code'. ",button("AJAX query", "q:query"), "</p>";
      echo "</div>";
   }
   function q_query () {
      echo "<!append id='$this->code'>"; // refers to the data-id, not data-component
      echo "<p> This is the AJAX query of the component '$this->code'. </p>";
   }
}
execute(new example);
5.2. data-component
data-component='code'

This UWeb attribute defines which page component this element belongs to. This changes how AJAX queries work inside this element: they invoke the appropriate query member (q_query()) on the component object, not on the page object.

See Components in AJAX above for more information.

5.3. find_component() (JS)
function find_component (elem);

Return the code of the page component that contains the given HTML element.

It is the data-component attribute of the closest ancestor that has one, or null if there is none.