Access dom-repeat element's CSS class on tap - polymer

My dom repeat displays a list of icons which I can bookmark or unbookmark ,which generating dom-repeat I call a function to find if this icon is bookmarked or not,that will return CSS class
.book-marked {
color: red;
}
.not-book-marked {
color: green;
}
<template is="dom-repeat" items="{{membersList}}">
<iron-icon icon="bookmark" class$="[[_computeBookMark(item.userId)]]" on-tap="_toogleBookMark"></iron-icon>
</template>
Once I get all my list of icon now if user click that icon I need to toogle css class.so I wrote on-tap function
_toogleBookMark:function(e) {
var userData = e.model.item; //gets entire data object of that element
var index = e.model.index; //gets index of that element
},
I can't use ID since its dom-repeat ,Is there any other ways so that I can change CSS of that dom-repeat element in _toogleBookMark() function on clicking? or is it possible to change CSS with index??or using "e" reference in _toogleBookMark(e) function !!

Not sure if I understood correctly - you want to access the element you've tapped?
Just use the event.target property then. It will return the element on which the event happened, in this case, the icon you have tapped.
_toogleBookMark = function(e) {
e.target.classList.toggle("not-book-marked");
}
Check this example.
Mind you:
1) When using Shady DOM, assuming our element is a custom element, target can be a component from the element's template, not the element itself. To prevent that, use Polymer.dom(e).localTarget (read more here).
2) When using a custom element with light DOM children, the above may not be enough, your (local)target will be a light DOM child, not the element you wished for. In that case, use Element.closest(selector) to (optionally) go up the DOM to the element you want. Read more about the method here.

As you just want to swap your class on tap, do it like this:
Add an own attribute, like data-id="1" and the id attribute, but be sure they have the same value:
<template is="dom-repeat" items="{{membersList}}">
<iron-icon icon="bookmark" class$="[[_computeBookMark(item.userId)]]" on-tap="_toogleBookMark" data-id="{{item.userId}}" id="{{item.userId}}"></iron-icon>
</template>
Now, inside your _toggleBookMark function, you can access the current tapped element and swap CSS classes by:
_toogleBookMark:function(e) {
// this gives you your userId from the data-id attribute
var userId = e.path[0].dataId;
// we can access the element now with
var element = this.$$('#' + e.path[0].dataId);
if (element.classList.contains('book-marked')) {
element.classList.remove('book-marked');
element.classList.add('not-book-marked');
} else {
element.classList.add('book-marked');
element.classList.remove('not-book-marked');
}
},

Related

How do I create 3 buttons that change the webpage background to red, green, or blue on-click in HTML?

