How to reload javascript for jinja template? - javascript

I'm relatively new to jinja2 (and by no means a web dev expert), and I'm having trouble getting the javascript associated with a jinja template to reload (and update its variables) based on changes in the state of the program.
Essentially, I have one template iterating through a list of items:
{% for item in items %}
<p> {{item['property a']}}</p>
blah blah blah`
and then I call another template I've imported within that for loop:
{% import 'secondTemplate.html' as s %} //not sure whether this matters if it goes inside or outside the for loop
<p> {{s.doSecondStuff(item)}}</p>
That all works. The second template executes each time with each item, which is want I want.
But, I have some secondTemplate.js associated with secondTemplate.html that operates on the variables passed to secondTemplate.html. My problem is that secondTemplate.js only ever operates on the first item's values.
It seemed like the top answer from Loading external script with jinja2 template directive would work, so I've been trying to put the following in the for loop:
{% block javascript %}
<script type="text/javascript">
{% include "secondTemplate.js" %}
</script>
{% endblock %}
But the js is still only reflecting the values of the first item in the list.
Any ideas? Thanks!

If you open your Flask app with a browser, Flask looks through the routes you defined and selects the one that it finds the most appropriate, for example:
#app.route('/<name>')
def index(name):
return render_template('index.htm', var1=name)
It executes the inside of the function like a normal function in any programming language. And here is the point: The function only returns a string of HTML. It doesn't do any "magic" to pass any information about variable names to the browser. To be concrete, if index.htm looked like this:
<h1>Hello {{ name }}!</h1>
Then render_template('index.htm', name="world") returns a string with the content "<h1>hello world!</h1>", which is what your view returns, which is what Flask gives the browser.
So the browser, and therefore your Javascript code, have absolutely no idea which part of the HTML was a variable and which not. By the time you are executing Javascript in the browser, your Flask app already has forgotten about the client and the value of name it was given.
It's really unclear what you are asking, but i hope i made it clear to you how the thing you are trying to achieve will not be possible in that way.

I am not familiar with jinja, but there is a trick like below, if you want to reload js file (if I understand you right). Let's make all tags with id's, so:
<script src="be/happy.js" id ="be/happy.js"></script>
<script src="be/very/happy.js" id ="be/very/happy.js"></script>
and so on. Now is the clue. We can force browser to load file again, using this method:
var s = document.getElementById('be/happy.js'); //get access to node
d.parentNode.removeChild(s); //remove it from document
delete d; //destroy element
d = document.createElement('script'); //create new script tag
d.src = d.id = 'be/happy.js'; //add the same value to id and src attribute
document.getElementsByTagName('head')[0].appendChild(d); //append child again, what force browser to script reload

Related

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()

JavaScript: register a custom script type and define a handler?

HTML elements with a unique custom type attribute are ignored by the browser. Sometimes these are used by template engines. How do I define what happens when such a script element is loaded/created? (either while loading the page or when inserted dynamically)
In other words, does an onCreateElement event of some sorts exist in the DOM?
I could quite easily iterate through all script elements with attribute type=text/mycustomtype when the DOM loads using for instance the querySelector and then parse them with a function. This however does not work when a new script element is created and appended programatically. Is this currently possible?
var d = document.createElement('script')
d.setAttribute('type', 'text/mycustomtype')
d.innerHTML = 'define foo = 1;' // some code in some custom language
document.body.appendChild(d)
In this case, nothing will happen because the browser will ignore this unknown type. Can I somehow define a handler function for this? Thanks in advance.
I recently saw python script running in a browser using brython. View source and I see:
<script type="text/python3">
from interpreter import Interpreter
# open REPL in textarea with id "code"
Interpreter("code")
</script>
I wanted to know how this is possible. I googled for the answer, and found the dreaded stackoverflow question that's exactly what I want - with no answers.
I found a good answer in: Everything I Know About The Script Tag. It's exactly as you predicted when you say iterate through all script elements.
Their example looks like this:
<script type="text/emerald">
make a social network
but for cats
</script>
<script>
var codez = document.querySelectorAll('script[type="text/emerald"]');
for (var i=0; i < codez.length; i++)
runEmeraldCode(codez[i].innerHTML);
</script>

Adding jinja template dynamically

I have a jinja template that is the only content inside a set of div tags.
<div id="section0">
{% include 'temppage.html' %}
</div>
When I press a button, I want to replace everything between the tags with something else. I was hoping to replace it with another jinja template, "{% include 'realpage.html' %}", but first I am unsure of how to replace the entire section, instead of just replacing a single word. Second, can I even add a jinja template dynamically, or do I need replace it with a string with the contents of the file directly.
As glls said, replacing the content can be used with,
document.getElementById("section0").innerHTML = "something";
As for adding a jinja template dynamically, you need to replace the innerHTML with a multi-line string of the wanted jinja template, with is used with backticks, "`". So it would look like,
document.getElementById("section0").innerHTML = `{% include 'realpage.html' %}`;
The template is executed when the page loads (which is unavoidable as far as I'm aware), so when inspecting the html of the live page, the multi-line string will contain whatever is in the file you are including.
You could use a JS framework (such as Angular, React...) in order to achieve this...I am assuming you are trying to build a single page app?
Otherwise, you will have to rely more on Javascript in order to change the HTML under you div depending on what you click. For example, if you have button 1, 2, 3. Each rendering a different HTML template upon clicking.
Example (using jQuery):
$(document).on('click', '.some-class', function() {
document.getElementById("section0").innerHTML = "something";
});
fyi: "something" can be an html structure.

Javascript execution order

I want to give a static javascript block of code to a html template designer, which can be:
either inline or external or both
used once or more in the html template
and each block can determine its position in the template relative to the other javascript code blocks.
An example could be image banners served using javascript. I give code to template designer who places it in two places, once for a horizontal banner in the header and once for a vertical banner. The same code runs in both blocks but knowing their positions can determine if to serve a horizontal or a vertical image banner.
Make sense?
Another example: Say you have the same 2 javascript tags in a web page calling an external script on a server. Can the server and/or scripts determine which javascript tag it belongs to?
NOTE: Can we say this is a challenge? I know that I can avoid this puzzle very easily but I come across this on a regular basis.
JavaScript code can locate all <script> elements on the page and it can probably examine the attributes and the content to check from which element it came from. But that's probably not what you want.
What you want is a piece of JavaScript which replaces tags on the page with ad banners. The usual solution is to add a special element, say a IMG, for this and give that IMG an id or a class or maybe even a custom attribute (like adtype="vertical") and then use JavaScript to locate these elements and replace the content by changing the src attribute.
For example, using jQuery, you can should your images like so:
<img src="empty.gif" width="..." height="..." class="ad" adtype="..." />
Then you can locate each image with
$('img.ad')
[EDIT] Well, the server obviously knows which script belongs into which script tag because it inserts the script. So this is a no-brainer.
If the script wants to find out where it is in the DOM, add something which it can use to identify itself, say:
<script>var id= '329573485745';
Then you can walk all script tags and check which one contains the value of the variable id.
If you call an external script, then you can do the same but you must add the ID to the script tag as you emit the HTML:
<script id="329573485745" src="..." />
Then the external script can examine the DOM and lookup the element with this id. You will want to use an UUID for this, btw.
This way, a piece of JS can locate the script tag which added itself to the page.
Best thing would probably be to make an insert once function, and then have him insert only the function call where needed.
Like this:
timescalled=0
function buildad(){
var toinsert="" //Code to generate the desired piece of HTML
document.write(toinsert)
timescalled+=1 //So you can tell how many times the function have been called
}
Now a script block calling the function can simply be inserted wherever a banner is needed
<script type="text/javascript">buildad()</script>
Thanks for the tips everyone but I'll be answering my own question.
I figured out several ways of accomplishing the task and I give you the one which works nicely and is easy to understand.
The following chunk of code relies on outputting dummy divs and jQuery.
<script>
// Unique identifier for all dummy divs
var rnd1="_0xDEFEC8ED_";
// Unique identifier for this dummy div
var rnd2=Math.floor(Math.random()*999999);
// The dummy div
var d="<div class='"+rnd1+" "+rnd2+"'></div>";
// Script which :
// Calculates index of THIS dummy div
// Total dummy divs
// Outputs to dummy div for debugging
var f1="<script>$(document).ready(function(){";
var f2="var i=$('."+rnd1+"').index($('."+rnd2+"'))+1;";
var f3="var t=$('."+rnd1+"').length;";
var f4="$('."+rnd2+"').html(i+' / '+t);";
var f5="});<\/script>";
document.write(d+f1+f2+f3+f4+f5);
</script>
Why not not just place the function call on the page instead of the entire code block? This way you can pass in a parameter to tell it what type of advertisement is needed?
BuildAd('Tower');
BuildAd('Banner');
Javascript itself has no clue of it's position in a page. You have to target a control on the page to get it's location.
I don't think it is possible for JavaScript code to know where it was loaded from. It certainly doesn't run at the point it is found, since execution isn't directly tied to the loading process (code usually runs after the whole DOM is loaded). In fact, in the case of externals, it doesn't even make sense, since only one copy of the code will be loaded no matter how many times it is encountered.
It shouldn't be the same code for each banner - there will be a parameter passed to whatever is serving the image banner which will specify the intended size.
Can you give a specific example of what you need this for?
To edit for your recent example: The simple answer is no. I could help you approach the problem from a different direction if you post details of your problem
The term "static block of code" leaves a lot of room for interpretation.
Inline scripts (e.g., ones that rely on document.write and so must be parsed and executed during the HTML parsing phase) cannot tell where they are in the DOM at runtime. You have to tell them (as in one of the first answers you got).
I think you'll probably find that you need to change your approach.
A common way to keep code and markup separate (which is useful when providing tools to HTML designers who aren't coders) is to have them use a script tag like so:
<script defer async type='text/javascript' src='pagestuff.js'></script>
...which then triggers itself when the page is loaded (using window.onload if necessary, but there are several techniques for being triggered earlier than that, which you want because window.onload doesn't trigger until the images have all loaded).
That script then looks for markers in the markup and manipulates the page accordingly. For instance (this example uses Prototype, but you can do the same with raw JavaScript, jQuery, Closure, etc.):
document.observe("dom:loaded", initPage);
function initPage() {
var verticals = $$('div.vertical');
/* ...do something with the array of "vertical" divs in `verticals`,
such as: */
var index;
for (index = 0; index < verticals.length; ++index) {
vertical.update("I'm vertical #" + index);
}
}
The designers can then have blocks on the page that are filled in by code which they flag up in a way that's normal for them (classes or attributes, etc.). The code figures out what it should do based on the classes/attributes of the blocks it finds when it runs.

Getting content of a script file using Javascript

I have the following script element in my web page:
<script src="default.js" type="text/javascript"></script>
Using JavaScript, I want to be able to retrieve the content of the script file. I know I could use an ajax request to get the data but then I am getting something from the server that I already have locally.
So what I would prefer to do is retrieve the content from the DOM (if that's possible) or something that has the same result.
Cheers
Anthony
UPDATE
I was trying to simplify the question, maybe a bad a idea, I thought this way would cause less questions.
The real situation I have is as follows, I actually have
<script type="text/html" class="jq-ItemTemplate_Approval">
...
html template that is going to be consumed by jQuery and jTemplate
...
</script>
Now this works fine but it means each time the page loads I have to send down the template as part of the HTML of the main page. So my plan was to do the following:
<script src="template.html" type="text/html"></script>
This would mean that the browser would cache the content of template.html and I would not have to send it down each time. But to do this I need to be able to get the content from the file.
Also in this case, as far as I know, requesting the content via ajax isn't going to help all that much because it has to go back to the server to get the content anyway.
If I understand you correctly, you don't want to use Ajax to load an html template text, but rather have it loaded with the rest of the page. If you control the server side, you can always include the template text in an invisible div tag that you then reference from Javascript:
<div id="template" style="display:none;">
...template text...
</div>
<script>
// pops up the template text.
alert(document.getElementById("template").innerHTML);
</script>
If you are just looking for to load the template so that you can have it cached, you can put the contents in a variable like this:
<script>
var template = "template text..";
</script>
or you can load it using ajax and store the template in a variable so it is accessible. It's pretty trivial in jquery:
var template;
$.get("template.html", function(data){
template = data;
});
unless you load a script as literal text in the page, it does not exist as text. It is interpreted by the browser and melded into the runtime, with any other scripts.
If you want the source you have to fetch it again,if with Ajax get the responseText.
It will come from the browser cache, and doesn't have to be downloaded again.
I think what you want to do is to assign a variable inside template.js. Then you have the variable available for use wherever you want in jquery. Something like:
var tpl = "<div> ... </div>"
Wouldn't this be a simpler solution to your problem? We do this in Ext JS. I think this will work for you in jQuery.
You could get the attribute of the src of the script and then use XHR to get the contents of the JS file. It's a much cleaner way of doing it IMO. e.g.:-
if(window.XMLHttpRequest) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.status == 200 && xhr.readyState == 4) {
var sourceCode = xhr.responseText;
alert('The source code is:-\n'+sourceCode);
}
}
xhr.open("GET",document.getElementById('scriptID').src,true);
xhr.send(null);
}
Using an iFrame & HTML5 Local Storage
Save the templates for rendering later...
not stoked about the iFrame, but it seems to be working pretty good (haven't ran performance tests yet)
Put the iFrame on the page you want the template on (index.html)
<html>
<head>
<iframe src="mustache.Users.html" onload="this.remove();" class="hidden" id="users_template"></iframe>
</head>
</html>
Make sure the src attribute is set
hide the element until you can get rid of it after it loads
Put this body wrapper around your template (mustache.Users.html)
(don't worry it won't show up in the template)
<body onload="localStorage.setItem('users_template',this.document.body.innerHTML);">
<ul class="list-group" id="users" >
{{#users}}<li>{{name}}</li>{{/users}}
</ul>
</body>
replace 'users_template' with whatever name for your variable
the 'onload' attribute saves the template into localStorage during load
Now You can access your templates from anywhere
localStorage.getItem('users_template')
OR
window.localStorage.getItem('users_template')
What is in the JavaScript file? If it's actual code, you can run functions and reference variables in there just like you had cut and paste them into the webpage. You'll want to put the include line above any script blocks that reference it.
Is this what your looking to accomplish?
Why not use Ajax (well Ajah because its html :-))?
when the server is set up correctly and no no-cache or past expires headers are sent, the browser will cache it.
The way that most JavaScript import files work is they include a script, that immediately calls a function with a parameter of certain text, or of another function. To better illustrate, say you have your main index.html file, set it up like this:
<html>
<head>
</head>
<body>
<script>
let modules = {};
function started(moduleName, srcTxt) {
modules[moduleName] = (srcTxt) //or something similar
}
</script>
<!--now you can include other script tags, and any script tags that will be included, their source can be gotten (if set up right, see later)-->
<script src="someOtherFile.js"></script>
</body>
</html>
now make that other file, someOtherFile.js, and right away when its loaded, simply call that "started" function which should already be declared in the scope, and when thats done, then whatever text is passed, from the file, is stored in the main index.html file. You can even stringify an entire function and put it in, for example:
started("superModule", (function() {
/*
<?myCustomTemplateLanguage
<div>
{something}Entire Javascript / html template file goes here!!{/something}
</div>
?>
*/
}).toString());
now you can access the inner content of the function, and get all the text in between the comments, or better yet, then do other parsing etc, or make some other kind of parsing identifiers at the beginning and end of the comments, as shown above, and get all text in between those

Categories

Resources