Loading Admin Bar via Javascript

When caching the entire page of a Craft CMS web­site using FastC­GI caching, Var­nish, or plu­g­ins like Blitz, using the {{ adminbar() }} Twig embed func­tion is not an option. If you did that, Admin Bar’s tool­bar would be cached with the page and it would appear — and expose edit links — to the end user.

Admin Bar 3.1.3 intro­duced a fea­ture that made it a lit­tle eas­i­er to add the Admin Bar tool­bar to sites that are ful­ly cached. This fea­ture requires the {{ getAdminBarAssets() }} twig func­tion be put on the page some­where — which puts the toolbar’s CSS and Javascript on the page — then an HTTP request can be used to get the tool­bar HTML that is ren­dered for that page. To put it all togeth­er, a JS func­tion, window.adminBarInit();, needs to be fired to fin­ish ini­tial­iz­ing the toolbar.

To make this a lit­tle eas­i­er, here are a few exam­ples for dif­fer­ent ways to embed the tool­bar in dif­fer­ent Javascript setups.

Screenshot of Admin Bar

The Process

Each of these exam­ples try to do the same things:

  1. Call out to a con­troller in Admin Bar to see if the tool­bar can be embed­ded (based on whether or not a user is logged in). If it can, a content string will include all of the toolbar’s HTML markup.
  2. The HTML gets added to the top of the main <body> ele­ment (or an oth­er that is spec­i­fied by the script).
  3. Upon embed­ding the HTML, the window.adminBarInit(); func­tion is fired.

Vanil­la Javascript

If your website’s edi­tors use a brows­er that sup­ports the fetch() API, this snip­pet can be dropped onto the page or in any oth­er Javascript file that is loaded on the page. The great thing about this script is that it doesn’t require any oth­er JS libraries or dependencies.

// Check if `fetch()` can be fired and look for `adminBarInit` function on the page
if (window.fetch && typeof window.adminBarInit === "function") {
    const data = {
        params: {}, // Admin Bar aurgments
        uri: window.location.pathname.substr(1) || '__home__', // Get URI and pass it through. Use `__home__` for your homepage.
    };

	// Make our call to the Admin Bar controller
    fetch('/actions/admin-bar/bar', {
        method: 'POST',
        body: JSON.stringify(data),
        headers:{
            'Content-Type': 'application/json'
        }
    }).then(res => res.json())
        .then((response) => {
        	// Check to see if content string came back
            if (response.response === 'success' && response.content) {
                const div = document.createElement('div');
                div.innerHTML = response.content;

                if (div.children.length > 0) {
                    // Add Admin Bar to the top of the <body> tag
                    document.body.prepend(div.children[0]);

                    // OR

                    // Add Admin Bar to the bottom of the <body> tag
                    // document.body.appendChild(div.children[0]);
                }

                // Init Admin Bar
                window.adminBarInit();
            }
        })
        .catch(error => console.error('Error:', error));
}

Javascript Class (ES6+)

If you want to class it up a bit, drop­ping this code into a file and import­ing it via JS mod­ule load­ing gives you the same result as the Vanil­la Javascript method, but it allows you to change attrib­ut­es and even hold off on fir­ing the window.adminBarInit() until you’re ready to do so.

export default class AdminBar {
    constructor(args = {}) {
        // Where does the Admin Bar HTML get placed
        this.container = args.container ? document.querySelector(args.container) : document.body;
        
        this.loaded = false;
        
        // Prepend or append using values: top OR bottom
        this.location = args.location || 'top';
        
        // Parameters passed into Admin Bar
        this.params = args.params || {};
        
        // The URI of the page. Use '__home__' for the homepage
        this.uri = args.uri || window.location.pathname.substr(1) || '__home__';
        
        // Tell this script to hold off on fireing the `load` and `init` functions
        this.wait = args.wait || false;

        // Check to see if functions from `{{ getAdminBarAssets() }}` are on the page
        if (typeof window.adminBarInit === "function" && window.fetch && !this.wait) {
            if (this.load()) {
                this.init();
            }
        }
    }
	// Call adminBarInit() to add Javascript events via the {{ getAdminBarAssets() }} Twig tag
    static init() {
        window.adminBarInit();

        console.log('Embedded Admin Bar for URI', this.uri);
    }
    // Load Admin Bar and check to see if script is availbale
    // Pass in an optional callback funciton that fires when script is loaded and succesful
    load(cb = false) {
        const data = {
            params: this.params,
            uri: this.uri,
        };

        fetch('/actions/admin-bar/bar', {
            method: 'POST',
            body: JSON.stringify(data),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(res => res.json())
            .then((response) => {
                if (response.response === 'success') {
                    const div = document.createElement('div');
                    div.innerHTML = response.content;

                    if (div.children.length > 0) {
                        if (this.location === 'top') {
                            this.container.prepend(div.children[0]);
                        } else {
                            this.container.appendChild(div.children[0]);
                        }

                        this.loaded = true;

                        if (typeof cb === 'function') {
                            cb();
                        }

                        return true;
                    }
                }

                return false;
            })
            .catch(errorMessage => console.error('Error:', errorMessage));
    }
}

To add it to your Javascript file, import it and then hoist it when you are ready.

// Import the class into your project
import AdminBar from './adminbar.js';

// Set the class to a variable to access it later
const adminbar = new AdminBar();

// That’s it.

// ==============================================================
// OR
// ==============================================================

// Options can be passed in to configure the toolbar on load
const adminbar = new AdminBar({
    container: '#masthead',
    location: 'bottom',
    params: { fixed: true },
    uri: 'a-different-path-than-the-current-page',
});

// ==============================================================
// OR
// ==============================================================

// Pass `{ wait: true }` to set properties later
let adminbar = new AdminBar({ wait: true });

// Make changes as needed
adminbar.container = '.page-header';
adminbar.params = {
    sticky: false,
};

// Pass in a callback function to init Admin Bar
adminbar.load(adminbar.init);

jQuery

If your web­site is already using jQuery, using jQuery.ajax() is a sim­ple way to get Admin Bar’s markup onto the page. It’s sim­i­lar to the fetch() exam­ple, but offers sup­port for old­er browsers.

import jQuery from 'jquery';

// get the URI from the current page
const data = {
	params = {},
	uri: window.location.pathname ?? '__home__',
}

// request Admin Bar after converting array to JSON
if (typeof adminBarInit === "function") {
    jQuery.ajax({
        type: 'POST',
        url: '/actions/admin-bar/bar',
        cache: false,
        data: JSON.stringify(data),
        dataType: 'json',
        success: function(data) {
            if (data.response === 'success') {
                // Add Admin Bar to the top of the <body> element
                jQuery('body').prepend(data.content);

                // Fire init function that gets loaded into the template
                // via the {{ getAdminBarAssets({ uri: craft.app.request.url }) }} Twig tag
                window.adminBarInit();
            }
        },
        error: function(err) {
            console.log("Error");
            console.log(err);
        }
    });
};

Vue JS Component

When Admin Bar 3.1.3 was released I cre­at­ed a sin­gle file com­po­nent for Vue­JS and includ­ed it as an exam­ple. While this method is still valid, it’s a bit messier than the fetch() exam­ples above.

What’s Next?

As Craft CMS makes it eas­i­er to go head­less, I’m think­ing of tak­ing Twig out of the equa­tion alto­geth­er. I don’t have any sol­id plans for a 4.x ver­sion, but after fry­ing a few big­ger fish I’ll begin explor­ing this idea. As always, should you, dear read­er, have any sug­ges­tions, please let me know.

Admin Bar icon

Admin Bar

Front-end short­cuts for clients logged into Craft CMS.