I'm trying to create a webpage where there are three working buttons that are labelled "Red", "Green", and "Blue". When the user clicks on one of the buttons, the entire webpage should change to the color of the specific button that was clicked.
This is what I have so far, but I'm pretty sure I'm doing something wrong:
function myFunction() {
document.getElementById("H1").style.color = "#ff0000";
}
<h1 id="H1">H1</h1>
<button type="button" onclick="myFunction()">Set H1 to Red</button>
We begin with creating three radio buttons named red, green, blue inside the form tag and use attribute named on Click and assign value document. bgcolor=color name to it.
There is only one problem here that you changed color but you want to change background color.
Hope this will help you.
function myFunction() {
document.body.style.backgroundColor = "#ff0000";
}
onClick the button will invoke the changeColor function which will take the innerText
of the button as style value and set it as background color of the body.
function changeColor (element){
document.body.style.backgroundColor = element.innerText
}
console.log(document.body.style.backgroundColor)
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<button onclick="changeColor(this)" >Red</button>
<button onclick="changeColor(this)" >Green</button>
<button onclick="changeColor(this)" >Blue</button>
</body>
</html>
You can also use class or id selector to select the element instead of using tag name.
const element = document.getElementsByClassName(".class")
In this case the element will be an array of nodes that have the same class. You can use forEach loop to trigger color change on all of them.
or use an Id if the target is a single element.
const element = document.getElementById("id")
You can also use onclick event listener to change the color.
First the "problems," which I'm not convinced are problems since clicking the <button> does exactly what the <button> says – in its text – that it will do. However:
function myFunction() {
// as the button-text implies this JavaScript retrieves
// the element with the id of 'H1', and updates its
// CSS - via the inline style element - to the colour
// of '#ff0000' (or '#f00'), which is red.
document.getElementById("H1").style.color = "#ff0000";
}
<h1 id="H1">H1</h1>
<!-- Obviously you know that this element is intended to set
the colour of the <h1> element, given the text of the
<button>; also you're using an inline event-handler
(the 'onclick' attribute) to bind a function to an event,
which is considered bad practice due to the obtrusive
nature of the event-binding, and difficulty of updating
function calls: -->
<button type="button" onclick="myFunction()">Set H1 to Red</button>
To do as you ask, and instead set the background-color of a given element, we must:
retrieve that element in the JavaScript,
update the correct – background-color – property,
find a means by which the <button> can "communicate" the correct value to which that property must be set, and ideally
use unobtrusive JavaScript to bind the function to the 'click' event.
To do that:
// assigning a meaningful name to the function
// in order to make future maintenance easier:
function setBackgroundColor(evt) {
// retrieving the element we wish to style:
const body = document.querySelector('body')
// accessing the style interface, and
// updating the background-color, via the
// JavaScript camelCased property-name:
body.style.backgroundColor = evt.currentTarget.value;
}
// retrieving the <button> elements:
const buttons = document.querySelectorAll('button');
// iterating over that NodeList of <button>
// elements, using NodeList.prototype.forEach():
buttons.forEach(
// using an Arrow function, passing in a reference
// to the current Node of the NodeList over which
// we're iterating. Here we use the
// EventTarget.addEventListener() method to bind the
// setBackgroundColor() functiona as the 'click'
// event-handler when the node is clicked (also fired
// on keyboard navigation if the user hits spacebar
// or enter):
(node) => node.addEventListener('click', setBackgroundColor)
)
<h1 id="H1">H1</h1>
<!-- here we're adding a "value" attribute to hold
the colour to set; we're using a colour-name
('red'), a hexadecimal ('#87cefa'), and
a hsl() ('120deg 93% 80%') value as the colour: -->
<button type="button" value="red">Red</button>
<button type="button" value="#87cefa">Blue</button>
<button type="button" value="hsl(120deg 93% 80%)">Red</button>
There is, of course, an alternative and that's to use a colour-picker <input>, which allows the user to pick any colour of their choice and simply pass the chosen value to the function:
// assigning a meaningful name to the function
// in order to make future maintenance easier:
function setBackgroundColor(chosenColor) {
// caching the <body> element:
const body = document.querySelector('body');
// updating the background-color via the
// 'style' interface:
body.style.backgroundColor = chosenColor;
}
// retrieving the <input> element with a type-attribute
// equal to "color":
const input = document.querySelector('input[type=color]');
// using EventTarget.addEventListener() to use the anonymous
// function to call the setBackgroundColor() function, passing
// the value of the evt.currentTarget node (the color <input>)
// as the argument:
input.addEventListener('change', (evt)=> setBackgroundColor(evt.currentTarget.value));
<h1 id="H1">H1</h1>
<input type="color">
References:
Arrow functions.
CSSStyleDeclaration.
document.querySelector().
document.querySelectorAll().
EventTarget.addEventListener().

Jquery change css class from variable

For my site, I code a button allowing to change the css of a class present in a div card. My button is located in the card-footer. Having several cards, I can't / don't think to retrieve the element with an id (as there will be X times the same ID)
In order to circumvent this system, I therefore use a parentElement which goes up to the div card
<div class="card">
<div class="card-body">
<p class="change">Change one</p>
<p class="change">Change two</p>
<p class="change">Change three</p>
</div>
<div class="card-footer">
<i id="updateData">change</i>
</div>
</div>
jQuery($ => {
$('#updateData').click(e => {
var element = e.target.parentElement.parentElement;
$('.change').css('display','none');
});
});
I would like to indicate that only the class "changes" present in my element variable and not all the classes in the page.
I don't know how to add a variable to my ".css" command, do you know how ?
Thanks in advance !
First of all since you will have multiple elements with same id that means that you should not use ID and use class instead. Id is meant to be unique. So yours id="updateData" should become class="updateData". Now you can grab all of those buttons and assign event to all of them instead of just first like you were by using id selector.
$('.updateData').click(e=> {});
Next in order to be able to use clicked element in jQuery way convert from arrow function to regular anonymous function since arrow function overrides this keyword. And now you can use jQuery to hide like
$('.updateData').click(function() {
element = $(this).parent().parent();
element.hide();
});
If you want more precise selection to hide only .change elements use
$('.updateData').click(function() {
element = $(this).parent().parent();
element.find(".change").hide();
});
Not bad, but more efficient, when you have multiple click targets, is delegation:
$(document).on("click", ".updateData", function() { ... });
Also .hide() is convenient, but rather then "change the css of a class" add a class "hidden" or something! In best case the class further describes what the element is. CSS is just on top.

