Automatically open <details> element on ID call - html

I'm trying to automatically open a element when a containing is called by ID, for example: http://www.example.com/page.html#container. Ideally I'd like this to scroll to the point of the page where the is located (inside the summary element), and then open the details element. Obviously, the native scroll function works fine, but how can I set the details element to open?
Here's my HTML source:
<details class="concentration">
<summary>
<h4 id="sample-slug"><?php the_sub_field('emphasis_name'); ?></h4>
</summary>
<p><?php the_sub_field('emphasis_description'); ?></p>
<div class="courses"><?php the_sub_field('emphasis_course_list'); ?></div>
</details>
When example.com/page.html#sample-slug is called, how can I make the details element aware of that and add the "open" attribute? I want to make sure the content is visible when the anchor is called.

I don't think you can open <details> with CSS alone. But you can:
Get the hash with location.hash. Possibly listen to hashchange event.
Use document.getElementById to get the element.
Set its open property to true.
function openTarget() {
var hash = location.hash.substring(1);
if(hash) var details = document.getElementById(hash);
if(details && details.tagName.toLowerCase() === 'details') details.open = true;
}
window.addEventListener('hashchange', openTarget);
openTarget();
:target {
background: rgba(255, 255, 200, .7);
}
<details id="foo">Details <summary>Summary</summary></details>
<div>#foo #bar</div>

Below I provide solution that works for nested <details><summary> elements:
With help of javascript you can:
track changes of hash anchor in url
react to them by changing open attribute of details elements accordingly
Here is example that does it, code here → https://stackoverflow.com/a/55377750/544721

Pure JS solution:
function openTarget() {
const hash = location.hash.substring(1);
if (hash) {
const target = document.getElementById(hash);
if (target) {
const details = target.closest('details');
if (details)
details.open = true;
}
}
}
openTarget(); // onload
window.addEventListener('hashchange', openTarget);

Related

Sometimes links are not clickable [duplicate]

