/    Sign up×
Community /Pin to ProfileBookmark

Very random popup menu

I cannot get this to work properly. Sometimes it works. Sometimes it fails. The menu is created and ready. But refuse to show up (randomly) in tippy container.
[url=https://postimages.org/][img]https://i.postimg.cc/RZx0GJXg/tippy.png[/img][/url]

Sometimes i get it to work by delaying by an alert:
[url=https://postimages.org/][img]https://i.postimg.cc/vHvGb2Qd/alert.png[/img][/url]

What am I doing wrong? https://task.go4webdev.org/tasks

to post a comment
JavaScript

32 Comments(s)

Copy linkTweet thisAlerts:
@SempervivumMay 25.2022 — You are filling the templates for the menus "more" and "status" by ajax. As this is working asynchronously it's not guaranteed that the HTML is ready when you initialize tippy.

Fix it by initializing tippy in the callback of the XMLHttpRequest.
Copy linkTweet thisAlerts:
@sibertauthorMay 26.2022 — > @Sempervivum#1644207 Fix it by initializing tippy in the callback of the XMLHttpRequest.

I do not understand. Tippy code is a part of the AJAX call. So in my world it is already initialized. Or?

``<i>
</i>function api3get() {
var url = "https://api3.go4webdev.org/" + mod + '/all';
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.readyState === 4) {
filltable(this.response);
tippy('td.status', {
theme: 'custom',
trigger: 'click',
content: document.querySelector('.tippystatus').innerHTML,
allowHTML: true,
arrow: false,
appendTo: 'parent',
interactive: true
});
tippy('td.more', {
theme: 'custom',
trigger: 'click',
content: document.querySelector('.tippymore').innerHTML,
allowHTML: true,
arrow: false,
appendTo: 'parent',
interactive: true
});
}
};
xhr.send();
}<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumMay 26.2022 — @sibert#1644217

That's true, however the templates for the menus are populated by ajax either:
``<i>
</i>getstatus();

function getstatus() {
fetch('https://api3.go4webdev.org/status/all')
.then((response) =&gt; response.json())
.then(fillpopstatus)
.catch(error =&gt; console.log(error))
}<i>
</i>
`</CODE>
This request has to be ready before initializing tippy. I confirmed this by use of the debugger: When stopping at this line:<br/>
<C>
tippy('td.status', {</C><br/>
and viewing what<br/>
<C>
document.querySelector('.tippymore').innerHTML`

is returning, the ul is empty.
Copy linkTweet thisAlerts:
@sibertauthorMay 26.2022 — > @Sempervivum#1644221 This request has to be ready before initializing tippy.

I can confirm that filling the **popup menu is done** before **init tippy done** (by alerts in js).

How do I ensure that this is completely ready?
Copy linkTweet thisAlerts:
@SempervivumMay 26.2022 — @sibert#1644222

Initialize tippy in the `then</C> callback of the fetch request:
<CODE>
`<i>
</i>getstatus();

function getstatus() {
fetch('https://api3.go4webdev.org/status/all')
.then((response) =&gt; response.json())
.then(data =&gt; {
fillpopstatus();
// Template for the popup menu is ready now,
// init tippy here
})
.catch(error =&gt; console.log(error))
}<i>
</i>
``
Copy linkTweet thisAlerts:
@sibertauthorMay 26.2022 — > @Sempervivum#1644223 Initialize tippy in the then callback of the fetch request:

I did not get this to work, but something similar works:

``<i>
</i>getmore();

function getmore() {
fetch('https://api3.go4webdev.org/action/all')
.then((response) =&gt; response.json())
.then(fillpopmore)
.catch(error =&gt; console.log(error))
}

let popmore = document.querySelector(".popmore");
let fragment2 = new DocumentFragment()

function fillpopmore(data){
data.forEach(function(element) {
var start =
<svg class="std"> <use xlink:href="#
var end =
"/></svg>
var li = document.createElement('li')
li.setAttribute('data-action',element.action_id)
li.innerHTML = (start + element.action_id+ end + element.action_en)
fragment.appendChild(li)
})
popmore.appendChild(fragment)
}<i>
</i>
`</CODE>
and the init of tippy:

<CODE>
`<i>
</i>api3get();