Web Components - extend native element's style

I would like to extend the native button element but I am not sure how to add styling. In Google's example here they don't use a template so the fancy-button custom element itself is the button, rather than adding a template and shadow DOM containing a button element. It seems to defeat the object of extending a native element if I just add a button directly to the shadow DOM, but I don't know how to style and extend native element. How can I create a custom element which is simply the native button element extended to have a red background?
var style = `button { background-color: red; };
class FancyButton extends HTMLButtonElement {
constructor() {
super();
}
customElements.define('fancy-button', FancyButton, {extends: 'button'});
since you don't have shadowDOM involved you can use global CSS
you can set styles in the connectedCallback: this.style.background='red'
you can dynamically create a STYLE tag with unique identifier scoping your element
See JSFiddle for all 3 examples: https://jsfiddle.net/WebComponents/gohzwvL4/
Important is the notation for your Customized Built-In Element
Correct : <button is="fancy-button></button>
InCorrect: <fancy-button></fancy-button> (this is Autonomous Element notation)
.
Firefox pitfall:
The INcorrect notation works in Firefox , but not in Chrome & Opera
Firefox processes Extended Built-In Elements with Autonomous Element notation
but only for elements created in the DOM prior to definition:
This
<fancy-button>Hello Fancy Red Button #1</fancy-button>
<script>
class FancyButton extends HTMLButtonElement {
constructor() {
super();
}
connectedCallback() {
this.style.background = 'red';
}
}
customElements.define('fancy-button', FancyButton, { extends: 'button' });
</script>
<fancy-button>Hello Fancy Red Button #2</fancy-button>
is displayed in Firefox as:
any number of Custom Elements before the SCRIPT tag are colored!
When the <SCRIPT> is moved into the <HEAD> Firefox won't color any background
When the script is executed after the onload event all buttons are colored
This is non-standard behaviour!

Create an anchor link tag that just adjusts the hash without losing the query search string

Assume the current URL is: http://server.com/?key=value#/foo
In a normal anchor tag link, the following will just affect the anchor hash:
LINK
And the URL becomes: http://server.com/?key=value#/bar
However, I am adding links in a template in a web component that was imported from another .html file. Therefore, for the anchor hash to be relative to the loaded page instead of the component's html, I need to specify the link as follows:
LINK
However, a link like this causes the query search string to be lost: http://server.com/#/bar
Is there a clean solution here? Workaround, of course, is to create a new element inherited from that manually updates the window.document.location.
So, my current workaround is to just create a new anchor tag inherited from <a> that accepts an attribute hash instead of href (using Polymer 0.9):
<dom-module id="a-hash"></dom-module>
<script>
Polymer({
is: 'a-hash',
extends: 'a',
hostAttributes: { href: "" },
properties: { hash: String },
listeners: { tap: '_ontap', click: '_onclick' },
_onclick: function(e) { e.preventDefault(); },
_ontap: function(e) {
e.preventDefault();
document.location.hash = this.hash;
}
});
</script>
Usage:
Link: <a is=a-hash hash="/client/side/route">Click me</a>
I found a much cleaner solution to adding relative links in a new web component. Just add a:
<base href="../../" />
to the top of the component's .html file (assuming you keep your custom elements in an elements/element-name subdirectory) and then you can just add normal anchors such as:
<a href="#/bar>LINK</a>
And it will be created relative to the original app's URL instead of the component's html without losing the query string or reloading.
Just remember that ALL links in the component will now be relative to the root of the app instead of the component, so other references may need to be updated accordingly.

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 !