You are here

function drupal_html_id

Prepares a string for use as a valid HTML ID and guarantees uniqueness.

This function ensures that each passed HTML ID value only exists once on the
page. By tracking the already returned ids, this function enables forms,
blocks, and other content to be output multiple times on the same page,
without breaking (X)HTML validation.

For already existing IDs, a counter is appended to the ID string. Therefore,
JavaScript and CSS code should not rely on any value that was generated by
this function and instead should rely on manually added CSS classes or
similarly reliable constructs.

Two consecutive hyphens separate the counter from the original ID. To manage
uniqueness across multiple Ajax requests on the same page, Ajax requests
POST an array of all IDs currently present on the page, which are used to
prime this function's cache upon first invocation.

To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive
hyphens in the originally passed $id are replaced with a single hyphen.

File

Code

functiondrupal_html_id($id) {
// If this is an Ajax request, then content returned by this page request will
// be merged with content already on the base page. The HTML IDs must be
// unique for the fully merged content. Therefore, initialize $seen_ids to
// take into account IDs that are already in use on the base page.
$seen_ids_init = &drupal_static(__FUNCTION__ . ':init');
if (!isset($seen_ids_init)) {
// Ideally, Drupal would provide an API to persist state information about
// prior page requests in the database, and we'd be able to add this
// function's $seen_ids static variable to that state information in order
// to have it properly initialized for this page request. However, no such
// page state API exists, so instead, ajax.js adds all of the in-use HTML
// IDs to the POST data of Ajax submissions. Direct use of $_POST is
// normally not recommended as it could open up security risks, but because
// the raw POST data is cast to a number before being returned by this
// function, this usage is safe.
if (empty($_POST ['ajax_html_ids'])) {
$seen_ids_init = array();
}
else {
// This function ensures uniqueness by appending a counter to the base id
// requested by the calling function after the first occurrence of that
// requested id. $_POST['ajax_html_ids'] contains the ids as they were
// returned by this function, potentially with the appended counter, so
// we parse that to reconstruct the $seen_ids array.
if (isset($_POST ['ajax_html_ids'][0]) && strpos($_POST ['ajax_html_ids'][0], ',') === FALSE) {
$ajax_html_ids = $_POST ['ajax_html_ids'];
}
else {
// jquery.form.js may send the server a comma-separated string as the
// first element of an array (see http://drupal.org/node/1575060), so
// we need to convert it to an array in that case.
$ajax_html_ids = explode(',', $_POST ['ajax_html_ids'][0]);
}
foreach ($ajax_html_idsas$seen_id) {
// We rely on '--' being used solely for separating a base id from the
// counter, which this function ensures when returning an id.
$parts = explode('--', $seen_id, 2);
if (!empty($parts [1]) && is_numeric($parts [1])) {
list($seen_id, $i) = $parts;
}
else {
$i = 1;
}
if (!isset($seen_ids_init [$seen_id]) || ($i > $seen_ids_init [$seen_id])) {
$seen_ids_init [$seen_id] = $i;
}
}
}
}
$seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init);
$id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
// As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
// only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
// colons (":"), and periods ("."). We strip out any character not in that
// list. Note that the CSS spec doesn't allow colons or periods in identifiers
// (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
// characters as well.
$id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
// Removing multiple consecutive hyphens.
$id = preg_replace('/\-+/', '-', $id);
// Ensure IDs are unique by appending a counter after the first occurrence.
// The counter needs to be appended with a delimiter that does not exist in
// the base ID. Requiring a unique delimiter helps ensure that we really do
// return unique IDs and also helps us re-create the $seen_ids array during
// Ajax requests.
if (isset($seen_ids [$id])) {
$id = $id . '--' . ++$seen_ids [$id];
}
else {
$seen_ids [$id] = 1;
}
return$id;
}

Comments

if you need to do full form replacement yourself, be sure not to target the form using its id. Each time it is rebuilt, the HTML id is incremented by drupal_html_id(), so the replacement will only work the first time. Instead, either target a container within the form or some other selector that won't change each refresh.

"We currently don't use underscores in class names, so this new function should replace them with dashes by default, but allow callers to relax that restriction if they have a use case (like integrating with 3rd party code.)"