function api3get() {
let url = "https://api3.go4webdev.org/" + mod + '/all';
fetch(url)
.then((response) =&gt; response.json())
.then(filltable)
.then(tippy)
.catch(error =&gt; console.log(error))
}

function tippy() {
tippy('td.status', {
theme: 'custom',
trigger: 'click',
content: document.querySelector('.tippystatus').innerHTML,
allowHTML: true,
arrow: false,
appendTo: 'parent',
interactive: true
});
tippy('td.more', {
theme: 'custom',
trigger: 'click',
content: document.querySelector('.tippymore').innerHTML,
allowHTML: true,
arrow: false,
appendTo: 'parent',
interactive: true
});
}<i>

</i>
``

BUT I have to put a **delay alert** in order to get this to work. WHY?

https://task.go4webdev.org/tasks
Copy linkTweet thisAlerts:
@SempervivumMay 26.2022 — > Unfortunately I cannot find this alert. Give me a hint, which file?

popup.js. At the end.

But it may be cached and old version. I have purged and set to developer mode on Cloudflare, so it should show up.
Copy linkTweet thisAlerts:
@sibertauthorMay 26.2022 — > @Sempervivum#1644225 Unfortunately I cannot find this alert. Give me a hint, which file?

popup.js. At the end.

But it may be cached and old version. I have purged and set to developer mode on Cloudflare, so it should show up.
Copy linkTweet thisAlerts:
@SempervivumMay 26.2022 — The lists for more and status are populated by ajax, this function:
``<i>
</i>function getstatus() {
fetch('https://api3.go4webdev.org/status/all')
.then((response) =&gt; response.json())
.then(fillpopstatus)
.catch(error =&gt; console.log(error))
}<i>
</i>
`</CODE>
Ajax works asynchronously, this means, the request is sent to the server, then, <STRONG>**after a short delay**</STRONG>, the response from the sever arrives. Obviously, when you initialize tippy, this has not yet happened and the <C>
ul</C>s are still empty.<br/>
When you add the alert, it takes a fairly long time until the user reacts and during that time the response has arrived and the <C>
ul`s are populated when initializing tippy.
Copy linkTweet thisAlerts:
@SempervivumMay 26.2022 — PS: I assumed that tippy is using event bubbling for the click listener but if my proposal, initializing it after population the `ul`s, doesn't work, this seems not to be true. If so you will have to wait for both events, populating the table and population the ul. In this thread I posted some code demonstrating how to do this:

https://forum.webdeveloper.com/d/399776-multiple-functions-have-conditions-met/20
Copy linkTweet thisAlerts:
@sibertauthorMay 27.2022 — > @Sempervivum#1644229 If so you will have to wait for both events

I have added await to the popup menu building process. And it seems to work almost every time. But not reliable.

Do I have to add await also for the table creation? Will this not slow down the page?

https://task.go4webdev.org/tasks

``<i>
</i>var getstatus = async function () {
var response = await fetch('https://api3.go4webdev.org/status/all');
var json = await response.json();
fillpopstatus(json)
};

let popstatus = document.querySelector(".popstatus");
let fragment = new DocumentFragment()

function fillpopstatus(data) {
data.forEach(function(element) {
let start =
<svg class="std hide"><use xlink:href="#status
let end =
"/></svg>
var li = document.createElement('li')
li.setAttribute("data-action", element.status_id)
li.innerHTML = (start + element.status_id + end + element.status_en)
fragment.appendChild(li)
})
popstatus.appendChild(fragment)
}

getstatus();<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumMay 27.2022 — @sibert#1644248
>And it seems to work **almost** every time.

Apparently there is no improvement.

Try to fetch all of them in one await function like this:
``<i>
</i>var getAll = async function () {
var responseMore = await fetch('https://api3.go4webdev.org/status/more');
var dataMore = await responseMore.json();
var responseStatus = await fetch('https://api3.go4webdev.org/status/all');
var dataStatus = await responseStatus.json();
var responseTable = await fetch('url-for-table');
var dataTable = await responseTable.json();
// now the data from all responses is ready, we can go ahead
// and create the elements:
fillpopstatus(dataStatus);
fillpopstatus(dataMore);
filltable();
tippy();
};<i>
</i>
``