I have a link button inside a <td> which I have to disable. This works on IE but not working in Firefox and Chrome.
I tried all the following but not working on Firefox (using 1.4.2 js):
$(".myLink").attr('disabled', 'disabled');
$(".myLink").attr('disabled', true);
$(".myLink").attr('disabled', 'true');
Note - I cannot de-register the click function for the anchor tag as it is registered dynamically. AND I HAVE TO SHOW THE LINK IN DISABLED MODE.
You can't disable a link (in a portable way). You can use one of these techniques (each one with its own benefits and disadvantages).
CSS way
This should be the right way (but see later) to do it when most of browsers will support it:
a.disabled {
pointer-events: none;
}
It's what, for example, Bootstrap 3.x does. Currently (2016) it's well supported only by Chrome, FireFox and Opera (19+). Internet Explorer started to support this from version 11 but not for links however it's available in an outer element like:
span.disable-links {
pointer-events: none;
}
With:
<span class="disable-links">...</span>
Workaround
We, probably, need to define a CSS class for pointer-events: none but what if we reuse the disabled attribute instead of a CSS class? Strictly speaking disabled is not supported for <a> but browsers won't complain for unknown attributes. Using the disabled attribute IE will ignore pointer-events but it will honor IE specific disabled attribute; other CSS compliant browsers will ignore unknown disabled attribute and honor pointer-events. Easier to write than to explain:
a[disabled] {
pointer-events: none;
}
Another option for IE 11 is to set display of link elements to block or inline-block:
<a style="pointer-events: none; display: inline-block;" href="#">...</a>
Note that this may be a portable solution if you need to support IE (and you can change your HTML) but...
All this said please note that pointer-events disables only...pointer events. Links will still be navigable through keyboard then you also need to apply one of the other techniques described here.
Focus
In conjunction with above described CSS technique you may use tabindex in a non-standard way to prevent an element to be focused:
...
I never checked its compatibility with many browsers then you may want to test it by yourself before using this. It has the advantage to work without JavaScript. Unfortunately (but obviously) tabindex cannot be changed from CSS.
Intercept clicks
Use a href to a JavaScript function, check for the condition (or the disabled attribute itself) and do nothing in case.
$("td > a").on("click", function(event){
if ($(this).is("[disabled]")) {
event.preventDefault();
}
});
To disable links do this:
$("td > a").attr("disabled", "disabled");
To re-enable them:
$("td > a").removeAttr("disabled");
If you want instead of .is("[disabled]") you may use .attr("disabled") != undefined (jQuery 1.6+ will always return undefined when the attribute is not set) but is() is much more clear (thanks to Dave Stewart for this tip). Please note here I'm using the disabled attribute in a non-standard way, if you care about this then replace attribute with a class and replace .is("[disabled]") with .hasClass("disabled") (adding and removing with addClass() and removeClass()).
Zoltán Tamási noted in a comment that "in some cases the click event is already bound to some "real" function (for example using knockoutjs) In that case the event handler ordering can cause some troubles. Hence I implemented disabled links by binding a return false handler to the link's touchstart, mousedown and keydown events. It has some drawbacks (it will prevent touch scrolling started on the link)" but handling keyboard events also has the benefit to prevent keyboard navigation.
Note that if href isn't cleared it's possible for the user to manually visit that page.
Clear the link
Clear the href attribute. With this code you do not add an event handler but you change the link itself. Use this code to disable links:
$("td > a").each(function() {
this.data("href", this.attr("href"))
.attr("href", "javascript:void(0)")
.attr("disabled", "disabled");
});
And this one to re-enable them:
$("td > a").each(function() {
this.attr("href", this.data("href")).removeAttr("disabled");
});
Personally I do not like this solution very much (if you do not have to do more with disabled links) but it may be more compatible because of various way to follow a link.
Fake click handler
Add/remove an onclick function where you return false, link won't be followed. To disable links:
$("td > a").attr("disabled", "disabled").on("click", function() {
return false;
});
To re-enable them:
$("td > a").removeAttr("disabled").off("click");
I do not think there is a reason to prefer this solution instead of the first one.
Styling
Styling is even more simple, whatever solution you're using to disable the link we did add a disabled attribute so you can use following CSS rule:
a[disabled] {
color: gray;
}
If you're using a class instead of attribute:
a.disabled {
color: gray;
}
If you're using an UI framework you may see that disabled links aren't styled properly. Bootstrap 3.x, for example, handles this scenario and button is correctly styled both with disabled attribute and with .disabled class. If, instead, you're clearing the link (or using one of the others JavaScript techniques) you must also handle styling because an <a> without href is still painted as enabled.
Accessible Rich Internet Applications (ARIA)
Do not forget to also include an attribute aria-disabled="true" together with disabled attribute/class.
Got the fix in css.
td.disabledAnchor a{
pointer-events: none !important;
cursor: default;
color:Gray;
}
Above css when applied to the anchor tag will disable the click event.
For details checkout this link
Thanks to everyone that posted solutions (especially #AdrianoRepetti), I combined multiple approaches to provide some more advanced disabled functionality (and it works cross browser). The code is below (both ES2015 and coffeescript based on your preference).
This provides for multiple levels of defense so that Anchors marked as disable actually behave as such.
Using this approach, you get an anchor that you cannot:
click
tab to and hit return
tabbing to it will move focus to the next focusable element
it is aware if the anchor is subsequently enabled
How to
Include this css, as it is the first line of defense. This assumes the selector you use is a.disabled
a.disabled {
pointer-events: none;
cursor: default;
}
Next, instantiate this class on ready (with optional selector):
new AnchorDisabler()
ES2015 Class
npm install -S key.js
import {Key, Keycodes} from 'key.js'
export default class AnchorDisabler {
constructor (config = { selector: 'a.disabled' }) {
this.config = config
$(this.config.selector)
.click((ev) => this.onClick(ev))
.keyup((ev) => this.onKeyup(ev))
.focus((ev) => this.onFocus(ev))
}
isStillDisabled (ev) {
// since disabled can be a class or an attribute, and it can be dynamically removed, always recheck on a watched event
let target = $(ev.target)
if (target.hasClass('disabled') || target.prop('disabled') == 'disabled') {
return true
}
else {
return false
}
}
onFocus (ev) {
// if an attempt is made to focus on a disabled element, just move it along to the next focusable one.
if (!this.isStillDisabled(ev)) {
return
}
let focusables = $(':focusable')
if (!focusables) {
return
}
let current = focusables.index(ev.target)
let next = null
if (focusables.eq(current + 1).length) {
next = focusables.eq(current + 1)
} else {
next = focusables.eq(0)
}
if (next) {
next.focus()
}
}
onClick (ev) {
// disabled could be dynamically removed
if (!this.isStillDisabled(ev)) {
return
}
ev.preventDefault()
return false
}
onKeyup (ev) {
// We are only interested in disabling Enter so get out fast
if (Key.isNot(ev, Keycodes.ENTER)) {
return
}
// disabled could be dynamically removed
if (!this.isStillDisabled(ev)) {
return
}
ev.preventDefault()
return false
}
}
Coffescript class:
class AnchorDisabler
constructor: (selector = 'a.disabled') ->
$(selector).click(#onClick).keyup(#onKeyup).focus(#onFocus)
isStillDisabled: (ev) =>
### since disabled can be a class or an attribute, and it can be dynamically removed, always recheck on a watched event ###
target = $(ev.target)
return true if target.hasClass('disabled')
return true if target.attr('disabled') is 'disabled'
return false
onFocus: (ev) =>
### if an attempt is made to focus on a disabled element, just move it along to the next focusable one. ###
return unless #isStillDisabled(ev)
focusables = $(':focusable')
return unless focusables
current = focusables.index(ev.target)
next = (if focusables.eq(current + 1).length then focusables.eq(current + 1) else focusables.eq(0))
next.focus() if next
onClick: (ev) =>
# disabled could be dynamically removed
return unless #isStillDisabled(ev)
ev.preventDefault()
return false
onKeyup: (ev) =>
# 13 is the js key code for Enter, we are only interested in disabling that so get out fast
code = ev.keyCode or ev.which
return unless code is 13
# disabled could be dynamically removed
return unless #isStillDisabled(ev)
ev.preventDefault()
return false
Try the element:
$(td).find('a').attr('disabled', 'disabled');
Disabling a link works for me in Chrome: http://jsfiddle.net/KeesCBakker/LGYpz/.
Firefox doesn't seem to play nice. This example works:
<a id="a1" href="http://www.google.com">Google 1</a>
<a id="a2" href="http://www.google.com">Google 2</a>
$('#a1').attr('disabled', 'disabled');
$(document).on('click', 'a', function(e) {
if ($(this).attr('disabled') == 'disabled') {
e.preventDefault();
}
});
Note: added a 'live' statement for future disabled / enabled links.
Note2: changed 'live' into 'on'.
Bootstrap 4.1 provides a class named disabled and aria-disabled="true" attribute.
example"
<a href="#"
class="btn btn-primary btn-lg disabled"
tabindex="-1"
role="button" aria-disabled="true"
>
Primary link
</a>
More is on getbootstrap.com
So if you want to make it dynamically, and you don't want to care if it is button or ancor than
in JS script you need something like that
let $btn=$('.myClass');
$btn.attr('disabled', true);
if ($btn[0].tagName == 'A'){
$btn.off();
$btn.addClass('disabled');
$btn.attr('aria-disabled', true);
}
But be carefull
The solution only works on links with classes btn btn-link.
Sometimes bootstrap recommends using card-link class, in this case solution will not work.
Just add a css property:
<style>
a {
pointer-events: none;
}
</style>
Doing so you can disable the anchor tag.
I've ended up with the solution below, which can work with either an attribute, <a href="..." disabled="disabled">, or a class <a href="..." class="disabled">:
CSS Styles:
a[disabled=disabled], a.disabled {
color: gray;
cursor: default;
}
a[disabled=disabled]:hover, a.disabled:hover {
text-decoration: none;
}
Javascript (in jQuery ready):
$("a[disabled], a.disabled").on("click", function(e){
var $this = $(this);
if ($this.is("[disabled=disabled]") || $this.hasClass("disabled"))
e.preventDefault();
})
In Razor (.cshtml) you can do:
#{
var isDisabled = true;
}
Home
You can disable the HTML link as given below:
<style>
.disabled-link {
pointer-events: none;
}
</style>
Google.com
You can use inline JavaScript:
Google.com
you cannot disable a link, if you want that click event should not fire then simply Remove the action from that link.
$(td).find('a').attr('href', '');
For More Info :- Elements that can be Disabled
I would do something like
$('td').find('a').each(function(){
$(this).addClass('disabled-link');
});
$('.disabled-link').on('click', false);
something like this should work. You add a class for links you want to have disabled and then you return false when someone click them. To enable them just remove the class.
To disable link to access another page on touch device:
if (control == false)
document.getElementById('id_link').setAttribute('href', '#');
else
document.getElementById('id_link').setAttribute('href', 'page/link.html');
end if;
I would suggest turning the link into a button and using the 'disabled' attribute. You can see this issue to check how to convert a link to a button: How to create an HTML button that acts like a link
You can use this to disabled the Hyperlink of asp.net or link buttons in html.
$("td > a").attr("disabled", "disabled").on("click", function() {
return false;
});
There is one other possible way, and the one that I like best. Basically it's the same way lightbox disables a whole page, by placing a div and fiddling with z-index. Here is relevant snippets from a project of mine. This works in all browsers!!!!!
Javascript (jQuery):
var windowResizer = function(){
var offset = $('#back').offset();
var buttontop = offset.top;
var buttonleft = offset.left;
$('#backdisabler').css({'top':buttontop,'left':buttonleft,'visibility':'visible'});
offset = $('#next').offset();
buttontop = offset.top;
buttonleft = offset.left;
$('#nextdisabler').css({'top':buttontop,'left':buttonleft,'visibility':'visible'});
}
$(document).ready(function() {
$(window).resize(function() {
setTimeout(function() {
windowResizer();
}, 5); //when the maximize/restore buttons are pressed, we have to wait or it will fire to fast
});
});
and in html
<img src="images/icons/back.png" style="height: 50px; width: 50px" />
<img src="images/icons/next.png" style="height: 50px; width: 50px" />
<img id="backdisabler" src="images/icons/disabled.png" style="visibility: hidden; position: absolute; padding: 5px; height: 62px; width: 62px; z-index: 9000"/>
<img id="nextdisabler" src="images/icons/disabled.png" style="visibility: hidden; position: absolute; padding: 5px; height: 62px; width: 62px; z-index: 9000"/>
So the resizer finds the anchor's (the images are just arrows) locations and places the disabler on top. The disabler's image is a translucent grey square (change the width/height of the disablers in the html to match your link) to show that it is disabled. The floating allows the page to resize dynamically, and the disablers will follow suit in windowResizer(). You can find suitable images through google. I have placed the relevant css inline for simplicity.
then based on some condition,
$('#backdisabler').css({'visibility':'hidden'});
$('#nextdisabler').css({'visibility':'visible'});
I think a lot of these are over thinking. Add a class of whatever you want, like disabled_link. Then make the css have .disabled_link { display: none }
Boom now the user can't see the link so you won't have to worry about them clicking it. If they do something to satisfy the link being clickable, simply remove the class with jQuery: $("a.disabled_link").removeClass("super_disabled"). Boom done!

Is there a way to prevent events from slotted elements in the light dom from propagating througth the shadow dom?

The problem
I have a complex element P from a third party library that listen to events triggered by user interaction. I want to write a Web component that contain P in its shadow dom, and using the slot mechanism, I want any element C elements put in W’s light dom to be displayed at some place in P.
My problem is the following : For element C that are interactive, I would like to have events propagating directly to the light dom, without triggering any eventual event listener in P.
What I tried
Instead of directly addint the slot in P herachy,I tried to add the slot in an other element that I created, add this element in P herachy and stop the event propagation when bublling in this element. In term of encapsulation, this element is not a parent of the sloted element from the light dom, but doing so still prevent the events to reach W.
Exemple reporducing the situation
The external lib creating P (P.js) :
export function P(container) {
const superComplexInnerHierachy = document.createElement("div")
superComplexInnerHierachy.textContent = "Some P lib's interactive stuff"
superComplexInnerHierachy.addEventListener(
"click",
() => console.log("I'm the third party P lib, I do stuff on click.")
)
container.append(d1)
const thingsIDo = {
add : (elem) => {
superComplexInnerHierachy.append(elem)
superComplexInnerHierachy.append("More P lib's interactive stuff")
}
}
return thingsIDo
}
The web compenent W that I'm trying to write (W.js):
import {P} from "P.js"
class W extends HTMLElement {
constructor(){
this.attachShadow({mode : "open"})
this.value = "Something else"
// Create the lib stuff in the shadow root
this.p = P(this.shadowRoot)
// Add a slot in P's hierachy to inject an element from the light dom
const slot = document.createElement("slot")
this.p.add(slot)
}
}
customElements.define("w-component", W);
The html snippet where W is used.
<script type="module" src="W.js"></script>
<w-component>
<div name="an_interactive_element_usupecting_of_P">
<input type="button" value="Button A">
<input type="button" value="Button B">
</div>
</w-component>
<script type="text/javascript">
document.querySelector("w-component")
.addEventListener("click", evt => {
console.log(`${evt.target.value} was clicked`)
})
</script>
Behavior
The behavior of the current code
When clicking on A
I'm the third party P lib, I do stuff on click.
Button A was clicked
When clicking on things added by P
I'm the third party P lib, I do stuff on click.
Something else was clicked
What would like to have
When clicking on A
Button A was clicked
When clicking on things added by P
I'm the third party P lib, I do stuff on click.
Something else was clicked
A solution could be to capture the event before it is transmitted to the Shadow DOM, by setting the 3rd parameter of addEventListener() to true.
If you detect that it comes from a slotted element and you can handle it by yourself, call stopPropagation() to prevent its propagation.
document.querySelector("w-component").addEventListener("click", evt => {
console.log(`${evt.target.value} was clicked`)
if ( evt.path.findIndex( elem => elem.localName === "slot" ) !== -1 )
evt.stopPropagation()
}, true )

Replace element with razor textarea

I want to take a tag and replace with a #Html.textarea() razor html helper but it doesn't look as if JQuery can replace DOM elements with html helpers. How do I go about this?
using(#Html.BeginForm())
{
<a id="clickme">Edit</a>
<div>#Model.username</div>
}
How can I replace this div with #Html.Textarea ? JQuery could do it with div and input tags.
jQuery cannot replace a tag with #Html.TextArea() !
The TextArea helper method is a C# method, which gets executed when razor tries to render the view. This happens in your web server. jQuery is a client side library and anything you do with jQuery happens at client side, in your browser.
But all these helper methods ultimately generate some HTML for DOM elements. That means, you can use jQuery to manipulate visibility of that.
If you are trying to do something like an inline edit, you can use a script like this , to start with
First, render the text area along with your label div, but have it hidden initially. Also wrap the label,edit link and the hidden input inside a container div which we can use later to help with our jQuery selectors.
#using (#Html.BeginForm())
{
<div class="edit-item">
Edit
<div class="edit-label">#Model.FirstName</div>
#Html.TextAreaFor(a => a.FirstName,
new { style = "display:none;", #class = "edit-text" })
</div>
<div class="edit-item">
Edit
<div class="edit-label">#Model.UserName</div>
#Html.TextAreaFor(a => a.UserName,
new { style = "display:none;", #class = "edit-text" })
</div>
}
Now when the user clicks edit, you have to toggle the visibility of the label and hidden input and update the value of label after user done editing the value in the input element.
$(function () {
$("a[data-mode]").click(function (e) {
e.preventDefault();
var _this = $(this);
var c = _this.closest(".edit-item");
c.find(".edit-text").toggle();
c.find(".edit-label").toggle();
if (_this.attr("data-mode") === 'label') {
_this.attr("data-mode", 'edit');
_this.text("done");
} else if (_this.data("mode") === 'edit') {
c.find(".edit-label").text(c.find(".edit-text").val());
_this.text("edit");
_this.attr("data-mode", 'label');
}
});
});
This is a head start. You can optimize this code as needed.
Here is a working jsfiddle for your reference

How to remove a shadow root from an HTML element adorned with a Shadow DOM from a template?

I'm exploring imports, templates, shadow DOM and custom elements in Chrome Canary (33.0.1712.3). In a grid layout I have a particular content element (region of the display) that will display different web components or cloned light DOM fragments imported from files.
However, I'm unable to redisplay ordinary HTML DOM once a shadow DOM has been added because I don't know how to remove the shadow root. Once created, the shadow root remains and interferes with the rendering of ordinary DOM. (I've looked at various W3C specs such as intro to web components, shadow DOM, templates, Bidelman's articles on HTML5 Rocks, etc.) I've isolated the problem in a simple example below:
Click "show plain old div"; click "show shadowed template"; click "show plain old div". Inspect in devtools after each click. After the third click, there is no output below the buttons and in devtools I am seeing:
<div id="content">
#document-fragment
<div id="plaindiv">Plain old div</div>
</div>
What do I need to add to removeShadow() to remove the shadow root and fully reset the content element to its initial state?
removing_shadows.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<template id="shadowedTemplateComponent">
<style>
div { background: lightgray; }
#t { color: red; }
</style>
<div id="t">template</div>
<script>console.log("Activated the shadowed template component.");</script>
</template>
<template id="plainDiv">
<div id="plaindiv">Plain old div</div>
</template>
</head>
<body>
<div>
<input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
<input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
<div id="content"></div>
</div>
<script>
function removeChildren(elt) {
console.log('removing children: %s', elt);
while (elt.firstChild) {
elt.removeChild(elt.firstChild);
}
}
function removeShadow(elt) {
if (elt.shadowRoot) {
console.log('removing shadow: %s', elt);
removeChildren(elt.shadowRoot); // Leaves the shadow root property.
// elt.shadowRoot = null; doesn't work
// delete elt.shadowRoot; doesn't work
// What goes here to delete the shadow root (#document-fragment in devtools)?
}
}
function showPlainOldDiv() {
console.log('adding a plain old div');
var host = document.querySelector('#content');
removeChildren(host);
removeShadow(host);
var template = document.querySelector('#plainDiv');
host.appendChild(template.content.cloneNode(true));
}
function showShadowTemplate() {
console.log('adding shadowed template component');
var host = document.querySelector('#content');
removeChildren(host);
removeShadow(host);
var template = document.querySelector('#shadowedTemplateComponent');
var root = host.shadowRoot || host.webkitCreateShadowRoot();
root.appendChild(template.content.cloneNode(true));
}
</script>
</body>
</html>
The spec of Shadow DOM moved from v0 to v1.
One of the changes is that in v1 there is no way to create shadow root on itself and the host element may contain only one shadow root.
So it seems like the answer of replacing the shadow root with a new blank shadow root is not valid anymore.
Solution paths:
if the host element self (div in your example) has no special value beside holding that Shadow DOM, one can just replace the host element as a whole
if one still likes to preserve the host, clearing the Shadow DOM with something like e.shadowRoot.innerHTML = '' might be sufficient
You can't remove a shadow root once you add it. However, you can replace it with a newer one.
As mentioned here, http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/, the newest shadow root "wins" and becomes the rendered root.
You can replace your shadow root with a new shadow root that only contains the <content> pseudo-element to insert everything from the light DOM back into the shadow DOM. At that point, as far as I know it will be functionally equivalent to having no shadow DOM at all.
rmcclellan is correct that you cannot truely "remove" a ShadowRoot v2. But, you can fake it.
The OuterHTML PARTIAL Solution
elementWithShadowDOMv2.outerHTML = elementWithShadowDOMv2.outerHTML;
HOWEVER, there is a major caveat: although there is no visual change, elementWithShadowDOMv2 still refers to the destroyed element with the ShadowDOMv2 as if elementWithShadowDOMv2.parentNode.removeChild( elementWithShadowDOMv2 ) were called. This also "removes" event listeners on the element. Observe the demo below.
var addShadowHere = document.getElementById("add-shadow-here");
addShadowHere.addEventListener("mouseenter", function() {
addShadowHere.style.border = '2em solid blue';
});
addShadowHere.addEventListener("mouseleave", function() {
addShadowHere.style.border = '';
});
var shadow = addShadowHere.attachShadow({mode:"open"});
var button = shadow.appendChild(document.createElement("button"));
button.textContent = "Click Here to Destroy The ShadowDOMv2";
button.addEventListener("click", function() {
addShadowHere.outerHTML = addShadowHere.outerHTML;
update();
});
update();
function update() {
// This just displays the current parent of the addShadowHere element
document.getElementById("parent-value").value = "" + (
addShadowHere.parentNode &&
addShadowHere.parentNode.cloneNode(false).outerHTML
);
}
<div id="add-shadow-here">Text Hidden By Shadow DOM</div>
addShadowHere.parentNode => <input readonly="" id="parent-value" />
Notice how the blue border stops working after you remove the ShadowDOM. That is because the event listeners are no longer registered on the new element: the event listeners remain registered on the old element that has now been removed from the DOM.
Thus, you must refresh any references to the element and reattach any event listeners. Here is an example of how you could reobtain a reference to the new element.
function removeShadowWithCaveat(elementWithShadow) {
if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
var parent = elementWithShadow.parentNode;
var prior = elementWithShadow.previousSibling;
elementWithShadow.outerHTML = elementWithShadow.outerHTML;
return prior.nextSibling || parent.firstChild;
}
If you need access to the elements which are naturally hidden by the existing shadow root and which will become exposed after the expulsion of the shadow root, then here is an alternative method that will perfectly preserve these nodes.
function removeShadowWithCaveat(elementWithShadow) {
if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
var ref = elementWithShadow.cloneNode(true);
while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild );
elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow);
return ref;
}
Working Solution
var createShadowProp = (
"createShadowRoot" in Element.prototype ? "createShadowRoot" : "webkitCreateShadowRoot"
);
function removeChildren(elt) {
console.log('removing children: %s', elt);
while (elt.firstChild) {
elt.removeChild(elt.firstChild);
}
}
function removeShadowWithCaveat(elementWithShadow) {
if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
var ref = elementWithShadow.cloneNode(true);
while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild );
elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow);
return ref;
}
function showPlainOldDiv() {
console.log('adding a plain old div');
var host = document.querySelector('#content');
removeChildren(host);
// Remove the shadow
host = removeShadowWithCaveat(host);
var template = document.querySelector('#plainDiv');
host.appendChild(template.content.cloneNode(true));
}
function showShadowTemplate() {
console.log('adding shadowed template component');
var host = document.querySelector('#content');
removeChildren(host);
// Remove the shadow
host = removeShadowWithCaveat(host);
var template = document.querySelector('#shadowedTemplateComponent');
var root = host.shadowRoot || host[createShadowProp]({
"open": true
});
root.appendChild(template.content.cloneNode(true));
}
<div>
<input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
<input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
<div id="content"></div>
</div>
<template id="shadowedTemplateComponent" style="display:none">
<style>
div { background: lightgray; }
#t { color: red; }
</style>
<div id="t">template</div>
<script>console.log("Activated the shadowed template component.");</script>
</template>
<template id="plainDiv" style="display:none">
<div id="plaindiv">Plain old div</div>
</template>
Also note the misuse of vendor prefixes (a problem that far too many developers have issues with). You are correct that, at the time that this question was asked, there was only the prefixed version of createShadowRoot (which was webkitCreateShadowRoot). Nevertheless, you must ALWAYS check to see if the unprefixed createShadowRoot version is available in case if browsers standardize the API in the future (which is now the case). It might be nice to have your code working today, but it's awesome to have your code working several years from now.
In Chrome:
Press F12, DevTool will open
Click gear icon in DevTool
Uncheck "show user agent shadow DOM" checkbox
Enjoy !

