element or other object on the page, for instance a div created with >>id=divname<< text can be hidden/shown >><< Required parameter: id=objname id attribute of object to be toggled Optional parameters: id2=obj2name second object (div), for toggling betwen first and second object init=hide hides the element initially (default: show) show=labelname label of link or button when div is hidden (default: "Show") hide=labelname label of link or button when div is shown (default: "Hide") label=labelname label of link or button for both toggle states (shortcut for setting 'show' and 'hide' to the same value) ttshow=tooltiptext text that appears when the user hovers over the "show" link (default: "Show") tthide=tooltiptext text that appears when the user hovers over the "hide" link (default: "Hide") tt=tooltiptext text that appears when the user hovers over the link in both states (shortcut for setting 'ttshow' and 'tthide' to the same value) group=classname on clicking Show, show div with associated 'id' (standard behaviour), but hide all other divs with class classname. display=value what the 'display' CSS property of the specified element should be set to, when it’s shown ('block', 'inline-block', etc.; see https://www.w3schools.com/CSSref/pr_class_display.asp for details) (default: "block") display2=value like 'display', but for the alternate element (id2) (default: "block") set=1 sets a cookie to remember toggle state (default: 0) button=1 display a button instead of a link (default: 0) printhidden=1 show hidden elements when printing (default: 1) nojs=integer set to 1 or 2 will show toggle links/buttons if browser does not support javascript. Set to 2 will hide hidden divs via style in page head and not via javascript, so that for non-js browser initially hidden divs stay hidden. (default: 0) Alternative syntax: (:toggle divname:) Alternative syntax with options: (:toggle hide divname:) initial hide (:toggle hide divname button:) initial hide, button (:toggle name1 name2:) toggle between name1 and name2 See https://www.pmwiki.org/wiki/Cookbook/Toggle for additional info. */ # Recipe version (date). $RecipeInfo['Toggle']['Version'] = '2022-06-17'; # Declare $Toggle for (:if enabled Toggle:) recipe installation check. global $Toggle; $Toggle = 1; # Defaults. SDVA($ToggleConfig, [ 'id' => '', // no default div name 'id2' => '', // no default div2 name 'init' => 'show', // initial state of element (visible) 'show' => XL("Show"), // link text ‘Show’ 'hide' => XL("Hide"), // link text ‘Hide’ 'ttshow' => XL("Show"), // tooltip text ‘Show’ 'tthide' => XL("Hide"), // tooltip text ‘Hide’ 'group' => '', // no default group (class) name 'display' => 'block', // default to `display:block`; 'display2' => '', // default alternate element to same as primary 'set' => false, // set no cookie to remember toggle state 'button' => false, // display as link by default 'printhidden' => true, // hidden divs get printed 'nojs' => 0, // in no-js browser links are not shown, initial hidden divs are shown ]); $ToggleConfigStack = [ $ToggleConfig ]; # Retrieve cookie. global $CookiePrefix, $pagename; $current_page_group = PageVar($pagename, '$Group'); $current_page_name = PageVar($pagename, '$Name'); $toggle_cookie_name = "{$CookiePrefix}_toggle_{$current_page_group}_{$current_page_name}"; $toggle_cookie = isset($_COOKIE[$toggle_cookie_name]) ? json_decode($_COOKIE[$toggle_cookie_name], true) : null; Markup('toggle', 'directives', '/\(:(toggle(?:set)?)\s+(.*?):\)/i', 'ToggleMarkup'); # All in one function. function ToggleMarkup($m) { global $HTMLHeaderFmt, $HTMLFooterFmt, $HTMLStylesFmt, $UploadUrlFmt, $UploadPrefixFmt; global $ToggleConfigStack, $toggle_cookie_name, $toggle_cookie; global $MarkupMarkupLevel; extract($GLOBALS['MarkupToHTML']); // Parse directive arguments. $parsed_args = ParseArgs($m[2]); // Get parameters without keys. if ( isset($parsed_args['']) && is_array($parsed_args[''])) { while (count($parsed_args['']) > 0) { $parameter = array_shift($parsed_args['']); if ($parameter == 'button') $parsed_args['button'] = 1; else if ($parameter == 'set') $parsed_args['set'] = 1; else if ($parameter == 'printhidden') $parsed_args['printhidden'] = 1; else if ($parameter == 'hide') $parsed_args['init'] = 'hide'; else if ($parameter == 'show') $parsed_args['init'] = 'show'; else if (!isset($parsed_args['id'])) $parsed_args['id'] = $parameter; else if (!isset($parsed_args['id2'])) $parsed_args['id2'] = $parameter; } } // Ensure that (:toggleset:) in (:markup:) only affects things on that // and deeper markup levels. for ($i = ($MarkupMarkupLevel ?? 0); !($ToggleConfig = ($ToggleConfigStack[$i] ?? null)); $i--); if ($m[1] == 'toggleset') { // Just setting options for toggles on the page. $ToggleConfig = array_merge($ToggleConfig, $parsed_args); unset($ToggleConfig['#']); $ToggleConfigStack[$MarkupMarkupLevel] = $ToggleConfig; return ''; } else { // An actual toggle. Fill in un-specified parameters with defaults. $opt = array_merge($ToggleConfig, $parsed_args); } $HTMLStylesFmt['toggle'] = " @media print { .toggle { display: none; } } \n" . ".toggle img { border: none; } \n"; $HTMLHeaderFmt['toggle'] = <<<'EOT' EOT; # javascript for toggling and cookie setting $HTMLFooterFmt['toggleobj'] = <<\n EOT; // Styling for errors. $error_opening_tag = ''; $error_closing_tag = ''; // Retrieve the ids of both the primary and (if specified) alternate elements. // (the 'div' options are for backwards compatibility with ShowHide and ToggleLink recipes) $id = $opt['div'] ?? $opt['id']; $id2 = $opt['div2'] ?? $opt['id2']; if ($id == '') { $error_message = '[Toggle] No object id specified.'; return Keep($error_opening_tag . $error_message . $error_closing_tag); } // Verify that the ids of both elements do not contain special characters // which are forbidden in CSS identifiers. (Among other things, this forbids // quotation marks, which prevents Javascript injection attacks via ids.) $CSS_forbidden_characters_regex = '/[!"#\$%&\'\(\)\*\+,\.\/\:;<=>\?@\[\\\\\]\^`\{\|\}~]/'; $error_message_template = '[Toggle] Invalid ID specified for element: ELEMENT_ID'; $error_messages = [ ]; if (preg_match($CSS_forbidden_characters_regex, $id)) $error_messages[] = str_replace('ELEMENT_ID', $id, $error_message_template); if (preg_match($CSS_forbidden_characters_regex, $id2)) $error_messages[] = str_replace('ELEMENT_ID', $id2, $error_message_template); if (count($error_messages) > 0) return Keep(implode('
', array_map(function ($msg) { return ($error_opening_tag . $msg . $error_closing_tag); }, $error_messages))); // Values for the ‘display’ CSS property. $display = $opt['display']; $display2 = $opt['display2'] ?? $display; // Set labels for (both states of) the toggle link/button. $labels = [ ]; $labels['show'] = $opt['label'] ?? $opt['lshow'] ?? $opt['show']; $labels['hide'] = $opt['label'] ?? $opt['lhide'] ?? $opt['hide']; // Same with tooltips. $tooltips = [ ]; $tooltips['show'] = $opt['tt'] ?? $opt['ttshow']; $tooltips['hide'] = $opt['tt'] ?? $opt['tthide'] ; // Transform label text into image tags, if appropriate. // (since we won’t be putting the label text through pmwiki markup // processing, which would normally process image attach links) // (but maybe we should? TODO: investigate this) // Also encode apostrophes (for non-images). $ipat = '/^(.+\.(png|gif|jpg|jpeg|ico|svg))(?:\s*\|\s*(.+?))?$/i'; foreach ($labels as $k => $val) { $is_image = preg_match($ipat, $val, $m); if ($is_image) { // Check for image, make image tag $image = $m[1]; $prefix = (strstr($image, '/')) ? '/' : $UploadPrefixFmt; $path = FmtPageName($UploadUrlFmt.$prefix, $pagename); $tooltips[$k] = $m[3] ?? $tooltips[$k]; $labels[$k] = ""; $opt['button'] = ''; } else { // Apostrophe encoding $labels[$k] = str_replace("'", "’", $val); } } // If the element is part of a defined group, then hide it, unless it’s explicitly set to be initially-shown. if ( $opt['group'] != '' && ($parsed_args['init'] ?? '') != 'show') $opt['init'] = 'hide'; // If set=1 (i.e. cookie setting enabled), then check if a cookie is set; // if it is, read the element’s initial state from the cookie. if ($opt['set'] == 1) $opt['init'] = $toggle_cookie[$id] ?? $opt['init']; /* OPTION RETRIEVAL ENDS; NOW PROCESSING BEGINS */ // Set initial state, and update labels and target state // (for when user clicks the toggle/link button). $display_property_value = ($opt['init'] == 'show') ? $display : 'none'; if (!($toggle_cookie[$id2] ?? null)) $alternate_element_display_property_value = ($opt['init'] == 'show') ? 'none' : $display2; $label = ($opt['init'] == 'show') ? $labels['hide'] : $labels['show']; $tooltip = ($opt['init'] == 'show') ? $tooltips['hide'] : $tooltips['show']; $state_to_toggle_to = ($opt['init'] == 'show') ? 'hide' : 'show'; if ( $opt['nojs'] < 2 || $opt['init'] == 'show') { if (!isset($HTMLHeaderFmt['toggle-styles'])) $HTMLHeaderFmt['toggle-styles'] = ''; // Open script tag. $HTMLHeaderFmt['toggle-styles'] .= ''; } else { // Set initial state of element via embedded CSS. $HTMLStylesFmt[] = "#{$id} { display: {$display_property_value}; } \n"; // Set initial state of alternate element (in the same way). if ($id2) $HTMLStylesFmt[] = " #{$id2} { display: {$alternate_element_display_property_value}; } \n"; } // Set separate styles for print view, if ‘printhidden’ option is set. if ($opt['printhidden'] == 1) { $HTMLStylesFmt[] = "@media print { #{$id} { display: {$display} !important; } } \n"; if ($id2) $HTMLStylesFmt[] = "@media print { #{$id2} { display: {$display} !important; } } \n"; } // Save the Toggle state/data for this element. $HTMLFooterFmt[] = << EOT; // Construct toggle link or button (later it is modified with javascript). $out = " 0 ? " no-js-visible" : "") . "'>" . ($opt['button'] == 1 ? "" : "{$label}") . ""; return Keep($out); }