(just a rough draft and not tested)
Copy linkTweet thisAlerts:
@sibertauthorMay 27.2022 — > @Sempervivum#1644249 Try to fetch all of them in one await function like this:

This is of course an option. But in my case I will need to create 50-100 different popup menus. Which seems to be overwhelmingly big function to me. I guess I have to split it in some way. Any idea? One specific getAll() for each and every window?

> @Sempervivum#1644249 Apparently there is no improvement.

There is an improvement when you redraw the window. But it is very random.
Copy linkTweet thisAlerts:
@SempervivumMay 27.2022 — @sibert#1644250

I see, but even when populating all of these menus without async await there will be a bunch of fetch requests.

One might consider to use an array like this:
``<i>
</i>const menuData = [
{url: 'https://api3.go4webdev.org/status/more', fct: fillmore},
{url: 'https://api3.go4webdev.org/status/all', fct: fillall},
// and so on
]<i>
</i>
``

Then loop through, fetch and process the data.
Copy linkTweet thisAlerts:
@sibertauthorMay 27.2022 — > @Sempervivum#1644251 One might consider to use an array like this:

Smart. Thank you!

But every window have to have their own "array", I guess? In order to avoid loading not needed menus?

Will this be smartest to create an array in a script-tag specific to each window? Or is there another way?
Copy linkTweet thisAlerts:
@SempervivumMay 27.2022 — Unfortunately I do not understand what you mean by "window" in this context. An individual HTML/PHP file while having a global JS file for all of them?
Copy linkTweet thisAlerts:
@sibertauthorMay 27.2022 — > @Sempervivum#1644253 Unfortunately I do not understand what you mean by "window" in this context.


``<i>
</i>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;script src="/js/for_this_window.js" defer&gt;&lt;/script&gt; &lt;---- corresponding array for this window
&lt;/head&gt;<i>
</i>
``


> An individual HTML/PHP file while having a global JS file for all of them?

Javascript the most unreadable language I ever have seen. So a global means to me almost unmanageable.

But I will consider storing corresponding array in a Redis Database and fetched into respectively window.

Possible?
Copy linkTweet thisAlerts:
@SempervivumMay 27.2022 — So sorry, still don't understand what you mean when writing "window". `window</C> in terms of javascript is, say, the root object of all variables. Any variable can be accessed by <C>window.variableName`.
Copy linkTweet thisAlerts:
@sibertauthorMay 27.2022 — > @Sempervivum#1644260 window

html window or page :-) Different applications / language uses different terminology. My main desktop application uses "window instance", hence the confusion. But a browser window can contain several pages. And each page can use its own local javascript target to this page only.
Copy linkTweet thisAlerts:
@SempervivumMay 27.2022 — So in HTML or PHP file 1 you will include a js file specific for that file or window containing the array and a call for the function that creates the table and menus.
``<i>
</i>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;script src="/js/menu.js"&gt;
&lt;script src="/js/for_this_window.js" defer&gt;&lt;/script&gt;
&lt;/head&gt;<i>
</i>
`</CODE>

for_this_window.js:
<CODE>
`<i>
</i>const menuData = [
{url: 'https://api3.go4webdev.org/status/more', fct: fillmore},
{url: 'https://api3.go4webdev.org/status/all', fct: fillall},
// and so on
];
createAll(menuData);<i>
</i>
``

Correct?
Copy linkTweet thisAlerts:
@vclupvclupshopMay 27.2022 — thats good
Copy linkTweet thisAlerts:
@sibertauthorMay 28.2022 — > @Sempervivum#1644263 So in HTML or PHP file 1 you will include a js file specific for that file or window containing the array and a call for the function that creates the table and menus.

Yes. Correct. Is this a way to go?

Or could storing the arrays in a database be easier to maintain?

Like array = fetch(arrays for page X) ? This is the way I store the SQL queries. Which is very easy to maintain at least for me as a db guy. To find a code for a particular page in an endless VS Code editor makes me search for options.
Copy linkTweet thisAlerts:
@SempervivumMay 28.2022 — Yes, IMO this is a good way to go and is used widely.

Storing the array in the database would be an overkill of complexity. Additionally this would not be possible when the array contains references to functions.

