2
\$\begingroup\$

There is a web page I visit which I would like to print. However, I only want to print a couple of things on it, not everything.

So, I am making a bookmarklet that I can use whenever I visit the page to hide everything from the printer except what I want to print.

The site already has a class, 'd-print-none', to hide an element from the printer.

The following code is the javascript I made to base my bookmarklet on.

My idea is to first apply the 'd-print-none' class to everything. Then selectively remove it.

I remove the class from the element that contains what I would like to print as well as any of its children.

Then I recursively remove it from the parents until I reach the document element.

function removeClass(t_selector, t_class){
    document.querySelectorAll(t_selector + ' *').forEach(x=>x.classList.remove(t_class))
   
    node = document.querySelector(t_selector)
    while (node && node.nodeType != Node.DOCUMENT_NODE) {
        node.classList.remove.(t_class);
        node = node.parentNode;
    }
}

document.querySelectorAll( '*' ).forEach(x=>x.classList.add('d-print-none'))
removeClass('app-flex-chart', 'd-print-none')
removeClass('app-flex-grid', 'd-print-none')

Adding the class to all elements was pretty straightforward and succinct as was removing it from all of the target element's children.

Climbing the tree to apply it to the element itself and any of its parents was more verbose and error prone. Sadly, there appears to be no css selector to find all parents of an element.

I paired the loop down to be as succinct and readable as I could.

I am looking for ideas on how to make it more robust, succinct, or readable. Open to any and all suggestions and criticisms.

For that while loop, I had to check if the node I was on existed and check to make sure it wasn't the document node, either case would cause an error.

EDIT: To show generic structure of page

<html>
    <head>...</head>
    <body>
        <many>
            <nested>
                <tags>
                    <app-flex-chart>
                        <many-more-nested-tags>Actual Content</many-more-nested-tags>
                    </app-flex-chart>
                </tags>
            </nested>
        </many>
    </body>
</html>

The app-flex-chart tag is the only unique piece along the chain to the content. All other classes and tags seem to be reused throughout the page.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Declare variables before using them - your node variable is never declared. Use let node = instead.

Typo? node.classList.remove.(t_class) should be node.classList.remove(t_class);

Variable casing The vast majority of professional JavaScript uses camelCase for most variables, including parameters. There are a few exceptions (like ALL_CAPS for things like constant magic numbers, and PascalCase for constructors and namespaces).

Variable names It's not entirely clear on the initial encounter of removeClass what the parameters mean. Maybe change:

  • t_selector -> selectorToShow
  • t_class -> hiddenClassName

or something similar.

Always use strict equality, avoid sloppy equality to avoid readers of the code having to understand coercion weirdness to be 100% confident of what the code is doing.

Semicolons Either use semicolons when appropriate, or (if you don't like them and you're an expert who can avoid the pitfalls of ASI) don't. To be stylistically consistent, choose one style and stick with it. Consider a linting rule.

Relying on a site's classes is unreliable, especially for sites that are being actively maintained and developed. Many of those who write bookmarks and userscripts are familiar with this issue. The site may change their CSS, resulting in d-print-none doing nothing. Bookmarklets and userscripts are prone to requiring updates when a site changes regardless - but reducing the chance of needing maintenance when a change occurs is good.

Whatever d-print-none does currently, I'd append another <style> tag that inserts your own class (or data attribute) that does the same thing, eg:

const style = document.body.appendChild(document.createElement('style'));
style.textContent = `
@media print
{    
    .melted-bookmarklet-print-none, .melted-bookmarklet-print-none *
    {
        display: none !important;
    }
}
`;

Then toggle that class instead (and do style.remove() at the end if you want).

(You might be tempted to iterate through the elements and apply the style directly to elements, but that could break the site's built-in display properties if it happens to have styles already applied directly to elements.)

Or, you could use CSS alone instead of iterating through elements. How exactly to do this depends on where the app-flex-chart and app-flex-grid are located on the page, but it could look something like this, if they're both descendants of the <body>:

const style = document.body.appendChild(document.createElement('style'));
style.textContent = `
@media print
{    
    body > *:not(app-flex-chart):not(app-flex-grid),
    body > *:not(app-flex-chart):not(app-flex-grid) * {
        display: none !important;
    }
}
`;

(feel free to engineer to be more flexible and less repetitive if you think it'd be helpful)

This way, all elements which are not the .app-flex-chart nor .app-flex-grid will be hidden, without any intermediate classes or element iteration.

Or save a reference to the two elements you want to print, remove everything, then append the two back:

const elms = ['app-flex-chart', 'app-flex-grid'].map(s => document.querySelector(s));
document.body.textContent = '';
for (const elm of elms) {
  document.body.appendChild(elm);
  // if needed to put them on separate lines:
  document.body.appendChild(document.createElement('div'));
}

That's the version I'd prefer, if feasible. You may have to insert intermediate elements for the page's styles to apply. (If that gets too complicated, use a different approach.) If you want the original page to remain interactable, you could open a new window which gets populated with the content of elms instead. (You'll also have to append <style> and CSS link tags from the original page)

Bookmarklet or userscript? You're currently using this as a bookmarklet. It depends how you're managing them, but if you aren't using a special utility for it, I think userscripts are more maintainable, due to a few factors:

  • Userscript editor interfaces are meant to be usable as code editors. In contrast, a bookmarklet, when installed, isn't very user-friendly to edit:

    enter image description here

  • Userscripts can be saved in separate external files, rather than existing in the browser's internal ether.

  • Userscripts are more flexible in when they're run. Most importantly, they can run automatically without requiring a click. You may find this helpful.

If you want to try this out as a userscript, try Tampermonkey.

Depending on how you're using the printed page and how often you're printing, you can consider using Puppeteer in Node to scrape and print the elements completely automatically.

\$\endgroup\$
6
  • \$\begingroup\$ I am still digesting your answer. However, I most like your suggestion to use css alone. However, when I use your example code, everything becomes hidden. \$\endgroup\$
    – S. Melted
    Commented Dec 21, 2020 at 19:38
  • \$\begingroup\$ You'll have to tweak the code depending on the layout of the site in question. Working demo here: jsfiddle.net/wza6x9mr \$\endgroup\$ Commented Dec 21, 2020 at 19:45
  • \$\begingroup\$ Ah, I see the issue now, but don't know the solution. The elements to be shown are nested within other elements. These other parent elements need to be shown as well or the children remain hidden. See demo: jsfiddle.net/m0cyqr28 \$\endgroup\$
    – S. Melted
    Commented Dec 21, 2020 at 20:10
  • \$\begingroup\$ If they're not on the same level, it'll be more tedious: jsfiddle.net/5f3xvwb0 I'd prefer a different method \$\endgroup\$ Commented Dec 21, 2020 at 20:34
  • \$\begingroup\$ The two tags are nested 9 and 10 levels deep. \$\endgroup\$
    – S. Melted
    Commented Dec 21, 2020 at 20:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.