Make anchor links refer to the current page when using <base>

When I use the HTML <base> tag to define a base URL for all relative links on a page, anchor links also refer directly to the base URL. Is there a way to set the base URL that would still allow anchor links to refer to the currently open page?
For example, if I have a page at http://example.com/foo/:
Current behaviour:
<base href="http://example.com/" />
bar <!-- Links to "http://example.com/bar/" -->
baz <!-- Links to "http://example.com/#baz" -->
Desired behaviour:
<base href="http://example.com/" />
bar <!-- Links to "http://example.com/bar/" -->
baz <!-- Links to "http://example.com/foo/#baz" -->
I found a solution on this site: using-base-href-with-anchors that doesn't require jQuery, and here is a working snippet:
<base href="https://example.com/">
/test
Anchor
Or without inline JavaScript, something like this:
document.addEventListener('DOMContentLoaded', function(){
var es = document.getElementsByTagName('a')
for(var i=0; i<es.length; i++){
es[i].addEventListener('click', function(e) {
e.preventDefault()
document.location.hash = e.target.getAttribute('href')
})
}
})
Building upon James Tomasino's answer, this one is slightly more efficient, solves a bug with double hashes in the URL and a syntax error.
$(document).ready(function() {
var pathname = window.location.href.split('#')[0];
$('a[href^="#"]').each(function() {
var $this = $(this),
link = $this.attr('href');
$this.attr('href', pathname + link);
});
});
A little bit of jQuery could probably help you with that. Although base href is working as desired, if you want your links beginning with an anchor (#) to be totally relative, you could hijack all links, check the href property for those starting with #, and rebuild them using the current URL.
$(document).ready(function () {
var pathname = window.location.href;
$('a').each(function () {
var link = $(this).attr('href');
if (link.substr(0,1) == "#") {
$(this).attr('href', pathname + link);
}
});
}
Here's an even shorter, jQuery based version I use in a production environment, and it works well for me.
$().ready(function() {
$("a[href^='\#']").each(function() {
this.href = location.href.split("#")[0] + '#' + this.href.substr(this.href.indexOf('#')+1);
});
});
You could also provide an absolute URL:
<base href="https://example.com/">
test
Rather than this
test
I'm afraid there is no way to solve this without any server-side or browser-side script. You can try the following plain JavaScript (without jQuery) implementation:
document.addEventListener("click", function(event) {
var element = event.target;
if (element.tagName.toLowerCase() == "a" &&
element.getAttribute("href").indexOf("#") === 0) {
element.href = location.href + element.getAttribute("href");
}
});
<base href="https://example.com/">
/test
#test
It also works (unlike the other answers) for dynamically generated (i.e. created with JavaScript) a elements.
If you use PHP, you can use following function to generate anchor links:
function generateAnchorLink($anchor) {
$currentURL = "//{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
$escaped = htmlspecialchars($currentURL, ENT_QUOTES, 'UTF-8');
return $escaped . '#' . $anchor;
}
Use it in the code like that:
baz
To prevent multiple #s in a URL:
document.addEventListener("click", function(event) {
var element = event.target;
if (element.tagName.toLowerCase() == "a" &&
element.getAttribute("href").indexOf("#") === 0) {
my_href = location.href + element.getAttribute("href");
my_href = my_href.replace(/#+/g, '#');
element.href = my_href;
}
});
My approach is to search for all links to an anchor, and prefix them with the document URL.
This only requires JavaScript on the initial page load and preserves browser features like opening links in a new tab. It also and doesn't depend on jQuery, etc.
document.addEventListener('DOMContentLoaded', function() {
// Get the current URL, removing any fragment
var documentUrl = document.location.href.replace(/#.*$/, '')
// Iterate through all links
var linkEls = document.getElementsByTagName('A')
for (var linkIndex = 0; linkIndex < linkEls.length; linkIndex++) {
var linkEl = linkEls[linkIndex]
// Ignore links that don't begin with #
if (!linkEl.getAttribute('href').match(/^#/)) {
continue;
}
// Convert to an absolute URL
linkEl.setAttribute('href', documentUrl + linkEl.getAttribute('href'))
}
})
You can use some JavaScript code inside the tag that links.
<span onclick="javascript:var mytarget=((document.location.href.indexOf('#')==-1)? document.location.href + '#destination_anchor' : document.location.href);document.location.href=mytarget;return false;" style="display:inline-block;border:1px solid;border-radius:0.3rem"
>Text of link</span>
How does it work when the user clicks?
First it checks if the anchor (#) is already present in the URL. The condition is tested before the "?" sign. This is to avoid the anchor being added twice in the URL if the user clicks again the same link, since the redirection then wouldn't work.
If there is sharp sign (#) in the existing URL, the anchor is appended to it and the result is saved in the mytarget variable. Else, keep the page URL unchanged.
Lastly, go to the (modified or unchanged) URL stored by the mytarget variable.
Instead of <span>, you can also use <div> or even <a> tags.
I would suggest avoiding <a> in order to avoid any unwanted redirection if JavaScript is disabled or not working, and emulate the look of your <a> tag with some CSS styling.
If, despite this, you want to use the <a> tag, don't forget adding return false; at the end of the JavaScript code and set the href attribute like this <a onclick="here the JavaScript code;return false;" href="javascript:return false;">...</a>.
From the example given in the question. To achieve the desired behavior, I do not see the need of using a "base" tag at all.
The page is at http://example.com/foo/
The below code will give the desired behaviour:
bar <!-- Links to "http://example.com/bar/" -->
baz <!-- Links to "http://example.com/foo/#baz" -->
The trick is to use "/" at the beginning of string href="/bar/".
If you're using Angular 2 or later (and just targeting the web), you can do this:
File component.ts
document = document; // Make document available in template
File component.html
<a [href]="document.location.pathname + '#' + anchorName">Click Here</a>