Save/Load rpivottable configuration - r

I use rpivottable on several (rmarkdown) web pages.
I have seen an example here of saving/restoring table configuration to/from cookie.
Since I am not good in javascript, I would like to ask if it is possible to programmatically add two buttons in the rmd page, on top of the table control, allowing the users to save/load their preferred table configurations (either to cookie, or to a local file, if possible). Could you provide sample code to achieve that?
Thanks.

This one took a while. I used local storage. I've got a lot of styling here, but it's unnecessary. I used the output of flexdashboard, since that tends to cause me the most problems with JS.
<style>
body { /*push content away from far right and left edges*/
margin-right: 2%;
margin-left: 2%;
}
.rpivotTable {
overflow:auto;
resize: both;
box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
-moz-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
-webkit-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
border: 1px solid white;
padding: 5px;
margin: 5px 20px 50px 5px;
}
.btn {
vertical-align: middle;
-moz-box-shadow: 0px 10px 14px -7px #000000;
-webkit-box-shadow: 0px 10px 14px -7px #000000;
box-shadow: 0px 10px 14px -7px #000000;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
border: .5px solid black;
display: inline-block;
font-size: 1.3em;
padding: .3em 0px;
width: 18em;
text-decoration: none; /*no underline!!*/
cursor: pointer;
}
.btn:active { /*simulate movement*/
position: relative;
top: 1px;
}
</style>
I've used the content that I've found in other questions.
## R Markdown
<div style="margin-right:5%;">
`r stringi::stri_rand_lipsum(10)`
</div>
```{r cars}
library(rpivotTable)
data(mtcars)
names(mtcars)[10] <- "George.Dontas"
```
Here is the **first** Div.
## Including Plots
Do you want to save or restore the previously saved pivot tables' configuration?
<a id='saveBtn' class='btn' style="background-color:#003b70;color:white;">Save Current Configuration</a>
<a id='restoBtn' class='btn' style="background-color:#b21e29;color:white;">Restore Previous Configuration</a>
```{r pressure, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols=c("cyl","carb"),width="100%", height="400px")
```
```{r morePressure, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols=c("cyl","carb"),width="100%", height="400px")
```
This should be a different aspect of the report.
```{r evenMorePressure, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols=c("cyl","carb"),width="100%", height="400px")
```
Here is the JS/JQuery...it's a bit ugly and a rather unseemly hodgepodge of the two (JS/JQuery).
```{r listenOrElse,results="as-is",engine="js"}
// save current state of the tables to my browser
setTimeout(function(){ //add the events first
document.querySelector('a#saveBtn').addEventListener('click', savoring);
document.querySelector('a#restoBtn').addEventListener('click', giveItBack);
function savoring() { // function to save
el = document.querySelectorAll('.rpivotTable');
for(i=0; i < el.length; i++){
elId = el[i].getAttribute("id");
stringy = $('#' + elId).data("pivotUIOptions"); // collect rows/columns filters
delete stringy['aggregators']; // remove the arbitrary
delete stringy['renderers'];
stringy2 = JSON.stringify(stringy); // make it one key:value
window.localStorage.setItem('table' + i, stringy2); // store it!
}
};
function giveItBack() { // function to regurgitate
el = document.querySelectorAll('.rpivotTable');
console.log("working on the giver");
ods = [...el[0].ownerDocument.scripts]; // make it an array
for(j=0; j < el.length; j++){
elId = el[j].getAttribute("id");
where = ods.filter(function(ods){ // filter scripts for table data
return ods.dataset['for'] === elId;
})[0].innerHTML;
where2 = JSON.parse(where).x.data; // WOOO HOO! I figured it out!!
where3 = HTMLWidgets.dataframeToD3(where2); // finally sheesh!!
gimme = window.localStorage.getItem('table' + j); // get storage
$('#' + elId).pivotUI(where3, JSON.parse(gimme), true, "en"); // put it back!
}
}
},100);
```
Update
Thanks for pointing out some opportunities for improvement, #George Dontas. This update changes how the configuration is saved. I'm sure there are still ways to improve it, though.
This update adds the file or webpage name as part of the key-value pair used to store the information. Now, both the name of the webpage/script and table number need to match for the tables to update. Additionally, this will alert the user when a configuration cannot be restored. This alert would occur if there is nothing saved and if there is no file name and table matching configuration saved.
Updates to Saving the Configuration
There is one new line and one modified line of code in savoring().
New:
path = window.location.pathname.split("/").pop().split(".").slice()[0]; //f name
Modified:
window.localStorage.setItem(path + '_table' + i, stringy2); // store it
The entire function with changes:
function savoring() { // function to save
el = document.querySelectorAll('.rpivotTable');
path = window.location.pathname.split("/").pop().split(".").slice()[0];
for(i=0; i < el.length; i++){
elId = el[i].getAttribute("id");
stringy = $('#' + elId).data("pivotUIOptions"); // collect filters
delete stringy['aggregators']; // remove the arbitrary
delete stringy['renderers'];
stringy2 = JSON.stringify(stringy); // make it one key:value
window.localStorage.setItem(path + '_table' + i, stringy2); // store it
}
};
Updates to Restoring the Configuration
There are few new lines in this function. The name has to be collected, as in the savoring() changes. Additionally, this function now has an alert for the user.
I started out with the basic system alert, but it wasn't up to snuff for my tastes, so I also developed a custom alert box. I've included both here.
Basic Alert and Updated Configuration Retrieval
The only thing that changes from my original answer to making a basic alert are the following lines of code within the giveItBack() function:
path = window.location.pathname.split("/").pop().split(".").slice()[0]; //f name
and
if(window.localStorage.getItem(path + '_table' + j) === null) {
jj = j + 1;
alert("WARNING: There is no saved pivot table configuration for " + path + "'s table " + jj + ".");
continue; // don't update, go to next table (if more than 1)
}
Here is the complete giveItBack() function (note that notice(msg) and msg are here, but commented out):
function giveItBack() { // function to regurgitate
el = document.querySelectorAll('.rpivotTable');
console.log("working on the giver");
ods = [...el[0].ownerDocument.scripts]; // make it an array
path = window.location.pathname.split("/").pop().split(".").slice()[0]; //name
for(j=0; j < el.length; j++){
elId = el[j].getAttribute("id");
where = ods.filter(function(ods){ // filter scripts data
return ods.dataset['for'] === elId;
})[0].innerHTML;
where2 = JSON.parse(where).x.data; // WOOO HOO! I figured it out!!
where3 = HTMLWidgets.dataframeToD3(where2); // finally formatted
// is there a saved configuration that matches this file and table?
if(window.localStorage.getItem(path + '_table' + j) === null) {
jj = j + 1;
//this is for the standard alert box
alert("WARNING: There is no saved pivot table configuration for " + path + "'s table " + jj + ".");
//msg = "<b>WARNING</b><br><br>There is no saved pivot table configuration for<br>" + path + "."
//notice(msg); //this is for the custom alert box
continue; // go to next loop
}
gimme = window.localStorage.getItem(path + '_table' + j); // get storage
$('#' + elId).pivotUI(where3, JSON.parse(gimme), true, "en"); // put it back!
}
};
Custom Alert and Updated Configuration Retrieval
If you choose to use a more custom approach to the alert message, there is a lot more (luckily, it should be copy and paste). You will use the giveItBack function from the updates for the basic alert, but comment out or delete alert(... and uncomment msg and notice().
For the CSS in my original answer, update the styles for .btn to .btn, #noted and .btn:active to btn:active, #noted:active.
This is the remaining CSS for the custom alert. You can add this CSS to the other style tags or keep them separated.
<style>
#notice-wrapper {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1000000;
background: transparent;
display: none;
transition: opacity 1s ease-in;
}
#notice-box {
-moz-box-shadow: 0px 10px 14px -7px #000000;
-webkit-box-shadow: 0px 10px 14px -7px #000000;
box-shadow: 0px 10px 14px -7px #000000;
border-radius: 4px;
border: .5px solid black;
width = 300px;
background: #003b70;
color: white;
min-height: 200px;
position: absolute;
top: 50%;
left: 50%;
margin: -100px 0 0 -150px;
}
#notHead {
text-align: center;
font-size: 1.3em;
padding: 4px;
margin: 2.5em;
font-family: Verdana, sans-serif;
}
#noted {
background: #b21e29;
margin: .5em;
width: 120px;
font-family: Verdana, sans-serif;
}
</style>
The JS for the custom alert box is next. I placed this function within the setTimeout(function(){ with savoring() and giveItBack().
function notice(msg) {
function cr() {
if(document.querySelector('#notice-wrapper') === null) {
wrapper = document.createElement('div');
wrapper.id = 'notice-wrapper';
html = "<div id='notice-box'><h2 id='notHead'></h2><div id='noticeBtns'>";
html += "<button id='noted'>OK</button></div></div>";
wrapper.innerHTML = html;
document.body.appendChild(wrapper);
}
insta = document.querySelector('#notice-wrapper');
placer(insta);
return(insta);
}
function placer(insta) {
wrapper = insta;
winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientheight;
wrapper.style.height = winHeight + "px";
}
function showy(el) {
el.style.display = "block";
el.style.opacity = 1;
}
function goAway(el) {
el.style.opacity = 0;
setTimeout(function(){
el.style.display = "none";
}, 1000);
}
function takeAction(msg) {
insta = cr();
insta.querySelector('#notHead').innerHTML = msg;
showy(insta);
insta.querySelector('#noted').addEventListener('click', function() {
goAway(insta);
}, false);
}
takeAction(msg);
}
Of course, with this custom option, you have the opportunity to style it as you see fit. Style control isn't an option with the system alert messaging system.

Don't mark up this answer—it's not another answer—it's an update. (And written for anyone who might read it; not just who asked...)
This updated method of saving and restoring table configuration is based on the modified version of the package rpivotTable (in Github: fraupflaume/rpivotTable).
I've kept this answer self-contained so that you would not have to look at the other answer to assemble the code.
This works for one to many rpivotTable widgets in the same RMarkdown rendered webpage.
Dynamic Sizing: This script includes dynamic resizing, limited by the available width of the webpage (it will not overlap other content, and it will not make the webpage wider). For dynamic sizing to work—
If you use the output html_document, you have to unset the max-width of the class main-container. This setting creates massive margins, which is anti-conducive to this widget.
You need to style the class .rpivotTable with overflow: auto; you cannot assign resize to this class.
If you use the output flex_dashboard, you cannot leave the default setting vertical-scroll: fit (the opposite of dynamic sizing!)
You cannot set the default height and width of the widget at the widget level. Setting this makes the size utterly static.
It is perfectly acceptable to set the height and width of the table with static or dynamic sizes when creating the table (rpivotTable()).
What did I forget?
Cookie-ish Alert: The configuration save has two options for rendering an alert (alert that indicates there was no data to restore).
System Alert: If using the system alert there are two lines of Javascript in the cookie function. The rest of the Javascript and CSS related to the alert is for the custom alert option.
Custom Alert: I'm going to touch on this from two perspectives: the CSS and the JS.
CSS: The only required CSS is ensuring the pop-up alert ack button has the style cursor: pointer. I believe that styling beyond that is strictly preference. The CSS that is related to the alert includes the following classes and ids: #noted, #notice-wrapper, notice-box, and notHead.
JS: There are just a few lines of JS in the cookie-ish function, giveItBack. The notice function for the alert box.
Cookie-ish vs. subtotal: The fix for this issue (and what would have been an issue with tsv) required a few additional lines of code in the cookie-ish function, giveItBack. savoring did not change.
Cookie-ish Alignment: Currently, the configuration is identified by the name of the file or webpage and by iteration (when you have more than one table, the order in which they appear by index). Consider the impact of reordering tables or renaming the file/page.
Dynamic Sizing Chunk
This chunk can go anywhere in your script; assumed: echo=FALSE is default, or the output option hides echoes.
```{r spicy, engine="js", include=FALSE, results="asis"}
scrp = ["https://cdnjs.cloudflare.com/ajax/libs/css-element-queries/1.2.3/ElementQueries.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/css-element-queries/1.2.3/ResizeSensor.js"];
setTimeout(function(){ // this function adds the URLs to the HTML <head>
for(i=0; i < scrp.length; i++) {
script = document.createElement('script');
script.src = scrp[i];
script.type = "text/javascript";
document.head.appendChild(script);
}
ElementQueries.listen(); // just listen!!
}, 200); //wait a sec!
```
The JS for the Cookie-ish and Alerts
There should be plenty of comments to decipher the basic idea of what this does.
```{r listenOrElse,results="asis",engine="js"}
// save current state of the tables to my browser
setTimeout(function(){
// add to buttons
document.querySelector('a#saveBtn').addEventListener('click', savoring);
document.querySelector('a#restoBtn').addEventListener('click', giveItBack);
function savoring() { // function to save
el = document.querySelectorAll('.rpivotTable');
path = window.location.pathname.split("/").pop().split(".").slice()[0]; //filename
for(i=0; i < el.length; i++){
elId = el[i].getAttribute("id");
stringy = $('#' + elId).data("pivotUIOptions"); // collect rows/col filters
delete stringy['aggregators']; // remove not-parse-friendly keys
delete stringy['renderers'];
stringy2 = JSON.stringify(stringy); // one key:value pair for storage
window.localStorage.setItem(path + '_table' + i, stringy2); // STORE it!
}
};
function giveItBack() { // function to regurgitate
el = document.querySelectorAll('.rpivotTable');
console.log("working on the giver");
ods = [...el[0].ownerDocument.scripts]; // make it an array
path = window.location.pathname.split("/").pop().split(".").slice()[0]; //filename
for(j=0; j < el.length; j++){
elId = el[j].getAttribute("id");
where = ods.filter(function(ods){ // filter scripts for data
return ods.dataset['for'] === elId;
})[0].innerHTML;
where2 = JSON.parse(where).x.data; // format data for pivotUI()
where3 = HTMLWidgets.dataframeToD3(where2); // ...still formatting
if(window.localStorage.getItem(path + '_table' + j) === null) { // alert
// basic system alert
//jj = j + 1;
//alert("WARNING: There is no saved pivot table configuration for " + path + "'s table " + jj + ".");
// custom alert
msg = "<b>WARNING</b><br><br>There is no saved pivot table configuration for<br>" + path + "."
notice(msg);
// Either Alert: from here on needed whether basic or custom alert
continue;
}
gimme = window.localStorage.getItem(path + '_table' + j); // get storage
gimmeMore = JSON.parse(gimme); // prepare for recall
if(where.includes('"subtotals":true')){ // is the option 'subtotals' used?
gimmeMore.renderers = $.pivotUtilities.subtotal_renderers;
gimmeMore.dataClass = $.pivotUtilities.SubtotalPivotData;
};
if(where.includes('"tsv":true')){ // is the option 'tsv' used?
gimmeMore.renderers = $.extend(gimmeMore.renderers, $.pivotUtilities.export_renderers);
};
$('#' + elId).pivotUI(where3, gimmeMore, true, "en"); // put it back!
}
};
function notice(msg) { // all of this is for the custom alert box
function cr() {
if(document.querySelector('#notice-wrapper') === null) { // if an alert doesn't exist
wrapper = document.createElement('div');
wrapper.id = 'notice-wrapper';
html = "<div id='notice-box'><h2 id='notHead'></h2><div id='noticeBtns'>";
html += "<button id='noted'>OK</button></div></div>";
wrapper.innerHTML = html;
document.body.appendChild(wrapper);
}
insta = document.querySelector('#notice-wrapper'); // a container for the alert box
placer(insta);
return(insta);
}
function placer(insta) { // make the size reasonable, based on viewing screen
wrapper = insta;
winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientheight;
wrapper.style.height = winHeight + "px";
}
function showy(el) {
el.style.display = "block"; // keep the content confined; add a control
el.style.opacity = 1;
}
function goAway(el) { // use the control to hide the alert when ack
el.style.opacity = 0;
setTimeout(function(){
el.style.display = "none";
}, 1000);
}
function takeAction(msg) { // use all of the above: make alert and render it
insta = cr();
insta.querySelector('#notHead').innerHTML = msg;
showy(insta);
insta.querySelector('#noted').addEventListener('click', function() {
goAway(insta);
}, false);
}
takeAction(msg); // pop-up ENGAGED
}
},200); // give me a sec—my browser may be slow... or my widgets may be fast... or...
```
The CSS
This will not go into an R chunk. This is all of the CSS used in my example. I did not use an external style sheet, although that is a better practice.
This CSS controls the alert, the buttons used for saving and retrieving the configuration, along with body and margin styles. I have included the CSS to make the pivot tables pop out, but it is commented out (these are /* CSS commenters */). You can use/not use at your discretion.
<style>
body { /*push content away from far right and left edges*/
margin-right: 2%;
margin-left: 2%;
}
.main-container {
max-width: unset; // remove default from RMD
}
.rpivotTable {
overflow:auto; /*this is absolutely needed*/
/*resize: both; <- cannot have this*/
/*box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
-moz-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
-webkit-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
border: 1px solid white;
padding: 5px;
margin: 5px 20px 50px 5px;
max-width: 1100px;*/
}
.btn, #noted {
vertical-align: middle;
-moz-box-shadow: 0px 10px 14px -7px #000000;
-webkit-box-shadow: 0px 10px 14px -7px #000000;
box-shadow: 0px 10px 14px -7px #000000;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
border: .5px solid black;
display: inline-block;
font-size: 1.3em;
padding: .3em 0px;
width: 18em;
text-decoration: none; /*no underline!!*/
cursor: pointer;
}
.btn:active, #noted:active { /*simulate movement*/
position: relative;
top: 1px;
}
#notice-wrapper {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1000000;
background: transparent;
display: none;
transition: opacity 1s ease-in;
}
#notice-box {
-moz-box-shadow: 0px 10px 14px -7px #000000;
-webkit-box-shadow: 0px 10px 14px -7px #000000;
box-shadow: 0px 10px 14px -7px #000000;
border-radius: 4px;
border: .5px solid black;
width = 300px;
background: #003b70;
color: white;
min-height: 200px;
position: absolute;
top: 50%;
left: 50%;
margin: -100px 0 0 -150px;
}
#notHead {
text-align: center;
font-size: 1.3em;
padding: 4px;
margin: 2.5em;
font-family: Verdana, sans-serif;
}
#noted {
background: #b21e29;
margin: .5em;
width: 120px;
font-family: Verdana, sans-serif;
}
</style>
R <- that too... Well, the rest of the RMD...
Here is the code for the RMD I use as an example. The only thing that I didn't include are my version notes (at 16 now...).
---
title: "rpivottable_test"
output: html_document
---
```{r setup,include=F}
knitr::opts_chunk$set(echo = FALSE)
```
```{r data,include=F}
# devtools::install_github("fraupflaume/rpivotTable")
library(rpivotTable)
data(mtcars)
names(mtcars)[10] <- "George.Dontas"
```
## Make it Interesting...or not
Do you want to save or restore the previously saved pivot tables' configuration?
<!--- cookie-ish's buttons --->
<a id='saveBtn' class='btn' style="background-color:#003b70;color:white;">Save Current Configuration</a>
<a id='restoBtn' class='btn' style="background-color:#b21e29;color:white;">Restore Previous Configuration</a>
```{r showMe, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols = c("cyl"), width = "90%", height = "40%",
rendererOptions = list(
c3 = list(legend = list(show = FALSE),
data = list(labels = TRUE),
options = list(responsive = TRUE,
maintainAspectRatio = FALSE),
size = list(width = "600",
height = "500")),
d3 = list(size = list(width = "500", height = "500"))))
```
`r stringi::stri_rand_lipsum(3)`
## How about Another Table?
Tell me things. Make sure I am not going to overlap later. You better be listening!
```{r morePressure, echo=FALSE, fig.show="hold"}
rp <- rpivotTable(mtcars, rows = c("mpg", "am"), cols = "cyl",
width = "90%", height = "40%", subtotals = T,
tsv = T,
rendererOptions = list(
c3 = list(legend = list(show = FALSE), # this works!!
data = list(labels = TRUE),
size = list(width = "600",
height = "500"),
options = list(responsive = TRUE,
maintainAspectRatio = FALSE))))
rp
```
This should be *anywhere* other than here.
```{r itsMine, echo=FALSE, fig.show="hold"}
df1 <- data.frame(where = LETTERS[1:3], what = c(3.6, 5.6, 1.1))
x = rpivotTable(df1, width="80%", height="40%",
aggregatorName = "Count",
vals = "Sum",
cols = "where",
rows = "what",
rendererOptions = list(c3 = list(legend = list(show = FALSE), # this works!!
data = list(labels = TRUE),
size = list(width = "500",
height = "500"),
options = list(responsive = TRUE,
maintainAspectRatio = FALSE))))
x
```
Put something here
I only controlled d3 size in the first table. The next image reflects the difference between control and uncontrolled Treemap sizing.
I zoomed out in my browser so I could see the limits of the second treemap:
While I tried to provide enough information that anyone who came across this Q&A could potentially use it, I anxiously await when the questioner finds a way to break it :)