Anyway I wonder why you are using ajax that widely. Wouldn't it be sufficient to insert the menus into the HTML on the server? The main benefit of ajax is, that you can load content without reloading the page. And it's necessary only if info is needed that is available on the client, e. g. from user input.
Copy linkTweet thisAlerts:
@sibertauthorMay 28.2022 — > @Sempervivum#1644271 Wouldn't it be sufficient to insert the menus into the HTML on the server?

I have an old desktop application with about 100 SQL tables and status options for most of them. The simplest menu could be only **active** or **inactive**. But for some other tables there will be status menus using maybe up to 10 different status. And upon this there will be status menus that are dynamic depending on the data. Today I created these menus on-the-fly by using an internal script. This has been working for a couple of decades and is rather simple to maintain.

Porting this flexible thinking to the web may really look like overkilling for a couple of tables, but I am planning for the future. And I cannot see any future with hard coded fixed menus, because 100 menus with several on-the-fly options will not be maintainable IMO the "standard JS/HTML way". And to add an extra dimension the menus will be in several languages. 😆

Working with databases is a wonder of simplicity compared to hard coded JS/HTML. 100 Javascript files? In my imagination this will be a nightmare. Grouping them, may reduce the number, but also reduce maintainability. As a web newbie I am exploring ways to achieve my old flexibility but not adding complexity. Hence the AJAX popup menus.

I am using Postgresql today to store the menus, but considering Redis as a lookup database to reduce the HTML and JS code. I have no experience of using Redis, but from what I have read, it should be more or less memory based JSON storage. In my dreams this should reduce the JS code further as I think (still in my dreams) I do not need the JSON converting

var dataMore = await responseMore.json();

Am I thinking in the right direction?
Copy linkTweet thisAlerts:
@SempervivumMay 28.2022 — @sibert#1644272

Thanks for this detailed info about the background of your project.

I didn't intend to recommend to keep the HTML of the menus statically but instead create it from the database on the server instead of fetching it by ajax.

Another option is leaving the server side script as is but inserting the data directly into javascript via json.

On the client side in javascript when PHP is used on the server:
``<i>
</i>const data = &lt;_?php echo json_encode($array_created_from_database); ?_&gt;<i>
</i>
``

Now the data is available in the javascript variable. This will work synchronously, no need for callbacks or async await.
Copy linkTweet thisAlerts:
@sibertauthorMay 28.2022 — > @Sempervivum#1644274 the menus statically but instead create it from the database on the server instead of fetching it by ajax.

This is my lack of web knowledge. There is so many ways to skin a cat, that I am confused which way to choose...

But i have tried to fetch the data in Go (server side rendering), but I found it a bit clumsy. Because the page will not render until all data is fetched on the server. AXAJ gives a smoother redraw with larger tables.

So when the table was populated by AJAX, just being lazy I found it conveniently to continue with AXAJ for the menus as well. But I have considered to create the menus on the server side and using AXAJ for the table.

> Another option is leaving the server side script as is but inserting the data directly into javascript via json.

> On the client side in javascript when PHP is used on the server:


I am using Go instead of PHP, but this may be an option. I can embed JS in a Go HTML template in that way.

I will first try your way to skin the cat as a first step. Thank you for your tips!
Copy linkTweet thisAlerts:
@sibertauthorMay 30.2022 — > @Sempervivum#1644263 for_this_window.js:

