Best way to hide 10000 dropdown menus - javascript

Context -
I have a chat component and each individual chat message has a dropdown.
And the dropdown menu is opened by clicking the "More Options icon"(3 dots).
Each individual chat message is a "backbone item view"
One solution is to listen to click on "body", loop through all the menus and then close the dropdown by removing a class on it.
$("body").on("click", function() {
$(".drop-down-menu").each(function(idx, item) {
$(item).removeClass("open"); // open class indicated it is open via CSS
});
});
The CSS -
.drop-down-menu {
visibility: hidden;
opacity: 0;
&.open {
opacity: 1;
visibility: visible;
}
}
Will there be any performance impact if there are 10,000 messages or more?
Hence, I am looking for the best solution to hide the drop down if user clicks anywhere on the screen.
Thanks.

You can make some trivial changes that should improve the performance of your code. The first thing is that there's no reason to loop like you are doing. jQuery objects are collections and jQuery operations usually loop over the elements of a jQuery object. So:
$("body").on("click", function() {
$(".drop-down-menu").removeClass("open");
});
This will automatically remove the class open from all elements matched by the selector ".drop-down-menu". jQuery will still go over a loop internally, but it is faster to let jQuery iterate by itself than to have .each call your own callback and then inside the callback create a new jQuery object on which to call .removeClass.
Furthermore, you logically know that removing the open class from elements that do not have this class is pointless. So you can narrow the operation to only those elements where removing open makes sense:
$("body").on("click", function() {
$(".drop-down-menu.open").removeClass("open");
});
These are principles that are widely applicable and that have trivial cost to implement. Anything more than this runs into the realm of optimizations that may have downsides, and should be supported by actually profiling your code. You could replace the jQuery code with code that only uses stock DOM calls but then if you need support for old browsers the cost of dealing with this and that quirk may not be worth it. And if you are using stock DOM methods, there are different approaches that may yield different performance increases, at the cost of code complexity.