Related

How to create dynamic custom style on mat -dialog on each mat-table row with top triangle in Angular - Material

Here is my sample code.
Please find my working sample code here
I need to set triangle on right top corner in mat-dialog box - Angular.
I am getting top right corner triangle dialog box using static css on last row.
But here not able to get on each row on change request button click.
The below code is for the Dialog box Component
openDialog(Id, Currency, Amount, Reason, StatusDescription, payment, event) {
let targetAttr = event.target.getBoundingClientRect();
const dialogConfig = new MatDialogConfig();
dialogConfig.disableClose = true;
dialogConfig.autoFocus = true;
dialogConfig.data = {
Id: Id,
Reason: Reason,
StatusDescription: StatusDescription
};
dialogConfig.position = {
top: targetAttr.y + targetAttr.height + 10 + "px",
left: targetAttr.x - targetAttr.width - 20 + "px"
};
dialogConfig.panelClass = ['my-panel','arrow-top'];
const dialogRef = this.dialog.open(EditingDialogComponent, dialogConfig);
dialogRef.afterClosed().subscribe(
data => {
console.log("Dialog output:", data)
}
);
}
The Below code is from style.scss
/* Add application styles & imports to this file! */
#import "~#angular/material/prebuilt-themes/indigo-pink.css";
.my-panel {
overflow: hidden !important;
border-radius: 5px !important;
padding: 0px !important;
color: #fff;
}
.my-panel.arrow-top {
margin-top: 40px;
}
.my-panel.arrow-top:after {
content: " ";
position: absolute;
right: 100px;
top: 365px;
border-top: none;
border-right: 15px solid transparent;
border-left: 15px solid transparent;
border-bottom: 15px solid gray;
}
I am getting like this.
But I want dialog box with upper arrow on each row under change request button click event

