How to fix navbar overlap on dropdown items with a vuepress site - vue.js

There's always an overlap with Navbar dropdown when more than one is clicked. It focuses and takes a few minutes to clear this becomes a problem because it causes clutter.
The configuration for this in the Vuepress docs is just to add navbar items and ariaLabel any know how I can stop this behaviour.
themeConfig: {
nav: [
{
text: 'Languages',
ariaLabel: 'Language Menu',
items: [
{ text: 'Chinese', link: '/language/chinese/' },
{ text: 'Japanese', link: '/language/japanese/' }
]
}
]
}
Here's an example

To answer your question one would need to address two distinct issues:
how do I run custom JavaSCript in VuePress?
how do I close any previously open dropdowns on click in my current VuePress theme, using JavaScript?
For the first problem there are several solutions (one of them being by using a custom component with code run in its mounted() hook, but this would require you to include that component in every page and make sure it doesn't run more than one time (since you want to bind events to elements).
I believe the cleanest way would be by adding a <script> to <head> which can be achieved by adding this to the head prop of your .vuepress/config.js export:
head: [
// ...existing stuff, if any,
['script', {}, `
(function() {
// your code here...
})();
`]
]
However, there are a few problems with the above solution. Firstly, it's going to be run as soon as it's parsed, and that's inside the <head> tag. Which means none of the contents of your page are rendered yet. And the second problem is you're in a template literal. You don't really want to be writing JavaScript code in a template literal. Ideally you should be able to put your code in a '.js' file and append it as a <script> tag.
In order to do that, you need to create a .vuepress/public/ folder, if you don't already have one. Place your .js file in there (I used test.js but feel free to name it as you like). Modify the above code to:
['script', {}, `
(function() {
var s = document.createElement('script');
s.src = './test.js';
var h = document.querySelector('head');
h.appendChild(s);
})();
`]
Change ./test.js to your file's name.
Now your file has clean JavaScript and the door is open. Your code executes in the window object context.
To answer the second part of your question, well..., it largely depends on the theme you are using. If you're using the default theme (which seems to be the case, from the SS you posted), this should work, if placed inside your .js file:
document.addEventListener('DOMContentLoaded', fixDropDowns);
function fixDropDowns() {
document.body.addEventListener('click', (ev) => {
const header = document.querySelector('header');
if (header) {
const dds = header.querySelectorAll('.dropdown-wrapper');
[...dds].forEach(el => el.classList.remove('open'));
const curr = ev.target.closest('.dropdown-wrapper');
if (curr) {
curr.classList.add('open');
}
}
})
}
But it's based on a close inspection of the generated markup.
Specifically on the fact the dropdowns have a class of .dropdown-wrapper and that they're opened by toggling class open on them. The above is just an example and will likely not work on other themes and might even stop working on the default theme in some future version.

Related

Docusaurus: How can I have multiple versions of different docs in the docs directory?

I'm working with Docusaurus to create a documentation site for 3 different education courses - all within the docs folder.
So I'm looking for a way to have the version be different across folders in there, or figure out what the best strategy for this is.
Right now, in my docusaurus.config.js I have:
module.exports = {
presets: [
'#docusaurus/preset-classic',
docs: {
lastVersion: 'current',
versions: {
current: {
label: '1.0.0',
path: '1.0.0',
},
},
},
],
};
But I'm not sure how to keep track of 3 different versions across 3 different docs all within the same site.
Swizzle the navbar via wrapping
yarn run swizzle #docusaurus/theme-classic NavbarItem/DocsVersionDropdownNavbarItem -- --wrap
Modify the swizzled component like so:
src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js:
import React from "react";
import DocsVersionDropdownNavbarItem from '#theme-original/NavbarItem/DocsVersionDropdownNavbarItem';
import { useLocation } from '#docusaurus/router';
export default function DocsVersionDropdownNavbarItemWrapper(props) {
const { docsPluginId, className, type } = props
const { pathname } = useLocation()
/* (Custom) check if docsPluginId contains pathname
Given that the docsPluginId is 'charge-controller' and the routeBasePath is 'charge-controller', we can check against the current URI (pathname).
If the pathname contains the docsPluginId, we want to show the version dropdown. Otherwise, we don't want to show it.
This gives us one, global, context-aware version dropdown that works with multi-instance setups.
You want to declare a version dropdown for each plugin in your navbarItems config property for this to work well.
const doesPathnameContainDocsPluginId = pathname.includes(docsPluginId)
if (!doesPathnameContainDocsPluginId) {
return null
}
return <DocsVersionDropdownNavbarItem {...props} />;
}
For this to work, you need to have your documentation (based on products) split up using multi-instances: (https://docusaurus.io/docs/docs-multi-instance#docs-navbar-items)
Note that the preset docsPlugin ID always is "default".
You can try to use
import {
useActivePluginAndVersion,
} from '#docusaurus/plugin-content-docs/client';
const version = activePluginAndVersion.activeVersion.name; // use label instead of name if issues arise.
instead to get the current docsPluginId, name or label.
This would be the more "robust" solution I think. That said, we do use the solution I provided above as-is and it works fine for now.

Can't get full screen buttons to work with highcharts-vue

I'm using the highcharts-vue wrapper within my vue project. Things are going smooth except for the fact that I can't get the viewFullscreen option to work.
I've set things up following the docs and included the following within my main.js
import exportingInit from 'highcharts/modules/exporting'`
exportingInit(Highcharts);
The export function is working within my charts. For every chart I've set up the options as follow:
exporting: {
buttons: {
contextButton: {
menuItems: ['viewFullScreen', 'downloadPNG', 'downloadJPEG', 'downloadPDF']
}
}
}
alle the buttons ar visible and working except for the viewFullscreen button. that one isn't showing.
Following the docs from the highcharts api there isn't anything mentioned about having to include extra options or so to make use of the vieFullscreen mode.
Any thoughts on this?
You need to change the first string from viewFullScreen to viewFullscreen:
exporting: {
buttons: {
contextButton: {
menuItems: ['viewFullscreen', 'downloadPNG', 'downloadJPEG', 'downloadPDF']
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/ekr2mw7f/
API Reference: https://api.highcharts.com/highcharts/exporting.buttons.contextButton.menuItems

How to structure a complex web app with RequireJS

I saw there is somes questions related to mine (like this interesting one), but what I wonders is how to do it correctly, and I couldn't find it via the others questions or the RequireJS documentation.
I'm working on a quite heavy web application that will run in only one html page.
Before RequireJS, I used to do a lot of JS modules with public methods and connecting them via the on event on the Dom READY method, like this :
var DataList = function () {
this.base = arguments[0];
this.onUpdate = function (event) { ... }
}
$(function () {
var dataList = {}; DataList.apply(dataList, [$('#content')]);
$('table.main', dataList.base).on ('update', dataList.onUpdate);
});
With RequireJS, I can easily see that I can split DataList and all others classes like this on individual files, but what about the $(function () {}); part?
Can I still keep it this way, but instead of the DOM ready function of jQuery, I put the events on the main function() of the RequireJS, when my primary libs are loaded?
Or do I have to change the way I create JS "classes", to include a init function maybe, that will be called when I do a, for example :
require(['Datalist'], function(dataList) {
dataList.init($('#content'));
});
What annoys me the most is that since I have only one html file, I'm afraid the require() will have to load a huge list of files, I'd prefer it to load just libs that, them, would load sub libs required to work.
I don't know, the way of thinking with RequireJS lost me a bit :/
How would you do?
"Can I still keep it this way, but instead of the DOM ready function of jQuery, I put the events on the main function() of the RequireJS, when my primary libs are loaded?"
If you separate the functions or 'classes' into modules then you can use the RequireJS domReady function:
require(['module1'], function(module1) {
domReady(function(){
// Some code here ftw
})
});
The benefit here is the domReady function will allow downloading of the modules instantly but won't execute them until your DOM is ready to go.
"Or do I have to change the way I create JS "classes", to include a init function maybe, that will be called when I do a, for example"
You won't need to change the way you interact with your code this way, but you can probably improve it. In your example I would make DataList a module:
define(function(require) {
var $ = require('jquery');
var DataList = function () {
this.base = arguments[0];
};
DataList.prototype.onUpdate = function() {
};
return DataList;
});
require(['data-list'], function(DataList) {
var data = {};
// Call DataList with new and you won't need to set the context with apply
// otherwise it can be used exactly as your example
new DataList(data);
});
"What annoys me the most is that since I have only one html file, I'm afraid the require() will have to load a huge list of files, I'd prefer it to load just libs that, them, would load sub libs required to work."
Make your code as modular as you want/can and then use the optimiser to package it into one JS file.

How to programatically add and use elements (dialog box in this case)

So My first though was, that adding more, and more HTML elements is not a way to go, and I come up with this solution
var Jaxi = {
CurrentLocation: '/',
showLoginDialog: function () {
dojo.place('<div data-dojo-type="dijit.Dialog" style="width:600px;" id="loginDialog"><div id="dialog-content"></div><a href="javascript:Jaxi.CloseDialog()">Close</div>', dojo.body())
dojo.xhrGet({
url: "/Account/SingIn?ReturnUrl=" + Jaxi.CurrentLocation,
load: function (result) {
dojo.byId("dialog-content").innerHTML = result;
}
});
dojo.ready(function () {
dijit.byId("loginDialog").show();
});
},
CloseDialog: function () {
dijit.byId("loginDialog").hide();
dojo.destroy("loginDialog");
}
};
It's working.. To some degree at least. Dialog open, but no styles are appiled. But moreover I can't close dialog.
Question Is how to make it working ?
After you have placed the div in your body, Dojo needs to parse the HTML to "notice" the new widget. When it notices the data-dojo-type attribute it says "Hey, here's a widget, I need to make this into a beautiful Dialog".
showLoginDialog: function () {
dojo.place('<div data-dojo-type="dijit.Dialog" ....</div>', dojo.body());
dojo.parser.parse();
....
Of course, you also have to make sure your body tag has class="claro" (or any other theme you want to use).
That being said, I personally think this is a little messy way to make a dialog box. You are sort of mixing declarative with programmatic. I'm not sure what you mean by "My first though was, that adding more, and more HTML elements is not a way to go", but in my own opinion mixing HTML inside your javascript makes the code difficult to read. You may want to take a look at this sitepen article if you want a clean way to separate HTML and Javascript.

Using dijit.InlineEditBox with dijit.form.Select

I'm trying to use a dijit.form.Select as the editor for my dijit.InlineEditBox. Two problems / unexpected behavior seem to occur:
Inconsistently, the InLineEditBox doesn't have the initial value set as selected
Consistently, after selecting a choice, the value that should be hidden is shown instead of the label.
The width isn't set to 130px
Here's working code: http://jsfiddle.net/mimercha/Vuet8/7/
The jist
<span dojoType="dijit.InlineEditBox" editor="dijit.form.Select"
editorParams="{
options: [
{label:'None',value:'none'},
{label:'Student',value:'stu'},
{label:'Professor',value:'prof',selected:true},
],
style:'width:1000px;',
}"
editorStyle="width: 1000px;"
>
</span>
Any help is greatly appreciated! Thanks!
Okay, after a few MORE hours struggling with the mess that is dijit.InlineEditBox, I think I have the solution to the remaining issue (#2).
EDIT: My first solution to #2 is still flawed; the implementation at http://jsfiddle.net/kfranqueiro/Vuet8/10/ will never return the actual internal value when get('value') is called.
EDIT #2: I've revamped the solution so that value still retains the real (hidden) value, keeping displayedValue separate. See if this works better:
http://jsfiddle.net/kfranqueiro/Vuet8/13/
First, to recap for those who weren't on IRC:
Issue #1 was happening due to value not being properly set as a top-level property of the InlineEditBox itself; it didn't pick it up properly from the wrapped widget.
Issue #3 was happening due to some pretty crazy logic that InlineEditBox executes to try to resolve styles. Turns out though that InlineEditBox makes setting width particularly easy by also exposing it as a top-level numeric attribute. (Though IINM you can also specify a percentage as a string e.g. "50%")
Now, issue #2...that was the killer. The problem is, while InlineEditBox seems to have some logic to account for widgets that have a displayedValue attribute, that logic is sometimes wrong (it expects a displayedValue property to actually exist on the widget, which isn't necessarily the case), and other times missing entirely (when the InlineEditBox initializes). I've worked around those as best I could in my own dojo.declared extensions to InlineEditBox and the internal widget it uses, _InlineEditor - since generally it's a good idea to leave the original distribution untouched.
It's not pretty (neither is the underlying code I dug through to understand and come up with this), but it seems to be doing its job.
But man, this was rather interesting. And potentially pertinent to my interests as well, as we have used this widget in our UIs as well, and will be using it more in the future.
Let me know if anything backfires.
hm...
<span dojoType="dijit.InlineEditBox" editor="dijit.form.Select"
editorParams="{
options: [
{label:'None',value:'none'},
{label:'Student',value:'stu'},
{label:'Professor',value:'prof',selected:true},**<<<<** and this comma is for?
],
style:'width:1000px;',**<<<<** and this comma is for?
}"
editorStyle="width: 1000px;"
>
</span>
Also, when using dijit.form.Select, selected value is not attr "selected" but value.
And if you enter prof inside <span ...blah > prof </span> than your proper selected option will be selected ;)
Dijit select checks for VALUE, not attr.
This may be fixed in recent Dojo - see http://bugs.dojotoolkit.org/ticket/15141 - but using 1.7.3 I found this worked:
In my app directory, at the same level as dojo, dijit and dojox, I created a file InlineSelectBox.js which extends InlineEditBox with code to set the HTML on the associated domNode from the value of the Dijit, and which wires up that code to the onChange() event:
define(["dijit/InlineEditBox",
"dijit/form/Select",
"dojo/on",
"dojo/_base/declare",
"dojo/_base/array"
],
function(InlineEditBox, Select, on, declare, array){
return declare(InlineEditBox, {
_setLabel: function() {
array.some(this.editorParams.options, function(option, i){
if (option.value == this.value) {
this.domNode.innerHTML = option.label;
return true;
}
return false;
}, this);
},
postMixInProperties: function(){
this.inherited(arguments);
this.connect(this, "onChange", "_setLabel");
},
postCreate: function(){
this.inherited(arguments);
this._setLabel();
}
});
});
Then, in my view script:
require(["dojo/ready",
"app/InlineSelectBox",
"dijit/form/Select"
],
function(ready, InlineSelectBox, Select){
ready(function(){
// Add code to set the options array
var options = [];
// Add code to set the initial value
var initialValue = '';
var inlineSelect = new InlineSelectBox({
editor: Select,
editorParams: {options: options},
autoSave: true,
value: initialValue
}, "domNodeToAttachTo");
});
});
I was dealing with this situation a few months ago, and not finding a resolution i made my own algorithm.
I put a div with an event on Onclick that build programatically a Filtering Select on that div with the store i want to use.
function create(id,value){
var name = dojo.byId(id).innerHTML;
dojo.byId(id).parentNode.innerHTML = '<div id="select"></div>';
new dijit.form.FilteringSelect({
store: store,
autoComplete: true,
invalidMessage:"Invalid Selection",
style: "width: 80px;",
onBlur: function(){ },
onChange: function(){ },
required: true,
value: value,
disabled: false,
searchAttr: "name",
id: "status"+id,
name: "status"
},"select");
dijit.byId('status'+id).focus();
}
I used the onBlur event to destroy the widget and the onchange to save by xhr the new value.
The focus is below because the onBlur was not working properly.
note: the function is not complete.