Louis is offering a quick fix with efficient jQuery selectors.
For the long run, I would suggest making each message a MessageView component which has a ContextMenuView component. That way, each view only has one menu to take care of.
Catching clicks outside of an element
Then, use the following ClickOutside view as the context menu base view. It looks complicated, but it only wraps the blur and focus DOM events to know if you clicked outside the view.
It offers a simple onClickOutside callback for the view itself and a click:outside event which is triggered on the element.
The menu view now only has to implement the following:
var ContextMenuView = ClickOutside.extend({
toggle: function(val) {
this.$el.toggleClass("open", val);
this.focus(); // little requirement
},
// here's where the magic happens!
onClickOutside: function() {
this.$el.removeClass("open");
}
});
See the demo
var app = {};
(function() {
var $body = Backbone.$(document.body);
/**
* Backbone view mixin that enables the view to catch simulated
* "click:outside" events (or simple callback) by tracking the
* mouse and focusing the element.
*
* Additional information: Since the blur event is triggered on a mouse
* button pressed and the click is triggered on mouse button released, the
* blur callback gets called first which then listen for click event on the
* body to trigger the simulated outside click.
*/
var ClickOutside = app.ClickOutside = Backbone.View.extend({
events: {
"mouseleave": "_onMouseLeave",
"mouseenter": "_onMouseEnter",
"blur": "_onBlur",
},
/**
* Overwrite the default constructor to extends events.
*/
constructor: function() {
this.mouseInside = false;
var proto = ClickOutside.prototype;
this.events = _.extend({}, proto.events, this.events);
ClickOutside.__super__.constructor.apply(this, arguments);
this.clickOnceEventName = 'click.once' + this.cid;
},
/**
* Hijack this private method to ensure the element has
* the tabindex attribute and is ready to be used.
*/
_setElement: function(el) {
ClickOutside.__super__._setElement.apply(this, arguments);
var focusEl = this.focusEl;
if (focusEl && !this.$focusElem) {
this.$focusElem = focusEl;
if (!(focusEl instanceof Backbone.$)) {
this.$focusElem = Backbone.$(focusEl);
}
} else {
this.$focusElem = this.$el;
}
this.$focusElem.attr('tabindex', -1);
},
focus: function() {
this.$focusElem.focus();
},
unfocus: function() {
this.$focusElem.blur();
$body.off(this.clickOnceEventName);
},
isMouseInside: function() {
return this.mouseInside;
},
////////////////////////////
// private Event handlers //
////////////////////////////
onClickOutside: _.noop,
_onClickOutside: function(e) {
this.onClickOutside(e);
this.$focusElem.trigger("click:outside", e);
},
_onBlur: function(e) {
var $focusElem = this.$focusElem;
if (!this.isMouseInside() && $focusElem.is(':visible')) {
$body.one(this.clickOnceEventName, this._onClickOutside.bind(this));
} else {
$focusElem.focus(); // refocus on inside click
}
},
_onMouseEnter: function(e) {
this.mouseInside = true;
},
_onMouseLeave: function(e) {
this.mouseInside = false;
},
});
var DropdownView = app.Dropdown = ClickOutside.extend({
toggle: function(val) {
this.$el.toggle(val);
this.focus();
},
onClickOutside: function() {
this.$el.hide();
}
});
})();
var DemoView = Backbone.View.extend({
className: "demo-view",
template: $("#demo-template").html(),
events: {
"click .toggle": "onToggleClick",
},
initialize: function() {
this.dropdown = new app.Dropdown();
},
render: function() {
this.$el.html(this.template);
this.dropdown.setElement(this.$(".dropdown"));
return this;
},
onToggleClick: function() {
this.dropdown.toggle(true);
},
});
$("#app")
.append(new DemoView().render().el)
.append(new DemoView().render().el);
html,
body {
height: 100%;
width: 100%;
}
.demo-view {
position: relative;
margin-bottom: 10px;
}
.dropdown {
z-index: 2;
position: absolute;
top: 100%;
background-color: gray;
padding: 10px;
outline: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<div id="app"></div>
<script type="text/template" id="demo-template">
<button type="button" class="toggle">Toggle</button>
<div class="dropdown" style="display:none;">
This is a drop down menu.
</div>
</script>
Alternatives to detect a click outside an element
If you don't want, or can't use blur and focus events, take a look at How do I detect a click outside an element? for alternative techniques.
Lazy initialization of views
Another way to make an SPA more efficient is to delay the creation of new view to the very moment you need it. Instead a creating 10k context menu views, wait for the first time the user clicks on the toggle button and create a new view if it doesn't exist yet.
toggleMenu: function(){
var menuView = this.menuView;
if (!menuView) {
menuView = this.menuView = new ContextMenuView();
this.$('.dropdown').html(menuView.render().el);
}
menuView.toggle();
}
Pagination
Passed a certain threshold of HTML inside a webpage, the browser starts to lag and it impedes the user experience. Instead of dumping 10k views into a div, only show like a 100, or the minimum to cover the visible space.
Then, when scrolling to an edge (top or bottom), append or prepend new views on demand. Like the message list in any web-based chat app, like messenger.com.

Since you will only have one drop down menu open at a time, maybe you can keep a pointer to the element or index of the element it is attached to, instead of looping through all the menus.

Related

Avoid flickering when changing existing DOM elements on page load event

I have the following DOM element change function using vanilla JavaScript where I change the span element that contains some text string of the DOM with a page load event.
With the following code, the DOM elements are changed as expected. However, they still see a minimal fraction of flickering before the DOM element change for the variable desktop and mobile.
Example of the flickering scenario:
I see the tag span "Text I want to change" for a fraction of second.
I see the changed tag span "text1 changed" after the content has fully loaded.
I think this is happening because the DOM changes are applied when the DOM content of the page is fully loaded. I would like to hide the existing tag elements until the changes have been applied and then display them.
The elements structure for desktop and mobile I want to manipulate is like the following:
<span class="first-headline target-span">Text I want to change</span>
See the concept below:
var changesObj = {
"us": {
title: "text1 changed"
},
"eu": {
title: "text2 changed"
}
};
function changes() {
var webshop = changesObj[window.location.pathname.split('/')[1]];
console.log(webshop);
var changesText;
if (!webshop) {
console.log('webshop not found');
}
changesText = webshop.title;
if (document.querySelector('.target-span').innerText.length > 0) {
var desktop = document.querySelector('.target-span');
console.log(desktop);
desktop.innerText = changesText;
console.log(desktop.innerText);
console.log("applied changes for dekstop");
}
if (document.querySelector('.target-span').innerText.lenght > 0) {
var mobile = document.querySelector('.target-span');
mobile.innerText = changesText;
console.log(mobile.innerText);
console.log("applied changes for mobile");
}
}
function invokeChanges() {
document.addEventListener("DOMContentLoaded", function () {
changes();
});
}
invokeChanges();
Is there a way to initially hide the DOM element until the change to the existing element has been applied and then show it?
I was thinking of using something inline CSS rules like the following:
Set .style.visbility='hidden' and when the text content is changed to show it with .style.visbility='visble'.
But I'm not sure how this solution should be appropriately implemented in my code.
There are a few reasons as to why it's flickering, but two preventative steps you can take:
Use defer on your script tag <script defer>, as this lets the browser handle the order your scripts are run instead of DOMContentLoaded. You also get to avoid the changes wrapper function.
As suggested by this question (and your own reasoning) attach inline CSS / a CSS file to the page to set the text as invisible, then mark it as visible
If it doesn't affect the layout of your page too much, you can also opt to dynamically generate the element instead.
However, do note that regardless of any of these, these still require JS to be executed and may still have a delay. If you are able to use it, you may be interested in prerendering your webpage - from your JS code, it looks for the route name if its eu or us, which means your page is pre-renderable.
Quick & Dirty:
Place the script tag just below your span tag:
<span class="first-headline target-span">Text I want to change</span>
<script>
var changesObj = {
"us": {
title: "text1 changed"
},
"eu": {
title: "text2 changed"
}
};
function changes() {
var webshop = changesObj[window.location.pathname.split('/')[1]];
console.log(webshop);
var changesText;
if (!webshop) {
console.log('webshop not found');
}
changesText = webshop.title;
if (document.querySelector('.target-span').innerText.length > 0) {
var desktop = document.querySelector('.target-span');
console.log(desktop);
desktop.innerText = changesText;
console.log(desktop.innerText);
console.log("applied changes for dekstop");
}
if (document.querySelector('.target-span').innerText.lenght > 0) {
var mobile = document.querySelector('.target-span');
mobile.innerText = changesText;
console.log(mobile.innerText);
console.log("applied changes for mobile");
}
}
changes();
</script>
This way you avoid the asynchronous code part.
In any case you need to wait until the DOM has been loaded before you can manipulate it. Using an even listener for DOMContentLoaded would be the way to go. So, three things need to happen:
Wait for the DOM to load
Find the elements and change the text
Make the elements visible. You can either use the property visibility: hidden or display: none. Difference is that with visibility: hidden the element will still take up space. So, the choice depends on the context.
In the first example I add a timeout so that you can see what the page looks like just before the text is changed. I also styled the <p> (display: inline-block) so that you can see the size of the hidden span.
window.location.hash = "us"; // for testing on SO
var changesObj = {
"us": { title: "text1 changed" },
"eu": { title: "text2 changed" }
};
function changes(e) {
//let webshop = changesObj[window.location.pathname.split('/')[1]];
let webshop = changesObj[window.location.hash.substring(1)]; // for testing on SO
if (webshop) {
[...document.querySelectorAll('.target-span')].forEach(span => {
span.textContent = webshop.title;
span.classList.add('show');
});
}
}
document.addEventListener('DOMContentLoaded', e => {
setTimeout(changes, 1000);
});
p {
border: thin solid black;
display: inline-block;
}
.target-span {
visibility: hidden;
}
.target-span.show {
visibility: visible;
}
<p>
<span class="first-headline target-span">Text I want to change</span>
</p>
<p>
<span class="first-headline target-span">Text I want to change</span>
</p>
In the second example I combined all the code into one HTML page. Notice that the style is defined in the header. So, when the DOM is passed the CSS will be passed as well without having to load a external style-sheet (well, there are tricks for that as well, but out of scope for this answer).
<html>
<head>
<style>
p {
border: thin solid black;
}
.target-span {
visibility: hidden;
}
.target-span.show {
visibility: visible;
}
</style>
<script>
window.location.hash = "us"; // for testing on SO
var changesObj = {
"us": {title: "text1 changed"},
"eu": {title: "text2 changed"}
};
function changes(e) {
//let webshop = changesObj[window.location.pathname.split('/')[1]];
let webshop = changesObj[window.location.hash.substring(1)]; // for testing on SO
if (webshop) {
[...document.querySelectorAll('.target-span')].forEach(span => {
span.textContent = webshop.title;
span.classList.add('show');
});
}
}
document.addEventListener('DOMContentLoaded', changes);
</script>
</head>
<body>
<p>
<span class="first-headline target-span">Text I want to change</span>
</p>
<p>
<span class="first-headline target-span">Text I want to change</span>
</p>
</body>
</html>

Multiple instances of CodeMirror textarea on the same page

I'm trying to make a little project for job interview questions and have a bunch of questions with answers and code examples on the page. Each is inside a .collapsible div from Materialize.css, and when clicked shows the answer and code example.
What is the best way to go about this? I tried putting initializer into a function, grabbing all my textareas form the DOM, looping through them and turning them into CodeMirror textareas.
$(document).ready(function(){
var blocks = document.getElementsByClassName('code-block');
function createEditorFrom(selector) {
let editor = CodeMirror.fromTextArea(selector, {
lineNumbers : false,
mode: "swift",
});
}
for (var x = 0; x < blocks.length; x++) {
createEditorFrom(blocks[x]);
}
// Callback for Collapsible open
$('.collapsible').collapsible({
onOpen: function() {
// call editor.refresh()?
},
});
});
This does work but I feel like it is not a very elegant way of solving this issue. Is there a better way to do this?
Questions:
Is there a better way to create all the CodeMirror textareas?
The code in the editors does not appear until clicked. Nothing I do makes it work. Calling editor.refresh() (with setTimeout), and autorefresh: true.
One thing you will have to do anyhow is keeping a reference to your CodeMirror instances (eg. to get/set their value), so createEditorForm will have to return the CodeMirror instance, & you could for example push them to an array in the loop:
function createEditorFrom(selector) {
return CodeMirror.fromTextArea(selector, {
lineNumbers : false,
mode: "swift",
});
}
let codeblocks = [];
for (var x = 0; x < blocks.length; x++) {
codeblocks.push({
CM: createEditorFrom(blocks[x]),
el: blocks[x]
});
}
I've encountered a similar problem to "The code in the editors does not appear until clicked" and I even wrote a comment next to the solution. Translated to your implementation that would be:
function createEditorFrom(selector) {
var instance = CodeMirror.fromTextArea(selector, {
lineNumbers : false,
mode: "swift",
});
// make sure the CodeMirror unit expands by setting a null character
instance.setValue('\0');
instance.setValue('');
return instance;
}
For what it's worth, I also used some CSS tweaks to ensure consistent initial & max-height rendering:
/* CodeMirror */
.CodeMirror { height: auto; overflow: auto; max-height: 250px; }
.CodeMirror-scroll { height: auto; min-height: 24px; }
Is there a better way to create all the CodeMirror textareas [than by looping through them]?
Yes, you can lazy initialize them if they are not visible on page load (and actually I believe it's better to initialize them only when the parent node of the <textarea> is visible, I'm not sure if CodeMirror requires render-info (like Element.getBoundingClientRect), e.g. only load them onOpen of their collapsible parent.
I ended up initializing each one when the collapsible is opened. This works but I guess it would need to be adjusted if there were multiple CodeMirror textareas in the same collapsible.
I also added in a check becuase otherwise when the collapsible gets opened, then closed, then reopened it would create a duplicate textarea.
$(document).ready(function(){
$('.collapsible').collapsible({
onOpen: createCodeBlock
});
function createCodeBlock(target) {
var card = target.context.parentElement.parentElement;
var textarea = card.getElementsByClassName('code-block')[0];
// Placeholder class that prevents the duplicate creation of
// the same textarea when the collapsible is closed then reopened.
if (!textarea.classList.contains("created")) {
CodeMirror.fromTextArea(textarea, {
lineNumbers: false,
mode: "swift",
});
textarea.className += "created";
}
}
});

How can we stop the execution of javascript for a while until a decision is made?

I want to build my own popup box using div's (not the browser default). So when I display my own popup I want to stop the javascript execution until the user clicks a button on my own popup, same as the default confirmation popup in browsers. Is there a way to do this? If so how? I would like to avoid using jQuery.
You can't (and shouldn't) block JavaScript execution. It would be possible by introducing an endless while loop, but that would seriously degrade performance and also affect the handling of click events.
So, the best and probably only way to do this, is to use a callback that is called when you press a button. This does mean that you can't call this alternative confirm method in a synchronous way, though. Instead you can provide a callback that is executed when one of the buttons is pressed.
I hacked together an example. This is just made up on the fly, and only has some rudimentary styling. If it contains minor flaws, please forgive me.
/**
* The alternative confirmation function
*/
window.myConfirm = function(options) {
// Create the elements
var popup = document.createElement('div');
var box = document.createElement('div');
var ok = document.createElement('button');
var cancel = document.createElement('button');
// Style them
popup.className = 'lightbox';
box.className = 'dialog';
ok.className = 'button buttonOK';
cancel.className = 'button buttonCancel';
// Button texts
ok.innerText = 'OK';
cancel.innerText = 'Cancel';
// Click handlers.
ok.onclick = function(event) {
popup.parentNode.removeChild(popup);
options.onConfirm();
}
cancel.onclick = function(event) {
popup.parentNode.removeChild(popup);
options.onDecline();
};
// Clicking the box does nothing.
box.onclick = function(event){
event.stopPropagation();
};
// Clicking the popup equals cancel.
popup.onclick = cancel.onclick;
// Add all elements to the document.
popup.appendChild(box);
box.innerHTML = "<div><h2>" + options.title + "</h2>" + options.prompt + "</div>";
box.appendChild(ok);
box.appendChild(cancel);
// Finally show the box.
document.body.appendChild(popup);
};
/**
* The call
*/
myConfirm({
title: "Confirm",
prompt: "Are you sure?",
onConfirm: function() {
// The code that is executed when user presses OK.
alert('You confirmed');
},
onDecline: function() {
// Code executed on cancel, or when clicking next to the box.
alert('You declined');
}
});
.lightbox {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
text-align: center;
}
.dialog {
display: inline-block;
margin-top: 50px;
background-color: white;
padding: 50px;
}
.dialog div {
text-align: left;
}
There is no way of stopping the execution, get an input from the user and then continue from some point without using JavaScript popup boxes. You can build one on your own with html components and display it with buttons, provide call backs. You would need to block the window from user access, take the input and then unblock the window. You may use Javascript frameworks like jQuery to get a better styled, enhanced and optimized component. Here is piece of code demonstration:
JavaScript
function myCancelClick() {
hidePrompt();
alert("Cancel");
}
function myOkClick(){
hidePrompt();
alert("Ok");
}
function showPrompt() {
document.getElementById("promptPane").style.display = '';
document.getElementById("displayPane").style.display = 'none';
}
function hidePrompt() {
document.getElementById("promptPane").style.display = 'none';
document.getElementById("displayPane").style.display = '';
}
HTML body
<body>
<div id="displayPane">
<input type="button" onclick="showPrompt()" value="Show Prompt"/>
</div>
<div id="promptPane" style="display: none;">
<div id="myPrompt">what would you like to click?<input type="button" onclick='myCancelClick();' value="Cancel"/><input type="button" onclick='myOkClick();' value="Ok"/>
</div>
</div>
</body>
Fiddle
If you know, how much time you want to stop for; try using setTimeout() function of Javascript to produce a delay

jQuery Custom events for data attribute

I'm building a custom widget system for an app I'm working on. What I want to have happen is the ability for one widget to update another widget by changing out the data attributes value. On page load, the initial data is loaded into this data-attr via PHP and using jQuery to switch out the data after the fact.
For instance, one widget would work as follows:
PHP loads json data into DOM element
jQuery function is passed the elements ID and retrieves data from data-attr and uses it to produce a graph for example
Based on user interaction, another widget sends data to element's data-attr while also firing a custom jQuery event
Initial function gets the new data and updates it's graph
I've started a demo:
// Ranomize Number & Replace Variable
$(function() {
$('#random').click(function(e) {
e.preventDefault();
num = Math.random() + 100;
$('#data').attr('data-receiver', num);
});
});
// Receive Data & Output
$(function() {
var output = $('#output');
var received = $('#data').attr('data-receiver');
output.html(received);
// Not sure what to do next
});
#content {
background: #efefef;
margin: 40px auto;
display: block;
padding: 50px;
width: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="content">
<div id="data" data-receiver="10"></div>
<strong>Output:</strong>
<span id="output"></span>
<br/>
<br/>
Randomize
</div>
But to be honest I'm not sure how to start. I have some code putting a random value into the receiving DOM element, but not sure how to setup the event or write another function to receive and update the #output div.
I'm happy to answer questions or write more code to help better explain my goal. Thanks in advance.
Try utilizing .queue() , .promise() to create a "subscriber" , "publisher" pattern
var output = $("#output");
var process = function process(next) {
// `this`:`#data`
var num = Math.random() * 100;
$(this).data("receiver", num);
return next()
};
var update = function update() {
// `this`:`#data`
$(this).promise("process").then(function(received) {
// `received`:`#data`,
// do stuff with `received`
console.log(received.data("receiver"));
output.html(received.data("receiver"));
received.queue("process", process);
// add `process` to `received` `"process"` queue
console.log(received, received.queue("process"));
});
};
// queue first call to `process`
$("#data").queue("process", process);
$("#random").click(function (e) {
e.preventDefault();
update.call($("#data").dequeue("process"));
});
jsfiddle http://jsfiddle.net/jev4wuej/2/
I prefer to use custom events which allows for the code to be more decoupe and independent of each other.
jsfiddle
JS
var toolbar = {
init: function() {
$('.data-randomizer').click(this.handleRandomizer);
},
handleRandomizer: function() {
var number = Math.random() + 100;
$.event.trigger('update-request.storage-widget', [number]);
}
};
var output = {
init: function() {
$(document).on('updated.storage-widget', this.handleDisplay);
$.event.trigger('data-request.storage-widget', this.handleDisplay);
},
handleDisplay: function(event, number) {
$('.data-output-widget #output').text(number);
},
requestOut: function() {
}
};
var storage = {
init: function() {
$(document).on('update-request.storage-widget', this.handleUpdateRequest);
$(document).on('data-request.storage-widget', this.handleDataRequest);
},
handleUpdateRequest: function(event, number) {
$('.data-storage-widget').attr('data-receiver', number);
$.event.trigger('updated.storage-widget', [number]);
},
handleDataRequest: function(event, callback) {
var number = $('.data-storage-widget').data('receiver');
callback(event, number);
}
};
toolbar.init();
storage.init();
output.init();
HTML
<div id="content">
<div class="data-storage-widget" data-receiver="10"></div>
<div class="data-output-widget">
<strong>Output:</strong>
<span id="output"></span>
</div>
<div class="tool-bar-widget">
Randomize
</div>
</div>

Javascript/HTML: How to make a small UI pop out on select

I am making a chrome extension that interacts with the current page when the user selects some text.
What we want it to do is to make a small window pop up to let the user select within different options.
Something along the lines of this:
http://img-ipad.lisisoft.com/img/1/5/1526-1-pdf-highlighter.jpg
What we have so far is the following:
document.addEventListener('mouseup',boxOption)
function boxOption(){
var yourSelection = window.getSelection();
if (yourSelection!=""){
/* insert popup here */
}
}
Any help would be greatly appreciated
You can implement the popup by inserting an element into the DOM (or revealing an existing element within the DOM) which simply has a "z-index" property that puts it above the other elements. Ex:
// Create a class that encapsulates the menu element
// This particular implementation constructs a new element
// and adds it to the DOM, but you could instead take the
// element as a parameter or have it retrieve an existing element
var PopupMenu = function() {
this.element = document.createElement('div');
this.element.className = 'popup-menu';
document.body.appendChild(this.element);
// ...
// set up event listeners for this element
// ...
};
// The menu is hidden unless it also has the 'enabled' class
PopupMenu.prototype.setVisible = function(isVisible) {
if (isVisible) {
this.element.classList.add('enabled');
} else {
this.elemnt.classlist.remove('enabled');
}
};
And then in your CSS, you could do:
.popup-menu {
display: none;
}
.popup-menu.enabled {
display: block;
/* this just needs to be larger than the z-index of the items it covers */
z-index: 100;
}
I'll leave the rest of the styling/handlers of the menu up to you.

Categories

Resources