Styling element in shadow dom

I have two custom elements
<desktop-canvas id="desktop">
#shadow-root (open)
<desktop-window>
</desktop-window>
<desktop-canvas>
I'm trying to style <desktop-window> like so:
#desktop::shadow desktop-window {
background-color: red;
padding: 25px;
margin: 25px;
display: block;
}
But desktop-window dosen't receive the style. What am I doing wrong? The same syntax seems to be working in this codepen (not by me): https://codepen.io/matt-west/pen/FtmBL
As announced here...
Starting in Chrome 63, you cannot use the shadow-piercing selectors ::shadow and /deep/ to style content inside of a shadow root.
According to that page you are only affected if you use Shadow DOM v0 components. You either use the shady DOM polyfill, switch to Shadow DOM v1 components or place the styles inside the component and use :host:
var XProductProto = Object.create(HTMLElement.prototype);
XProductProto.createdCallback = function() {
var shadow = this.createShadowRoot();
var img = document.createElement('img');
img.alt = this.getAttribute('data-name');
img.src = this.getAttribute('data-img');
img.width = '150';
img.height = '150';
img.className = 'product-img';
shadow.appendChild(img);
img.addEventListener('click', function(e) {
window.location = this.getAttribute('data-url');
});
var link = document.createElement('a');
link.innerText = this.getAttribute('data-name');
link.href = this.getAttribute('data-url');
link.className = 'product-name';
shadow.appendChild(link);
var styleEl = document.createElement('style');
styleEl.innerHTML = `
:host .product-img {
cursor: pointer;
background: #FFF;
margin: 0.5em;
}
:host .product-name {
display: block;
text-align: center;
text-decoration: none;
color: #08C;
border-top: 1px solid #EEE;
font-weight: bold;
padding: 0.75em 0;
}`;
shadow.appendChild(styleEl);
};
var XProduct = document.registerElement('x-product', {
prototype: XProductProto
});
body {
background: #F7F7F7;
}
x-product {
display: inline-block;
float: left;
margin: 0.5em;
border-radius: 3px;
background: #FFF;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
font-family: Helvetica, arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
<x-product data-name="Ruby" data-img="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/ruby.png" data-url="http://example.com/1"></x-product>
<x-product data-name="JavaScript" data-img="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/javascript.png" data-url="http://example.com/2"></x-product>
<x-product data-name="Python" data-img="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/python.png" data-url="http://example.com/3"></x-product>
CSS Scoping Module Level 1 provides an answer to: Why is the shadow host so weird?:
The shadow host lives outside the shadow tree, and its markup is in control of the page author, not the component author.
It would not be very good if a component used a particular class name internally in a shadow tree stylesheet, and the page author using the component accidentally also used the the same class name and put it on the shadow host. Such a situation would result in accidental styling that is impossible for the component author to predict, and confusing for the page author to debug.

Set animated html5 canvas as the background without interacting with other elements?

I got the canvas working, I'm having issues trying to position it.
Specifically I want to implement them to the same effect as:
html {
background: url(back.jpg) no-repeat center center fixed;
background-size: cover;
}
for static images. Basically no interaction with other elements, and positioned as low as possible with regards to the stacking context. Additionally, I'd like to have the canvas background as compartmentalized / as segmented as possible from the rest of the code.
By segmented, I mean something like this:
<body>
<div id="backgroundContainer">
<canvas id="myCanvas"></canvas>
</div>
<div id="everythingElseContainer">
....
</div>
<script src="canvasAnimation.js"></script>
</body>
or this:
<body>
<div id="container">
<canvas id="myCanvas"></canvas>
<div id="everythingElse">
....
</div>
</div>
<script src="canvasAnimation.js"></script>
</body>
to minimize the possibility of css conflicts.
var WIDTH;
var HEIGHT;
var canvas;
var con;
var g;
var pxs = new Array();
var rint = 60;
$(document).ready(function(){
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
canvas = document.getElementById('canvas');
$(canvas).attr('width', WIDTH).attr('height',HEIGHT);
con = canvas.getContext('2d');
for(var i = 0; i < 100; i++) {
pxs[i] = new Circle();
pxs[i].reset();
}
setInterval(draw,rint);
});
function draw() {
con.clearRect(0,0,WIDTH,HEIGHT);
for(var i = 0; i < pxs.length; i++) {
pxs[i].fade();
pxs[i].move();
pxs[i].draw();
}
}
function Circle() {
this.s = {ttl:8000, xmax:5, ymax:2, rmax:10, rt:1, xdef:960, ydef:540, xdrift:4, ydrift: 4, random:true, blink:true};
this.reset = function() {
this.x = (this.s.random ? WIDTH*Math.random() : this.s.xdef);
this.y = (this.s.random ? HEIGHT*Math.random() : this.s.ydef);
this.r = ((this.s.rmax-1)*Math.random()) + 1;
this.dx = (Math.random()*this.s.xmax) * (Math.random() < .5 ? -1 : 1);
this.dy = (Math.random()*this.s.ymax) * (Math.random() < .5 ? -1 : 1);
this.hl = (this.s.ttl/rint)*(this.r/this.s.rmax);
this.rt = Math.random()*this.hl;
this.s.rt = Math.random()+1;
this.stop = Math.random()*.2+.4;
this.s.xdrift *= Math.random() * (Math.random() < .5 ? -1 : 1);
this.s.ydrift *= Math.random() * (Math.random() < .5 ? -1 : 1);
}
this.fade = function() {
this.rt += this.s.rt;
}
this.draw = function() {
if(this.s.blink && (this.rt <= 0 || this.rt >= this.hl)) this.s.rt = this.s.rt*-1;
else if(this.rt >= this.hl) this.reset();
var newo = 1-(this.rt/this.hl);
con.beginPath();
con.arc(this.x,this.y,this.r,0,Math.PI*2,true);
con.closePath();
var cr = this.r*newo;
g = con.createRadialGradient(this.x,this.y,0,this.x,this.y,(cr <= 0 ? 1 : cr));
g.addColorStop(0.0, 'rgba(255,255,255,'+newo+')');
g.addColorStop(this.stop, 'rgba(77,101,181,'+(newo*.6)+')');
g.addColorStop(1.0, 'rgba(77,101,181,0)');
con.fillStyle = g;
con.fill();
}
this.move = function() {
this.x += (this.rt/this.hl)*this.dx;
this.y += (this.rt/this.hl)*this.dy;
if(this.x > WIDTH || this.x < 0) this.dx *= -1;
if(this.y > HEIGHT || this.y < 0) this.dy *= -1;
}
this.getX = function() { return this.x; }
this.getY = function() { return this.y; }
}
html, body, div, button, canvas, .containr {
padding: 0;
border: none;
margin: 0;
}
html, body, .containr{
height: 100%;
width: 100%;
background: none;
}
html, body {
font-size: 13px;
text-decoration: none;
font-family: Verdana, Geneva, sans-serif !important;
}
button {
transition: all 0.24s ease;
}
h1 {
font-size: 4rem;
}
button {
font-size: 5.6rem;
}
#pixie {
position:fixed;
z-index: 0;
background: black;
}
.containr>div {
background: blue;
}
.containr {
overflow:hidden;
color: #ffffff;
z-index: 9;
font-size: 256%;
white-space: nowrap;
display: flex;
flex-flow: column nowrap;
justify-content: space-around;
align-items: center;
align-content: center;
}
.btnz {
margin-left: 2.4%;
margin-right: 2.4%;
background: #ffffff;
background: rgba(0, 0, 0, .36);
text-shadow: 1px 1px 3px #000;
padding: 2rem;
}
.btnz:hover {
background: #3cb0fd;
text-shadow: none;
text-decoration: none;
}
/* Outline Out */
.hvr {
display: inline-block;
vertical-align: middle;
-webkit-transform: translateZ(0);
transform: translateZ(0);
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-moz-osx-font-smoothing: grayscale;
position: relative;
}
.hvr:before {
content: '';
position: absolute;
border: #e1e1e1 solid 5px;
top: -4px;
right: -4px;
bottom: -4px;
left: -4px;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
-webkit-transition-property: top, right, bottom, left;
transition-property: top, right, bottom, left;
}
.hvr:hover:before, .hvr:focus:before, .hvr:active:before {
top: -18px;
right: -18px;
bottom: -18px;
left: -18px;
border: #ffffff solid 8px;
}
<!doctype html>
<html lang="en">
<head datetime="2015-10-31">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="containr">
<canvas id="canvas"></canvas>
<div>
<h1>Main Title</h1>
</div>
<div>
<button class="btnz hvr">
Button Butt
</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="js.js"></script>
</body>
</html>
To move objects down in the visual order use the CSS styling z-index smaller numbers move the element down under other elements, higher numbers bring it up.See MDN z-index for more info.
To set the background of an element to a canvas use
element.style.background= "url(" + canvas.toDataURL() + ")";
To isolate of compartmentalize some code the easiest way is to wrap it in a anonymous function and call it. Everything inside it is isolated. Use 'use strict' directive to ensure you do not accidentally create global scoped variables.
A normal anonymous function does nothing and can not be used.
function(){ console.log(42); }; // does nothing
But if you wrap it in () and then add the function call tokens to the end ( ) you can call it like any function.
(function(){ console.log(42); })(); // send the meaning of life,
// the universe, and everything
// to the console.
The function below wraps up a and nothing can get access to a outside the anonymous function.
(function(){
var a = 1;
})();
But you can easily forget to put var in front of a variable making the variable visible to the entire page.
(function(){
var a = 1;
outThere = 2; // Oh no this is has been placed in
// global scope because it is missing
// the var token.
})();
To stop this use the 'use strict' directive.
(function(){
"use strict"; // this must be the very first line of the function
var a = 1;
outThere = 2; // this will cause the javascript to throw a
// ReferenceError: outThere is not defined
})();
It throws an error and stop the function from running but at least you will know that you have a leak.
Everything inside the anonymous function will manage itself. Deleting itself when not needed any more. Or remaining in memory if the Javascript engine holds an internal reference.
The next function starts up and calls its own function doSomething then exits and is deleted completely including the big array.
(function(){
var bigArray = new Array(100000000);
function doSomething(){
console.log("Whats up?");
}
doSomething();
})();
The next one will create a big array and hold that array in memory for 10 seconds (lifeTime). This is because the setTimeout has given the javascript engine an internal reference to doSomething. As long as that reference exists the bigArray will remain (because of closure). After the timeout the reference his no longer need and thus disposed causing all associated referances to go as well and thus disappear. All done via the magic of garbage collection.
Info on Clouser
Info on Garbage collection MDN is out of date but I am sure a quick search on StackOverflow will help.
(function(){
var bigArray = new Array(100000000);
function doSomething(){
console.log("Big Array has had its time.");
}
setTimeout(doSomething,10000);
})();
Attaching an object to items outside the anonymous function scope will expose data in that object to the global scope.
The next function adds a property to a DOM element. This is visible to the global scope and also means that the lifetime of the function will be as long as that element exists.
(function(){
function Info(){
... create info ..
}
var element = document.getElementById("thisOutsideWorld");
var importantPrivateInfo = new Info();
element.keepThis = importantPrivateInfo;
})();
But this does not apply to primitive types as they are copied not referenced. These are Numbers, Strings, Booleans , Undefined, Null...
So to set the background to a canvas via a compartmentalized function see the following function
(function(){
'use strict';
var myCanvas = document.createElement("canvas");
myCanvas .width = 1024;
myCanvas .height =1024;
var ctx = canvas.getContext("2d");
// toDo
// draw the stuff you want.
var el = document.getElementById("myElement");
if(el !== null){
el.style.background = "url("+canvas.toDataURL()+")";
}
// all done
})(); // once run it will delete the canvas and ctx and leave only the copied dataURL
You may think that this exposes the canvas. But it is safe as the canvas is converted to a string and strings are copied not referenced.
If you need to keep the canvas for some period then use a timer to create an internal reference to the anonymous function
The following function will create a canvas and update it every second for 100 seconds. After that it will be deleted and completely gone.
(function(){
'use strict';
var myCanvas = document.createElement("canvas");
myCanvas .width = 1024;
myCanvas .height =1024;
var lifeCounter = 0;
var ctx = canvas.getContext("2d");
// toDo
// draw the stuff you want.
var el = document.getElementById("myElement");
function update(){
// draw stuff on the canvas
if(el !== null){
el.style.background = "url("+canvas.toDataURL()+")";
}
lifeCounter += 1;
if(lifeCounter < 100){
setTimeout(update,1000);
}
}
update(); //start the updates
// all done
})();
Hope this helps.

