A recent article on Pharyngula blog,
You ain’t no fortunate one,
discussed US wars, specifically the qeustion: depending on when you were born,
for how much of your life has the US been at war?
It was an interesting bunch of plots, constantly increasing until
for people born after 2001, the percentage hit 100%.
Really? That didn't seem right.
Wasn't the US in a lot of wars in the past?
When I was growing up, it seemed like we were always getting into wars,
poking our nose into other countries' business.
Can it really be true that we're so much more warlike now than we used to be?
It made me want to see a plot of when the wars were, beyond Pharyngula's
percentage-of-life pie charts. So I went looking for data.
The best source of war dates I could find was
American Involvement in Wars from Colonial Times to the Present.
I pasted that data into a table and reformatted it to turn it into Python
data, and used matplotlib to plot it as a Gantt chart.
(Script here:
us-wars.py.)
Sure enough. If that Thoughtco page with the war dates is even close to
accurate -- it could be biased toward listing recent conflicts,
but I didn't find a more authoritative source for war dates --
the prevalence of war took a major jump in 2001.
We used to have big gaps between wars, and except for Vietnam,
the wars we were involved with were short, mostly less than a year each.
But starting in 2001, we've been involved in a never-ending series of
overlapping wars unprecedented in US history.
The Thoughtco page had wars going back to 1675, so I also made a plot
showing all of them (click for the full-sized version).
It's no different: short wars, not overlapping, all the way back
to before the revolution. We've seen nothing in the past like the
current warmongering.
Depressing. Climate change isn't the only phenomenon showing
a modern "hockey stick" curve, it seems.
Tags: politics, programming, python, matplotlib
[
12:25 Jan 14, 2020
More politics |
permalink to this entry |
]
I wrote about
various ways of managing a persistent popup window from Javascript,
eventually settling on a postMessage()
solution that
turned out not to work in QtWebEngine. So I needed another solution.
Data URI
First I tried using a data:
URI.
In that scheme, you encode a page's full content into the URL. For instance:
try this in your browser:
data:text/html,Hello%2C%20World!
So for a longer page, you can do something like:
var htmlhead = '<html>\n'
+ '<head>\n'
+ '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n'
+ '<link rel="stylesheet" type="text/css" href="stylesheet.css">\n'
+ '</head>\n'
+ '\n'
+ '<body>\n'
+ '<div id="mydiv">\n';
var htmltail = '</div>\n'
+ '</body>\n'
+ '</html>\n';
var encodedDataURI = encodeURI(htmlhead + noteText + htmltail);
var notewin = window.open('data:text/html,' + encodedDataURI, "notewindow",
"width=800,height=500");
Nice and easy -- and it even works from file: URIs!
Well, sort of works. It turns out it has a problem related to
the same-origin problems I saw with postMessage.
A data: URI is always opened with an origin of about:blank;
and two about:blank origin pages can't talk to each other.
But I don't need them to talk to each other if I'm not using postMessage,
do I? Yes, I do.
The problem is that stylesheet I included in htmlhead above:
<link rel="stylesheet" type="text/css" href="stylesheet.css">\n'
All browsers I tested refuse to open the stylesheet in the
about:blank popup. This seems strange: don't people use stylesheets
from other domains fairly often? Maybe it's a behavior special to
null (about:blank) origin pages. But in any case, I couldn't find
a way to get my data: URI popup to load a stylesheet. So unless I
hard-code all the styles I want for the notes page into the Javascript
that opens the popup window (and I'd really rather not do that),
I can't use
data: as a solution.
Clever hack: Use the Same Page, Loaded in a Different Way
That's when I finally came across
Remy Sharp's page, Creating popups without HTML files.
Remy first explores the data: URI solution, and rejects it because of
the cross-origin problem, just as I did. But then he comes up with a
clever hack. It's ugly, as he acknowledges ... but it works.
The trick is to create the popup with the URL of the parent page that
created it, but with a named anchor appended:
parentPage.html#popup
.
Then, in the Javascript, check whether #popup
is in the
URL. If not, we're in the parent page and still need to call
window.open
to create the popup. If it is there, then
the JS code is being executed in the popup. In that case, rewrite the
page as needed. In my case, since I want the popup to show only whatever
is in the div named #notes, and the slide content is all inside a div
called #page, I can do this:
function updateNoteWindow() {
if (window.location.hash.indexOf('#notes') === -1) {
window.open(window.location + '#notes', 'noteWin',
'width=300,height=300');
return;
}
// If here, it's the popup notes window.
// Remove the #page div
var pageDiv = document.getElementById("page");
pageDiv.remove();
// and rename the #notes div so it will be displayed in a different place
var notesDiv = document.getElementById("notes");
notesDiv.id = "fullnotes";
}
It works great, even in file: URIs, and even in QtWebEngine.
That's the solution I ended up using.
Tags: programming, web, javascript
[
19:44 Jan 09, 2020
More tech/web |
permalink to this entry |
]
I'm trying to update my
htmlpreso
HTML presentation slide system to allow for a separate notes window.
Up to now, I've just used display mirroring. I connect to the
projector at 1024x768, and whatever is on the first (topmost/leftmost)
1024x768 pixels of my laptop screen shows on the projector. Since my
laptop screen is wider than 1024 pixels, I can put notes to myself
to the right of the slide, and I'll see them but the audience won't.
That works fine, but I'd like to be able to make the screens completely
separate, so I can fiddle around with other things while still
displaying a slide on the projector. But since my slides are in HTML,
and I still want my presenter notes, that requires putting the notes
in a separate window, instead of just to the right of each slide.
The notes for each slide are in a <div id="notes">
on each page. So all I have to do is pop up another browser window
and mirror whatever is in that div to the new window, right?
Sure ...
except this is JavaScript, so nothing is simple. Every little thing
is going to be multiple days of hair-tearing frustration, and this
was no exception.
I should warn you up front that I eventually found a much simpler way
of doing this. I'm documenting this method anyway because it seems
useful to be able to communicate between two windows, but if you
just want a simple solution for the "pop up notes in another window"
problem, stay tuned for Part 2.
Step 0: Give Up On file:
Normally I use file: URLs for presentations. There's no need
to run a web server, and in fact, on my lightweight netbook I usually
don't start apache2 by default, only if I'm actually working on
web development.
But most of the methods of communicating between windows don't work in
file URLs, because of the "same-origin policy".
That policy is a good security measure: it ensures that a page
from innocent-url.com can't start popping up windows with content
from evilp0wnU.com without you knowing about it. I'm good with that.
The problem is that file: URLs have location.origin
of null, and every null-origin window is considered to be a
different origin -- even if they're both from the same directory. That
makes no sense to me, but there seems to be no way around it. So if I
want notes in a separate window, I have to run a web server and use
http://localhost.
Step 1: A Separate Window
The first step is to pop up the separate notes window, or get a
handle to it if it's already up.
JavaScript offers window.open()
, but there's a trick:
if you just call
notewin = window.open("notewin.html", "notewindow")
you'll actually get a new tab, not a new window. If you actually
want a window, the secret code for that is to give it a size:
notewin = window.open("notewin.html", "notewindow",
"width=800,height=500");
There's apparently no way to just get a handle to an existing window.
The only way is to call window.open()
,
pop up a new window if it wasn't there before, or reloads it if it's
already there.
I saw some articles implying that passing an empty string ""
as the first argument would return a handle to an existing window without
changing it, but it's not true: in Firefox and Chromium, at least,
that makes the existing window load about:blank instead of
whatever page it already has. So just give it the same page every time.
Step 2: Figure Out When the Window Has Loaded
There are several ways to change the content in the popup window from
the parent, but they all have one problem:
if you update the content right away after calling window.open
,
whatever content you put there will be overwritten immediately when
the popup reloads its notewin.html page (or even about:blank).
So you need to wait until the popup is finished loading.
That sounds suspiciously easy. Assuming you have a function called
updateNoteWinContent()
, just do this:
// XXX This Doesn't work:
notewin.addEventListener('load', updateNoteWinContent, false);
Except it turns out the "load" event listener isn't called on reloads,
at least not in popups.
So this will work the first time, when the
note window first pops up, but never after that.
I tried other listeners, like "DOMContentLoaded" and
"readystatechange", but none of them are called on reload.
Why not? Who knows?
It's possible this is because the listener gets set too early, and
then is wiped out when the page reloads, but that's just idle
speculation.
For a while, I thought I was going to have to resort to an ugly hack:
sleep for several seconds in the parent window to give the popup time
to load:
await new Promise(r => setTimeout(r, 3000));
(requires declaring the calling function as async
).
This works, but ... ick.
Fortunately, there's a better way.
Step 2.5: Simulate onLoad with postMessage
What finally worked was a tricky way to use postMessage()
in reverse. I'd already experimented with using postMessage()
from the parent window to the popup, but it didn't work because the
popup was still loading and wasn't ready for the content.
What works is to go the other way. In the code loaded by the popup
(notewin.html in this example), put some code at the end
of the page that calls
window.opener.postMessage("Loaded");
Then in the parent, handle that message, and don't try to update the
popup's content until you've gotten the message:
function receiveMessageFromPopup(event) {
console.log("Parent received a message from the notewin:", event.data);
// Optionally, check whether event.data == "Loaded"
// if you want to support more than one possible message.
// Update the "notes" div in the popup notewin:
var noteDiv = notewin.document.getElementById("notes");
noteDiv.innerHTML = "Here is some content.
";
}
window.addEventListener("message", receiveMessageFromPopup, false);
Here's a complete working test:
Test of
Persistent Popup Window.
In the end, though, this didn't solve my presentation problem.
I got it all debugged and working, only to discover that
postMessage doesn't work in QtWebEngine, so
I couldn't use it in my slide presentation app.
Fortunately, I found a couple of other ways: stay tuned for Part 2.
(Update: Part 2: A Clever Hack.)
Debugging Multiple Windows: Separate Consoles
A note on debugging:
One thing that slowed me down was that JS I put in the popup didn't
seem to be running: I never saw its console.log()
messages.
It took me a while to realize that each window has its own web console,
both in Firefox and Chromium. So you have to wait until the popup has
opened before you can see any debugging messages for it. Even then,
the popup window doesn't have a menu, and its context menu doesn't
offer a console window option. But it does offer Inspect element,
which brings up a Developer Tools window where you can click on
the Console tab to see errors and debugging messages.
Tags: programming, web, javascript
[
20:29 Jan 05, 2020
More tech/web |
permalink to this entry |
]