DOM manipulation with mathjax - javascript

I'm trying to make a button that when pressed at least once, a math symbol/equation made from MathJax appears in a designated spot on my webpage. So far, this is what I have
//in JS
//when button is pressed, then x^2 appears
document.getElementById("button").addEventListener("click", function() {
MathJax.HTML.addElement(document.body, "div", {
id: "sqrt"
}, ["$$x^2$$"]);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/MathJax.js"></script>
<!--Press button for x^2 to appear-->
<button id="button">Press</button>
<!--x^2 appears here-->
<p id="sqrt"></p>
but every time I press the button, I don't get x^2, I literally get a string. I know the MathJax.HTML.addElement works when it's outside the event listener, but I'm trying to do this with a button. Any ideas would be helpful.
Thank you
Edit: My question is different because it's asking why, when I submit a button, it's showing my the literal string $$x^2$$ instead of the symbol. I know the document on the MathJax page mentions something about it, but I'm a complete novice and don't understand it. I was hoping someone could help me.
Link to a repl.it showing the problem:
https://mathjax-experiement--dolphinsupreme.repl.co
Edit 2: The x^2 symbol isn't showing up on the repl.it link, I'm not sure why because it's showing up on repl.it built-in page.

Once you have loaded mathJax, you need either a configuration script, or specify options through url:
eg ?config=TeX-AMS-MML_HTMLorMML&dummy=.js
see Loading and Configuring MathJax
MathJax works as a preprocessor, and renders mathematical expressions in the DOM where it's needed.
Everything else that contains mathematical expressions which comes from variables in your script, json or whatever must be interpreted (typesetted) to be properly rendered, once it's appended to the DOM.
MathJax.Hub.Typeset()
Outside the event listener, if MathJax.HTML.addElement(); is immediatly invoked when the DOM is loading, it will first add the element to the DOM, then is processed with the whole document (or the nodes you have specified in your configuration), that's why it works.
Later when you click a button to display a mathematical expression from a variable, you have to invoke Typeset() again:
MathJax.Hub.Typeset(".sqrt");
Indeed, addElement(), won't render your element:
addElement(parent, type[, attributes[, content]])
Creates a DOM element and appends it to the parent node provided. It is equivalent to
parent.appendChild(MathJax.HTML.Element(type,attributes,content))
However, mathjax works asynchronously, as rendering may take a while (otherwise, browser may freeze until it's done), so what will happen if you click while mathjax engine is still occupied to render something elsewhere in your page? To avoid your call to be forgotten, it is safer to enqueue your desired typeset in the mathjax queue:
MathJax.Hub.Queue(["Typeset",MathJax.Hub,".sqrt"]);
If the queue is empty, this will throw immediatly, if not, it will insured you that your action will be thrown at the end of the process.
Everything is exposed here.
Side Note :
In your snippet, you do not configure mathjax properly.
You also add an element with always the same id, which is not valid.
If you just want to replace the content of your div, don't use addElement.
Otherwise you should work with class and create unique ids. See the snippet for details.
// this works as I explain above (don't forget to escape backslash in your string)
//console.log("\\"); as a reminder!
MathJax.HTML.addElement(document.body, "div", {class: "sqrt",id:"sqrt_"+length},["\\begin{equation}x+1\\over\\sqrt{1-x^2}\\end{equation}"]);
document.getElementById("button1").addEventListener("click", function() {
var length = document.getElementsByClassName(".sqrt").length;
var expression = document.getElementById("expression").value || `$$x^2$$`;
MathJax.HTML.addElement(document.body, "div", {class: "sqrt",id:"sqrt_"+length},[expression]);
MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#sqrt_"+length]);
});
document.getElementById("button2").addEventListener("click", function() {
var expression = document.getElementById("expression").value || `$$x^2$$`;
document.getElementsByClassName("sqrt")[0].innerHTML = expression;
MathJax.Hub.Queue(["Typeset",MathJax.Hub,".sqrt"]);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/MathJax.js?config=TeX-AMS-MML_HTMLorMML&dummy=.js"></script>
<textarea id="expression"></textarea>
<!--Press button for x^2 to appear-->
<button id="button1">Add</button>
<button id="button2">Replace</button>
<!--x^2 appears here-->
<p class="sqrt">\begin{equation}
x+1\over\sqrt{1-x^2}
\end{equation}</p>

Related

How do I stop my dynamic content being appended to end of content?

I'm trying to write some JavaScript that once the page has finished loading will create a div in the place where the is placed.
Here is a stripped-back version of the code...
window.addEventListener('load', function () {
var content = document.createElement('div');
content.id = 'div-ID';
document.getElementsByTagName('body')[0].appendChild(content);
});
It works outside of the addEventListener(), however, when inside the event listener it always puts the created div below the rest of the page content not in the place the <script> tag is placed.
I'm certain the issue is to do with this line...
document.getElementsByTagName('body')[0].appendChild(content);
I need an alternative version to this which doesn't appendChild() but my JS isn't that good and everything I've tried hasn't worked.
Its most likely simple to achieve, I've tried searching Google and Stack Overflow but my search terms don't seem to be producing the desired results.
Any help on this would be much appreciated
You could do it with Node.insertBefore
As such, your code would be something like:
document.body.insertBefore( content, document.body.childNodes[0] );
The second parameter is the referenceNode, that has following comment:
referenceNode is not an optional parameter -- you must explicitly pass a Node or null. Failing to provide it or passing invalid values may behave differently in different browser versions.

Access an element by id inside Jupyter notebook _repr_javascript_ method

I cannot retrieve a newly added html object using its id while inside the Jupyter output cell. How can I do it?
EDIT: I have been able to replicate the same behavior in a notebook hosted on Azure:
https://notebooks.azure.com/rickteachey/projects/sandbox/html/js_repr_id_access.ipynb
NOTE: to run this notebook, click Clone at the top right and run it in your own Azure project/notebook.
The javacript first adds a new html button using the Jupyter API (ie, element.html(); in context, element refers to the Jupyter output cell <div>).
Then the code attempts to access the button using document.getElementById():
class C:
def _repr_javascript_(self):
return f'''
element.html(`<button id="clearBtn">Clear</button>`)
var x = document.getElementById("clearBtn")
alert(x)
'''
C()
EXPECTED BEHAVIOR: The alert should show a stringified version of the clearBtn html button object.
ACTUAL BEHAVIOR: The alert shows a null object, which means the script fails to grab the clearBtn - even though I can see it in the DOM when I look at the source.
It's possible I'm using the API incorrectly. If so, how am I supposed to do this?
Another weird issue: when I look at the same notebook on nbviewer, the alert pops up the clearBtn html object as expected. It does NOT behave this way on my local machine(s), or on Azure. Should I report this as a bug?
https://nbviewer.jupyter.org/urls/dl.dropbox.com/s/dwfmnozfn42w0ck/access_by_id_SO_question.ipynb
Updated answer, original below:
I've tested this now and think I worked out why the below fixes it. Specifically, when the output cell is generated and inserted in the page, it happens in the following order:
Page receives data from the server.
parses the HTML, including the <script> element.
executes the script, seemingly putting the element in its local scope by some means I haven't yet seen. (This is different from how it happens in the nbviewer page, where the output is already rendered, and the script gets it by using something like var element = $('#ad74eb90-4105-4cc9-83e2-37fb7e953a9f');, which can be seen in the source.)
only then inserts the HTML content in the document.
That means that at the time the script runs, the element is inside a detached DOM node. This can also be checked with the following:
alert(element[0].parentNode.parentNode) -> null
alert(element[0].parentNode.outerHTML) ->
<div class="output_area">
<div class="run_this_cell"></div>
<div class="prompt output_prompt">
<bdi>Out[28]:</bdi>
</div>
<div class="output_subarea output_javascript rendered_html">
<button id="clearBtn">Clear</button>
</div>
</div>
In other words, all manipulation or traversal of the rendered output needs to go through the element variable (such as $("#clearBtn", element) or element.find("#clearBtn") or even element[0].querySelector("#clearBtn")). It can't go through document, because the element isn't yet part of the document when the script runs.
Original answer:
This is just a vague idea: Is it possible the global document in this context is not actually the same document as the one element is in? There might be some iframe stuff going on in the editor, which might explain why it works after being rendered to a single page by nbviewer but not before. (Elements inside iframes are not part of the parent document, even though the browser's DOM viewer nests them as if they were.)
I would suggest using the element you already have to find the button you just inserted in it, instead of trying to find it from the document. (I'm not sure what kind of object element is, but there should be a way to get at the DOM node it's referencing and then use .querySelector("#clearBtn"), right?)
Edit: If the element.html() line is jQuery code, then element is a jQuery object and element.find("#clearBtn")[0] would find the contained button.
(This could also be done with element[0].querySelector("#clearBtn"). Note that the return value of .find() is itself a jQuery object, and that dereferencing [0] on a jQuery object returns the (first) DOM element inside it.)
I see two ways of doing it, by returning HTML or Javascript:
class C:
def _repr_javascript_(self):
alert = "alert('x');"
return f'''
element.html(`<button onclick="{alert}" id="clearBtn">Clear</button>`)
'''
C()
or
class C:
def _repr_html_(self):
return f'''
<button onclick="x()" id="clearBtn">Clear</button>
<script>
{{
var bt = document.getElementById("clearBtn");
bt.onclick = function(){{ alert('hi'); }};;
}}
</script>
'''
C()

Generate Bootstrap tooltip on-the-fly

I have a page with a text and some words in the text can change dynamically. These words have an element and should have a tooltip. When the user hovers the word (or I guess on a touch device clicks it), a tooltip should be generated using generateSpecialMarkupElement($(element).text()). Once the HTML has been rendered to the DOM another JavaScript function has to be called replaceMarkupHTML().
I don't have control over these functions unfortunately.
Now I'm wondering if there is a simple way in bootstrap get this done. For instance a before event to run the first function and an after event to call the second one.
Here is a simple example text and simplified versions of the functions:
http://jsfiddle.net/8aqz5auk/1/
So is there a bootstrap-way of hooking/intercepting this kind of thing? Or is there maybe another simple way it could be done?
Edit: I just had an idea. When bootstrap shows a tooltip, it seems to inject an element into the DOM. The interesting part is the container with the class 'tooltip-inner'. So I tried to listen on the body for new elements matching '.tooltip-inner' to be injected and whenever that happens I try to manipulate it:
$('body').on('DOMNodeInserted', '.tooltip-inner', function () {
var el = $(this)
el.html("") // empty the tooltip element
el.append(generateSpecialMarkupElement(el.text())) // insert the generated markup element
replaceMarkupHTML() // replace the markup element with normal html
});
Unfortunately it doesn't work. It just throws a a million errors and the site freezes when I try it.
Edit 2:
Thanks to Chris Barr, I got a little bit closer: http://jsfiddle.net/8aqz5auk/2/
But the tooltip doesn't always show up and the position of the tooltip seems to be kind of wrong (shows up on top of the word, rather then next to/above/below/...).
You might want to look into the tooltip events listed in the docs: https://getbootstrap.com/docs/3.3/javascript/#tooltips-events
$('.elements-with-tooltips').on('show.bs.tooltip', function () {
// do something…
})
You can run a function: before being shown, after being shown, before hiding, after hiding, and when the element is inserted into the DOM.

JavaScript code not running

I have the following code:
...<some code>...
...<elements load>...
<script type="text/javascript">
var selected_city = document.getElementById('jform_city').value;
var selected_province=document.getElementById('jform_province').value;
<!-- set the onchange property for all options in provice to activate getCities() -->
document.getElementById('jform_province').onchange = function() {
getCities();
}
if(selected_province != ''){
getCities();
}
</script>
...<more elements load>....
<script type="text/javascript">
alert("TEST");
document.getElementById(selected_city).selected="1";
</script>
It selects an option from a drop down list I have, the problem is, if I remove the alert("") it stops working for some reason, any ideas?
You need to wait for the document to load before trying to use document.getElementById. The alert is slowing things down enough that, behind the alert box, the element gets loaded. Without the alert, there may well be no element for "selected_city" when the following line gets run.
Check out this StackOverflow question for more info about waiting for the page to load.
EDIT
First of all, what happens when a web browser parses the page is that it translates the HTML elements it receives into a DOM - the Document Object Model, which is the in-memory representation of the page. Only after the element is in the DOM is it possible to manipulate it, and it is not necessarily true that the elements will enter the DOM in the same order as they appear in the HTML: modern fast browsers run very asynchronously, only guaranteeing that the page will end up looking as if the whole thing was loaded synchronously. This is easy to verify: almost every browser will load text and even display it while it goes and fetches images. Only after the image is fetched does it insert it into the display, shoving / reflowing the text if it has to. The end result is the same, but the page appears to load much faster this way.
Unfortunately, this "wait for it" guarantee does not apply to Javascript (as JS itself is allowed to change the way the page loads). Therefore, simply moving the script tag to the end of the document is not the same thing as waiting for the DOM to contain the element.
You have to actually wait for the browser to call you back and tell you that the DOM has been loaded - that's the only way to really know you can manipulate the DOM. This is what window.onload is for, this callback. I can think of a couple of reasons this isn't working for you:
If you actually just plugged in verbatim window.onload = function();, you missed the point - this is meant to not be an empty function, but your function. I'd assume that you didn't just type that in, but just in case, your code should be
window.onload = function(){
document.getElementById(selected_city).selected="1";
};
Alternatively, since window.onload=[some function] is assigning a function to be called later to one single variable, there can be only one. If you're loading some other script that also assigns window.onload, your callback can be lost.
This is why frameworks such as jquery, which has a ready function that can accept and call back any number of functions, are frontend developers' gold. Here is another StackOverflow question specifically asking about using onload.
Finally, this line:
var selected_city = document.getElementById('jform_city').value;
also requires the DOM to be loaded before it runs properly. selected_city itself could be null or undefined because #jform_city is not loaded when you're asking for its value. This in turn will cause document.getElementById(selected_city) to fail, even if that element is loaded at the time that you try to select it.
Asynchronicity is a real pain in Javascript.
Any time you need to get information from the page itself, as a rule of thumb, you must wait for the DOM to load. In practice, this means that almost all of your code (except that which does not on any way require the page to be loaded) should be in a function that gets called after the page is loaded.

Replacing HTML with Javascript in a way that is executable in a browser

I have no Javascript experience at all. What I want is to replace a single instance of a block of text in a page's HTML - how can I do this?
30 minutes of reading around has brought me this:
javascript:document.body.innerHTML = document.body.innerHTML.replace("this","that");
Am I even close?
With no experiance at all I recommend you take a look at jQuery. With jQuery you can do:
Given:
<p>block of text</p>
jQuery:
$('p').text("some other block of text");
javascript:document.body.innerHTML = "that"
1) If it is part of a URL, such as <a href="...">, then you need
javascript:void(document.body.innerHTML = document.body.innerHTML.replace("this","that"));
2) If it is part of an event, such as <button onClick="...">, then you need
document.body.innerHTML = document.body.innerHTML.replace("this","that");
3) If you are trying to replace ALL instances of "this" with "that", and not just the first, then you need
... .replace(/this/g,"that")
You cannot just execute that script in the address bar. It needs to operate on a document, but there is nothing to replace there. Executing javascript from the address bar will give you a new empty document on which that code operates.
Even if you try to load a document from javascript, the rest of your script gets executed first. Try this:
javascript:window.location='http://www.google.com';alert(document.innerHTML);
You'll see that the alert pops up before the page is loaded, and it shows 'undefined'.
Even when you try binding to the onload event of the document or the window it won't work. Probably because they are reset afterwards.
javascript:window.location='http://www.google.com';window.onload=function(){alert(document.innerHTML);};
And it makes sense; if this would work, you could manipulate the next page when jumping to that page, thus making it possible to inject javascript in a page you link to. That would be a big security issue, so it's a good thing this doesn't work.

Categories

Resources