Dynamic Text with surrounding line, whose container has a background image

I need to implement something like this..
-------------------------
| |
| |
|---- dynamic text --- |
| |
-------------------------
I want the line surrounding the "dynamic text" in css.
I tried using &::before and &::after css, but still when the dynamic text changes i need to stretch/decrease that one. Any ideas?
You could use JavaScript to set the width of :before and :after :pseudo-elements dynamically.
function foo() {
var ss = document.styleSheets;
var text = document.getElementById('text');
var box = document.getElementById('box');
var totalWidth = getComputedStyle(box).width.slice(0, -2);
var textWidth = text.offsetWidth;
var lineWidth;
var margin = 4; // Set the margin between text and line.
for (i = 0; i < ss.length; i++) {
var rules = ss[i];
for (j = 0; j < rules.cssRules.length; j++) {
var r = rules.cssRules[j];
if (r.selectorText == "#text:before") {
// If you want the margin to be set on both sides of the line,
// replace 'margin' with '(margin * 2)' in the next line.
lineWidth = ((totalWidth / 2) - (textWidth / 2) - margin);
r.style.width = lineWidth + 'px';
r.style.left = -(lineWidth + margin) + 'px';
} else if (r.selectorText == "#text:after") {
r.style.width = lineWidth + 'px';
r.style.right = -(lineWidth + margin) + 'px';
}
}
}
}
foo();
#box {
width: 300px;
height: 200px;
background-color: #FF0000;
line-height: 200px;
text-align: center;
}
#text {
position: relative;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 4px;
color: white;
}
#text:before {
content: "";
position: absolute;
border-bottom: 1px solid black;
height: 100px;
}
#text:after {
content: "";
position: absolute;
border-bottom: 1px solid black;
height: 100px;
}
<div id="box"><span id="text">This is the text which is dynamic</span>
</div>
<div id="divWithBackground">
<div id="divActsAsLine">
<span id="DynamicText">Your text here </span>
</div>
</div>
Now the CSS
#divActsAsLine{
border-botton:1px solid #00;
text-align:center;
}
#DynamicText{
position:relative;
background-color: #fff;
margin-bottom:-20px /*Adjust this based on your requirements*/
z-index:1 /* optional */
}
The logic is to make a div margin as background line and make the span overlap this line, to do this we need to either decrease or increase margin property. you might need to use z-index if necessary

