AJAX
Uray Web Library (UWeb)
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.
3.5.3. ajax_param_error() (PHP)
3.5.4. class ajax_parameter_error
The most important concepts of AJAX in UWeb are the following:
q:query
, e.g. on a button, and define a corresponding method q_query()
on the PHP page object. When this action is activated (e.g. the button is pressed), UWeb will execute this method on the server. See AJAX query for more information. data-name
). For more information about this and other methods, see AJAX data. Note that any data coming from the client must be validated on the server, see Error handling. <!command>
, and they can modify the page dynamically or do other actions on the client side. See AJAX commands for more information. The AJAX functionality of UWeb requires the following setup:
$has_ajax
must be enabled. execute()
must be called when creating the page object in PHP (instead of generate()
, which is used when generating a static HTML page). It ensures that the object can both generate the page and handle AJAX queries. See the examples below.
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
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.
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()
.
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.
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:
"query(2,abc)"
(see Query arguments); "comp:query"
; ;
). params
(optional): extra POST parameters passed to the server. It is an object with name-value pairs, or null.
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.
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()
.
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.
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.
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()
.
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
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
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:
input_error()
, which displays error marks for the specified input fields (and automatically terminates the PHP script); <!popup>
with an error message (but do not forget to terminate the PHP script after it, e.g. by exit
); basic_error
(but not from internal_error
), either a predefined one such as page_not_found
or access_denied
, or a custom exception class defined by the website (by default, exception messages are shown in a popup box, see error_message
). More automatic but less user-friendly error handling can be done e.g. in the following ways:
post_param_int()
etc. from params.php
for automatically checking and converting POST parameters (they throw post_parameter_error
if necessary); ajax_param_int()
etc. below for automatically checking and converting AJAX query arguments (they throw ajax_parameter_error
if necessary). 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()
.
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.
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.
class ajax_parameter_error
→ internal_error
→ basic_error
→ Exception
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()
.
class ajax_query_error
→ internal_error
→ basic_error
→ Exception
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.
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:
<!
and >
; >
, in a syntax similar to HTML; >
, which can contain any text, including HTML code, except the two characters <!
. Its meaning depends on the command, see their descriptions below. 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.
<!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.
<!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).
<!append id='elem'> new content
This AJAX command appends the given new HTML content to the end of the element.
<!remove id='elem'>
This AJAX command removes the given HTML element from the page.
<!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)'
).
<!show id='elem'>
This AJAX command shows the given HTML element, by removing the CSS class off
set by <!hide>
above.
<!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.
<!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>
:
class='class'
: CSS class(es) to add to the popup box. The default ones supported by UWeb: 'small'
: fixed small width, 'medium'
: fixed larger width, 'left'
: left-align the text. id='id'
: define the id
attribute of the popup box. permanent
: make the popup box permanent, i.e. do not close it when another popup is opened, except if it has the same id
. (See the data-permanent
attribute for show_popup()
.) Example (PHP):
echo "<!popup class='small'>"; echo "<p> This is some information. </p>"; echo popup_ok();
<!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.
<!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
<!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'
.
<!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
.
<!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
<!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.
<!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
<!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.
<!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.
<!eval> JavaScript code
This AJAX command simply evaluates the given JavaScript code.
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:
resp.command
: the name of the command (e.g. "some-command"
). resp.params
: the attributes (parameters) of the command, as an object with name-value pairs. The values are strings, except for empty arguments, where they are true
. For example, <!some-command attr='value' empty status='I'm OK.'>
gives resp.params = {attr: "value", empty: true, status: "I'm OK."}
. resp.content
: the content of the command, i.e. the string after the <!tag>
of the command until the next command (or the end of the output). 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.
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
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:
component
, defined in page.php
. data-component='code'
on the root element of the content it generates. Note that it can use the inherited property $this->code
for this, e.g. echo "... data-component='$this->code' ..."
. (Note: the attribute data-component
is independent from the data-id
system.) comp_code()
for each component code (e.g. comp_xyz()
), returning the appropriate component object. See basic_page::get_component()
for more information. component::__construct()
(or the component must override the constructor). See the example below.
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);
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.
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.