I have tried to implement your suggestion, and it may work. But there is a random order of execution. Sometimes the table is filled before the popup menus.

  • 1. How can I assure it is executed in correct order?

  • 2. Or does it matter which order they are executed?


  • https://jsfiddle.net/zru6oskf/15/
    Copy linkTweet thisAlerts:
    @SempervivumMay 30.2022 — The reason for the random order is that you are processing only one request inside the async function build_menus. Thus the requests are processed independently and the order of the responses from the server is random.

    I was able to force the order by processing all requests in one async function:
    ``<i>
    </i> async function build_menus() {
    var menus = JSON.parse(json);
    for (let i = 0; i &lt; menus.length; i++) {
    const item = menus[i];
    let response = await fetch(item.value);
    let json = await response.json();
    let ok = window[item.key](json);
    }
    }
    build_menus();<i>

    </i>
    ``
    Copy linkTweet thisAlerts:
    @sibertauthorJun 01.2022 — > @Sempervivum#1644309 I was able to force the order by processing all requests in one async function:

    Thank you indeed! It worked as expected. But there is a slight delay in the popup menu. Is there any way to avoid this?

    The code behind the page:

    ``<i>
    </i>var json =

    [{
    "key": "fillstatus",
    "value": "https://api3.go4webdev.org/status/all"
    }, {
    "key": "fillaction",
    "value": "https://api3.go4webdev.org/action/all"
    }, {
    "key": "filltable",
    "value": "https://api3.go4webdev.org/tsk/all"
    }]


    async function build_menus() {
    var menus = JSON.parse(json);
    for (let item of menus) {
    let response = await fetch(item.value);
    let json = await response.json();
    window[item.key](json);
    }
    }

    build_menus();


    /* ---------------------- */

    var table = document.querySelector('table');
    function filltable(data) {
    for (let item of data) {
    row = table.insertRow();
    row.setAttribute("data-id", item.tsk_id)

    r1 = row.insertCell(0);
    r1.setAttribute("data-key", "tsk_subject");
    r1.innerHTML = item.tsk_subject

    row.insertCell(1).className = "status"
    row.insertCell(2).className = "more"
    }
    tippy2()
    }<i>
    </i>
    ``

    Live: https://task.go4webdev.org/tsk
    Copy linkTweet thisAlerts:
    @SempervivumJun 03.2022 — Sorry for replying late, this thread lost focus on my screen.

    >there is a slight delay in the popup menu

  • 1. When testing the tooltips I notice that they pop up when I release the mouse. Maybe this is causing the illusion of a delay.

  • 2. There is a short animation (fade in) for the popup. The duration is 275ms for appearing. This might be set to 0.

  • 3. There is an option "lazy". The docs say:

  • >Determines if the positioning engine (powered by Popper.js) is created lazily. That is, it's only created when necessary upon showing the tippy for the first time.

    If you need to access the popperInstance synchronously after creation, set this to false. Note that disabling this decreases performance considerably.


    The default value is true, it might cause a delay when the popup is opened for the very first time.
    Copy linkTweet thisAlerts:
    @sibertauthorJun 03.2022 — > @Sempervivum#1644464 Sorry for replying late, this thread lost focus on my screen.

    Sorry for what? I am utmost thankful for all your valuable insights! Whenever they come...

    > When testing the tooltips I notice that they pop up when I release the mouse. Maybe this is causing the illusion of a delay.

    I do not think this is the case. The delay appear after mouseup. And mainly on the new IE (Safari). Not that obvious on Chrome.

    > There is a short animation (fade in) for the popup. The duration is 275ms for appearing. This might be set to 0.

    I cannot se any settings for this. Any tip?

    > There is an option "lazy". The docs say:

    The delay appear each click. Not only the first. So I do not think that this will help.
    Copy linkTweet thisAlerts:
    @SempervivumJun 03.2022 — @sibert#1644467
    >Not that obvious on Chrome.

    I tested it on Opera which, AFAIK, is using the same engine as Chrome. Maybe that's the reason why I didn't notice a significant delay.

    >I cannot se any settings for this. Any tip?
    >
    I viewed this page:

    https://atomiks.github.io/tippyjs/v5/all-props/

    and there are two properties related to the animation:

    `animation</C> for the type and <C>duration`.
    ×

    Success!

    Help @sibert spread the word by sharing this article on Twitter...

    Tweet This
    Sign in
    Forgot password?
    Sign in with TwitchSign in with GithubCreate Account
    about: ({
    version: 0.1.9 BETA 4.25,
    whats_new: community page,
    up_next: more Davinci•003 tasks,
    coming_soon: events calendar,
    social: @webDeveloperHQ
    });

    legal: ({
    terms: of use,
    privacy: policy
    });
    changelog: (
    version: 0.1.9,
    notes: added community page

    version: 0.1.8,
    notes: added Davinci•003

    version: 0.1.7,
    notes: upvote answers to bounties

    version: 0.1.6,
    notes: article editor refresh
    )...
    recent_tips: (
    tipper: @Yussuf4331,
    tipped: article
    amount: 1000 SATS,

    tipper: @darkwebsites540,
    tipped: article
    amount: 10 SATS,

    tipper: @Samric24,
    tipped: article
    amount: 1000 SATS,
    )...