Styling Google Maps InfoWindow

I've been attempting to style my Google Maps InfoWindow, but the documentation is very limited on this topic. How do you style an InfoWindow?
Google wrote some code to assist with this. Here are some examples: Example using InfoBubble, Styled markers and Info Window Custom (using OverlayView).
The code in the links above take different routes to achieve similar results. The gist of it is that it is not easy to style InfoWindows directly, and it might be easier to use the additional InfoBubble class instead of InfoWindow, or to override GOverlay. Another option would be to modify the elements of the InfoWindow using javascript (or jQuery), like later ATOzTOA suggested.
Possibly the simplest of these examples is using InfoBubble instead of InfoWindow. InfoBubble is available by importing this file (which you should host yourself): http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobubble/src/infobubble.js
InfoBubble's Github project page.
InfoBubble is very stylable, compared to InfoWindow:
infoBubble = new InfoBubble({
map: map,
content: '<div class="mylabel">The label</div>',
position: new google.maps.LatLng(-32.0, 149.0),
shadowStyle: 1,
padding: 0,
backgroundColor: 'rgb(57,57,57)',
borderRadius: 5,
arrowSize: 10,
borderWidth: 1,
borderColor: '#2c2c2c',
disableAutoPan: true,
hideCloseButton: true,
arrowPosition: 30,
backgroundClassName: 'transparent',
arrowStyle: 2
});
infoBubble.open();
You can also call it with a given map and marker to open on:
infoBubble.open(map, marker);
As another example, the Info Window Custom example extends the GOverlay class from the Google Maps API and uses this as a base for creating a more flexible info window. It first creates the class:
/* An InfoBox is like an info window, but it displays
* under the marker, opens quicker, and has flexible styling.
* #param {GLatLng} latlng Point to place bar at
* #param {Map} map The map on which to display this InfoBox.
* #param {Object} opts Passes configuration options - content,
* offsetVertical, offsetHorizontal, className, height, width
*/
function InfoBox(opts) {
google.maps.OverlayView.call(this);
this.latlng_ = opts.latlng;
this.map_ = opts.map;
this.offsetVertical_ = -195;
this.offsetHorizontal_ = 0;
this.height_ = 165;
this.width_ = 266;
var me = this;
this.boundsChangedListener_ =
google.maps.event.addListener(this.map_, "bounds_changed", function() {
return me.panMap.apply(me);
});
// Once the properties of this OverlayView are initialized, set its map so
// that we can display it. This will trigger calls to panes_changed and
// draw.
this.setMap(this.map_);
}
after which it proceeds to override GOverlay:
InfoBox.prototype = new google.maps.OverlayView();
You should then override the methods you need: createElement, draw, remove and panMap. It gets rather involved, but in theory you are just drawing a div on the map yourself now, instead of using a normal Info Window.
You can modify the whole InfoWindow using jquery alone...
var popup = new google.maps.InfoWindow({
content:'<p id="hook">Hello World!</p>'
});
Here the <p> element will act as a hook into the actual InfoWindow. Once the domready fires, the element will become active and accessible using javascript/jquery, like $('#hook').parent().parent().parent().parent().
The below code just sets a 2 pixel border around the InfoWindow.
google.maps.event.addListener(popup, 'domready', function() {
var l = $('#hook').parent().parent().parent().siblings();
for (var i = 0; i < l.length; i++) {
if($(l[i]).css('z-index') == 'auto') {
$(l[i]).css('border-radius', '16px 16px 16px 16px');
$(l[i]).css('border', '2px solid red');
}
}
});
You can do anything like setting a new CSS class or just adding a new element.
Play around with the elements to get what you need...
I used the following code to apply some external CSS:
boxText = document.createElement("html");
boxText.innerHTML = "<head><link rel='stylesheet' href='style.css'/></head><body>[some html]<body>";
infowindow.setContent(boxText);
infowindow.open(map, marker);
google.maps.event.addListener(infowindow, 'domready', function() {
// Reference to the DIV that wraps the bottom of infowindow
var iwOuter = $('.gm-style-iw');
/* Since this div is in a position prior to .gm-div style-iw.
* We use jQuery and create a iwBackground variable,
* and took advantage of the existing reference .gm-style-iw for the previous div with .prev().
*/
var iwBackground = iwOuter.prev();
// Removes background shadow DIV
iwBackground.children(':nth-child(2)').css({'display' : 'none'});
// Removes white background DIV
iwBackground.children(':nth-child(4)').css({'display' : 'none'});
// Moves the infowindow 115px to the right.
iwOuter.parent().parent().css({left: '115px'});
// Moves the shadow of the arrow 76px to the left margin.
iwBackground.children(':nth-child(1)').attr('style', function(i,s){ return s + 'left: 76px !important;'});
// Moves the arrow 76px to the left margin.
iwBackground.children(':nth-child(3)').attr('style', function(i,s){ return s + 'left: 76px !important;'});
// Changes the desired tail shadow color.
iwBackground.children(':nth-child(3)').find('div').children().css({'box-shadow': 'rgba(72, 181, 233, 0.6) 0px 1px 6px', 'z-index' : '1'});
// Reference to the div that groups the close button elements.
var iwCloseBtn = iwOuter.next();
// Apply the desired effect to the close button
iwCloseBtn.css({opacity: '1', right: '38px', top: '3px', border: '7px solid #48b5e9', 'border-radius': '13px', 'box-shadow': '0 0 5px #3990B9'});
// If the content of infowindow not exceed the set maximum height, then the gradient is removed.
if($('.iw-content').height() < 140){
$('.iw-bottom-gradient').css({display: 'none'});
}
// The API automatically applies 0.7 opacity to the button after the mouseout event. This function reverses this event to the desired value.
iwCloseBtn.mouseout(function(){
$(this).css({opacity: '1'});
});
});
//CSS put in stylesheet
.gm-style-iw {
background-color: rgb(237, 28, 36);
border: 1px solid rgba(72, 181, 233, 0.6);
border-radius: 10px;
box-shadow: 0 1px 6px rgba(178, 178, 178, 0.6);
color: rgb(255, 255, 255) !important;
font-family: gothambook;
text-align: center;
top: 15px !important;
width: 150px !important;
}
I have design google map infowindow with image & some content as per below.
map_script (Just for infowindow html reference)
for (i = 0; i < locations.length; i++) {
var latlng = new google.maps.LatLng(locations[i][1], locations[i][2]);
marker = new google.maps.Marker({
position: latlng,
map: map,
icon: "<?php echo plugins_url( 'assets/img/map-pin.png', ELEMENTOR_ES__FILE__ ); ?>"
});
var property_img = locations[i][6],
title = locations[i][0],
price = locations[i][3],
bedrooms = locations[i][4],
type = locations[i][5],
listed_on = locations[i][7],
prop_url = locations[i][8];
content = "<div class='map_info_wrapper'><a href="+prop_url+"><div class='img_wrapper'><img src="+property_img+"></div>"+
"<div class='property_content_wrap'>"+
"<div class='property_title'>"+
"<span>"+title+"</span>"+
"</div>"+
"<div class='property_price'>"+
"<span>"+price+"</span>"+
"</div>"+
"<div class='property_bed_type'>"+
"<span>"+bedrooms+"</span>"+
"<ul><li>"+type+"</li></ul>"+
"</div>"+
"<div class='property_listed_date'>"+
"<span>Listed on "+listed_on+"</span>"+
"</div>"+
"</div></a></div>";
google.maps.event.addListener(marker, 'click', (function(marker, content, i) {
return function() {
infowindow.setContent(content);
infowindow.open(map, marker);
}
})(marker, content, i));
}
Most important thing is CSS
#propertymap .gm-style-iw{
box-shadow:none;
color:#515151;
font-family: "Georgia", "Open Sans", Sans-serif;
text-align: center;
width: 100% !important;
border-radius: 0;
left: 0 !important;
top: 20px !important;
}
#propertymap .gm-style > div > div > div > div > div > div > div {
background: none!important;
}
.gm-style > div > div > div > div > div > div > div:nth-child(2) {
box-shadow: none!important;
}
#propertymap .gm-style-iw > div > div{
background: #FFF!important;
}
#propertymap .gm-style-iw a{
text-decoration: none;
}
#propertymap .gm-style-iw > div{
width: 245px !important
}
#propertymap .gm-style-iw .img_wrapper {
height: 150px;
overflow: hidden;
width: 100%;
text-align: center;
margin: 0px auto;
}
#propertymap .gm-style-iw .img_wrapper > img {
width: 100%;
height:auto;
}
#propertymap .gm-style-iw .property_content_wrap {
padding: 0px 20px;
}
#propertymap .gm-style-iw .property_title{
min-height: auto;
}
Use the InfoBox plugin from the Google Maps Utility Library. It makes styling/managing map pop-ups much easier.
Note that you'll need to make sure it loads after the google maps API:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_KEY&callback=initMap" async defer></script>
<script src="/js/infobox_packed.js" async defer></script>
Map InfoWindow supports html and css.
I usually create html string with css class. So I can attach any style I want to.
var contentString = '<div>Any Html here</div>';
marker.infowindow = new google.maps.InfoWindow({
content: contentString,
});
Here is the ref.
You could use a css class too.
$('#hook').parent().parent().parent().siblings().addClass("class_name");
Good day!

Resources