I have this bit of code. It is used to update the form after a select element changes. onChange an "ajax" call is made and this bit of code takes care of the response.
The first time everything works as expected. However it the dojo.parser.parse fails to return about 50% of the time.
At first it looked like this:
var targetNode = dojo.byId(node);
targetNode.innerHTML = data;
dojo.parser.parse(targetNode);
Then I read something about the objects existing. So I thought that maybe destroying them would help:
if(dojo.byId(node)) dojo.destroy(node);
dojo.create('div', { id: node }, afternode, 'after');
var targetNode = dojo.byId(node);
targetNode.innerHTML = data;
dojo.parser.parse(targetNode);
That didn't help any. What the h3ll is going on? Sometimes it parses some of the elements. Is this a known problem with the dojo.parser?
If you create the dijits declaratively and use dojo.parser.parse to parse them on-the-fly and you specify the ID of the dijit, once you parse the same HTML fragment twice, you'll get an error says that the dijit's ID has been registered.
<div dojoType="dijit.form.Button" id="myButton" />
The cause is that the dijits have not been destroyed yet and you can not reuse the ID. If you don't specify the ID when declaring it, you won't get this error, but you actually have memory leaks.
The correct way is to destroy the dijits before parsing the HTML fragment again. The return value of dijit.parser.parse is an array list that contains the references of all the dijits it parsed from the HTML fragment. You can keep the list and destroy the dijits first.
if (dijits) {
for (var i = 0, n = dijits.length; i < n; i++) {
dijits[i].destroyRecursive();
}
}
dijits = dojo.parser.parse(targetNode);
Related
I'm replacing in a 'page' the html content of a div from MySQL (via php) and sometimes but not always inserting, from MySQL, any javascript needed for that content using $.getScript().
This all works OK. When the user clicks next page it gets new html and javascript from MySQL but after many many pages it has accumulated many scripts which seems like a bad idea to me as I don't want scripts no longer needed floating around. They can be re-loaded if the user clicks the 'previous page' button (what happens then - multiple instances of the same script?).
These scripts get audio content (ion.sound) with arrays of data ~100 elements.
It seems that there's no way to delete those scripts and free up memory.
I'm worried about eventual performance issues. Or is this the completely wrong way of doing it?
Any help appreciated.
This probably wouldn't work with the getScript functionality. The reason is that in general, items get added to the global namespace after they loaded. There is already a good discussion on this question
Based on those suggestions, you could load your script, and save the differences inside the global namespace, and when you remove the script, simply remove those object again from the global namespace.
The example here loads jQuery when you click on the button, then checks which changes were made to the global window object and upon removal, deletes those items again from the window object.
const jQueryId = "#jQuery-loaded-js-id";
function addJQuery( cb ) {
if (document.querySelector(jQueryId)) {
console.info('JQuery already loaded');
return;
}
var scriptElem = document.createElement('script');
scriptElem.id = jQueryId.substr(1);
scriptElem.addEventListener('load', function( windowKeys, e ) {
console.log( 'jQuery loaded' );
// get the difference of keys between before load and after load
// this will only work if no other scripts were loaded in between
// which would mean, scripts should only be loaded one at the time
var newKeys = Object.keys( window );
var diff = newKeys.reduce( (set, item) => {
if (windowKeys.indexOf(item) === -1) {
set.push( item );
}
return set;
}, []);
scriptElem.dataset.globalKeys = diff;
}.bind(this, Object.keys(window)) );
scriptElem.src = 'https://code.jquery.com/jquery-2.2.4.min.js';
document.body.appendChild( scriptElem );
}
function isJQueryAvailable() {
// checks if jQuery exists
alert( !!(window.jQuery || window.$) );
}
function removeJQuery() {
var elem;
if (!( elem = document.querySelector(jQueryId) )) {
console.info('not available');
return;
}
document.body.removeChild(elem);
// removes any of the global keys added to the DOM
var keys = elem.dataset.globalKeys.split(',');
for (let i = 0; i < keys.length; i++) {
delete window[keys[i]];
}
console.info('JQuery removed');
}
<button type="button" onclick="addJQuery()">Get JQuery</button>
<button type="button" onclick="isJQueryAvailable()">Does JQuery Exist?</button>
<button type="button" onclick="removeJQuery()">Remove JQuery</button>
The example shouldn't really be what you need to implement, because it oversimplifies what could happen when a script loads. Probably you need to think of a more module based approach, where the scripts you load have a way to dispose themselves. I am just giving you an example of a potential approach
It seems that scripts don't hang around after calling $.getScript() as it loads the script then executes it. I put in a simple mechanism to count scripts and the number varies per page but is not accumulated - I think. Anyway I'll blunder on.
I have the follwing javascript code that it triggers an IronPython script when I load the report.
The only issue I have is that for a reason I don't know it does it (it triggers the script) a couple of times.
Can some one help me? below is the script:
var n=0;
$(function () {
function executeScript() {
if (n==0){
n=n+1;
now = new Date();
if (now.getTime()-$('#hiddenBtn input').val()>10000){
$('#hiddenBtn input').val(now.getTime());
$('#hiddenBtn input').focus();
$('#hiddenBtn input').blur();
}
}
}
$(document).ready(function(){executeScript()});
strong text});
Please, let me know if you need more information.
Thanks in advance!!!
I have had similar issues with Javascript executing multiple times. Spotfire seems to instance the JS more than once, and it can cause some interesting behavior...
the best solution, in my opinion, only works if users are accessing the document via a link (as opposed to browsing the library). pass a configuration block to set a document property with a current timestamp, which would execute your IP script. this is the most solid implementation.
otherwise, you can try something like this:
// get a reference to a container on the page with an ID "hidden"
var $hidden = $("#hiddenBtn input");
// only continue if the container is empty
if !($hidden.text()) {
var now = Date.now();
$hidden.text(now)
.focus()
.blur();
|}
this is essentially the same as the code you posted, but instead of relying on the var n, you're counting on the input #hiddenBtn > input being empty. there is a caveat that you'll have to ensure this field is empty before you save the document
one addtional solution is using a Data Function to update the document property, like #user1247722 shows in his answer here: https://stackoverflow.com/a/40712635/4419423
I'm trying to create an implementation of "infinite scrolling". Here is my relevant code:
var postsIndex = 0;
var chunk = 5;
function getNextChunk() {
for (i=0; i<chunk; i++) {
postsIndex++;
document.write(getTimeLineElement(posts[postsIndex]));
}
}
if (postsIndex === 0) { // initial chunk.
var contentDiv = document.createElement("DIV"); // Create the div container.
var divTextNode = document.createTextNode(getNextChunk()); // Create a text node with the initial chunk.
contentDiv.appendChild(divTextNode); // Append the text node to the div container.
contentDiv.style.border="solid";
}
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
alert("near bottom!");
contentDiv.appendChild(getNextChunk());
}
});
The initial chunk loads just fine and the loading of the 2nd chunk is triggered just fine. However, when the 2nd chunk gets loaded the original content is lost leaving only what was appended. As you can see I have placed a border around the div for testing purposes. I'm not seeing that border. I'm not sure why. This may be an indication that somehow this whole thing is not in the div element like I think that it is. Can anyone please tell me what I'm missing? Thanks for any input.
... doug
Your function, getNextChunk, which is called at various times after the document has loaded, itself calls document.write. As described in the notes in MDN's document.write documentation, when called against a document that has loaded, document.write makes an implicit call to document.open. The Notes in the MDN documentation for document.open state that when called against an existing document, document.open clears that document.
As such, your function is wiping out the document before inserting your content.
I don't know what you expect any of this to do anyway...you're calling appendChild while passing in a function call. This means that function's return value would be passed in--but it doesn't return anything...and it calls document.write in a loop...it's really all over the map.
We will start with the line that is not behaving the way you expect.
contentDiv.appendChild(getNextChunk());
What does this mean?
It means, execute the getNextChunk function and whatever it returns, add it to the DOM as a child of contentDiv.
So, what does getNextChunk return?
function getNextChunk() {
for (i=0; i<chunk; i++) {
postsIndex++;
document.write(getTimeLineElement(posts[postsIndex]));
}
}
As far as I can tell from that code, it does not actually return anything. Therefore, you are adding nothing as a child of your content div.
So what is getting added to the DOM?
You are calling getTimeLineElement, but adding its return value (assuming it is a string) directly to the HTML content of the page. This does not go into the div, but just gets appended to the document.
What did I do wrong?
Returns: You are passing the return value of a function which does not return anything.
Side effects: You have a function named get*, which suggests it exclusively reads data, but actually mutates the page (called a side effect).
Data types: The result of a function named getTimeLineElement, which suggests it returns a DOM element, actually returns a string.
Always understand what data types you are working with and how they move around from function to function.
I'm creating a little SPA framework, and I decided to integrate webcomponents.
Everything works fine, but I'm just curious about 1 thing.
I extended HTML element and HTML element fragment with method add
(note that it might not be perfect, it's still under the development)
DocumentFragment.prototype.add = function(options){
let element = document.createElement(options.elementType);
if (options.innerHTML){
element.innerHTML = options.innerHTML;
}
if (options.id){
element.id = options.id;
}
if (options.attributes){
for (let attr in options.attributes){
element.setAttribute(attr, options.attributes[attr]);
}
}
if (options.insertBefore){
this.insertBefore(element, options.insertBefore);
return element;
}
this.appendChild(element);
return element;
}
Element.prototype.add = DocumentFragment.prototype.add;
Now, in my view, I want to use this method like this:
root.add({
elementType: "test-component",
attributes: {
attr1: "value",
attr2: "26.1.2016",
attr3: "value"
}
It works, but the problem is, that createdCallback gets fired before I set attributes for this component, and I can't access these attribute in createdCallback.
Now, I'm using attributeChangedCallback to solve this, but it obviously isn't the best solution
attributeChangedCallback(attrName, oldVal, newVal) {
if (attrName == "attr1"){
*do something*
}
if (attrName == "attr2"){
*do something*
} ...
}
Is there a better way to solve this? Avoiding "add" method by extending innerHTML of parent element with something like
<test-component attr1="value"></test-component>
isn't very helpful, since this little "add" method simplifies my work a lot.
Every answer is much appretiated. Thanks
CreatedCallback gets fired whenever your element gets created(Obvious).
Which is very useful when you are using custom elements 'declaratively', i.e.
not appending it in DOM using js.
For your use case, CreatedCallback is getting fired as soon as you execute document.createElement and since you are executing setAttribute post that those won't be accessible in createdCallback method.
Using attributeChangedCallback is a valid alternative here. If you want to consider another one, you can use attachedCallback which gets executed whenever you are appending your node to DOM i.e executing appendChild on it.
Take a look at this article if you haven't already.
I'm working on a little project in JavaScript/jQuery.
In order to display results from a calculation done in javascript, I'd like to open a new window with some predefined content and modify this content in order to display the results: Im using code like this:
var resultwindow = window.open('result.html')
var doc = $('body', resultwindow.document);
doc.append("<p>Result</p>")
This is not working since the result document is not yet loaded when I append the content, so it is overwritten with the contents of 'result.html'.
I also tried
$(resultwindow.document).ready(function() {
// ... Fill result document here
})
and
$(resultwindow.document).load(function() {
// ... Fill result document here
})
but ready() works only on the current document (it is called immediately, if the current document is already loaded), and load doesn't get called at all.
Perhaps someone can point me to the right direction. Thanks in advance!
EDIT:
I finally solved this by creating the new document "by hand" in Javascript like:
w = window.open('','newwinow','width=800,height=600,menubar=1,status=0,scrollbars=1,resizable=1);
d = w.document.open("text/html","replace");
d.writeln('<html><head>' +
'<link rel="stylesheet" type="text/css" href="style.cs"/></head>' +
+'<body></body></html>');
// use d to manipulate DOM of new document and display results
If I were to do the same thing today (two years of experience later), I'd use some Javascript template library like Handlebars to maintain a template and compile it to javscript.
Your load call doesn't work because you're attempting to handle the load of the document, and chances are document does not even exist at this point. Which means you are passing null into jQuery, and it gracefully ignores you. Handle the load event of the raw window reference instead, and then you should be good to go...
var win = window.open("result.html");
$(win).load(function() {
$("body").append("<p>Result</p>");
});
The problem you have is that load() doesn't do what you think it does.
Instead, use bind("load", function() { /* Your function here */ }); then everything should work.
Correction:
load() is actually a dual-use function -- if it's called with a function as its first parameter, then it binds it to the load event of the object (or objects) in question, otherwise it loads the returned data (if any) into the elements in question. See Josh's answer for the real reason why it's not working.
Send the data to result.html in the querystring and then have the result.html display the data from there. If you want to be less obvious about it going through you could hash the data in the querystring and have the result page dehash it.