I've created a website that has index.html and quiz.html, one style.CSS and one app.JavaScript.
I have added some functions in the JS that refer to both pages. Lets say:
Page 1 index.html: the app.js has an event listener to show a box on click. That box then has a button to move to quiz.html (all works fine).
Page 2 quiz.html: on JavaScript I have functions to make the quiz run. The problem is that the code breaks before it gets to the functions for the quiz, with a line referring to the first page.
Error: app.js:14 Uncaught TypeError: Cannot read properties of null (reading 'addEventListener') at app.js:14:35
Line 14 is document.getElementById('btnPlay').addEventListener('click', openBox), that refers to an element within index.html, not the page I am now quiz.html.
I understand why, because on this current page we don't have a reference to it. But I have read in many places that is usually better to use one app.js file and not one per html page. How do we make this work? Any suggestions?
On some page B you don't have that #btnPlay Element therefore document.getElementById('btnPlay') equals undefined. And you cannot assign an event listner to it: undefined.addEventListener.
To make sure that element exists — before moving forward
use Optional Chaining ?..
document.getElementById('btnPlay')?.addEventListener('click', openBox)
otherwise on page B the "#btnPlay" is not found and your JavaScript will break.
Another "old" way to tackle that issue would've been using a statement:
const EL_btnPlay = document.querySelector("#btnPlay");
if (EL_btnPlay) EL_btnPlay.addEventListener("click", openBox);
where the aforementioned is just a shorthand to do the same.
Related
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.
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()
I am trying to show pdf document through ng2-pdfviewer. Following is code for viewing:
<pdf-viewer [page]="this.vars.page"
[src]="this.vars.pdfSrc"
[show-all]="true"
[original-size]="true"
></pdf-viewer>
It is working fine when show-all is set to false. But when I set it to true it skips first two pages and always starts from third page. Any help is appreciated.Thanks
I am suspicious that probably your error subsist in using the Typscript keyword this in a template. You don't need it in the template as it, because Angular will take care of making the binding between the class property (vars in your case) and the template, and 'this' is just not available.
So replace by the following:
<pdf-viewer [page]="vars.page"
[src]="vars.pdfSrc"
[show-all]="true"
[original-size]="true">
</pdf-viewer>
Please read about 'this'
__What I am trying todo____
Right now I am working with custom HTML5 tags. I am trying to create a tab-control element that is easy to set up. Todo this I create a parent element called 'tab-set' which works much like the 'ul' tag.
To insert tabs, you simply insert 'tab-element' tags (like 'li' tags). The tags can implement own behavior through custom prototypes which extend standardized element-prototypes such as the basic HTMLElement and are then registered with 'document.registerElement()'. At that point there are also opportunities to set callbacks that let you know whenever your element has been created or attached to something, which is what I use to do the necessary calculations on the placement of the individual tabs on the tab-control.
Let me say up-front that I've had trouble with this at first, then got it working, but upon rewriting the whole thing had troubles again for who knows why.
So, in the creation routine of the tab-set I iterate through all the child-tab-elements, calling their custom function 'setDimension', or atleast I am trying to. For some reason Chrome won't initialize the tab-element prototype (setDimension etc) before it has called both 'createdCallback' and 'attachedCallback' on my tab-set. This means that I can't call the child elements custom functions to set it's placement on creation of the tab-set.
Here you have some code samples of what I just described.
simple.html
...
<tab-set>
<tab-element>
<img>guybrush</img>
</tab-element>
<tab-element>
<img>le chuck</img>
</tab-element>
</tab-set>
...
tabs.js
...
tabSet = Object.create(HTMLDivElement.prototype);
tabSet.attachedCallback = function(){
for(/** calculations here **/)
listOfChildren[index].setDimensions(/** placement info **/);
//
// Chrome console: 'setDimensions' is not a function!
//
}
tabElement = Object.create(HTMLDivElement.prototype);
tabElement.setDimensions = function(/** placement info **/){
$(this).css(...);
}
document.registerElement('tab-set',tabSet);
document.registerElement('tab-element',tabElement);
...
The weird thing is that I have a working version of this, and yes, I have tried to emulate it's particular conditions such as for example loading the html-portion through jquery's .load() routine. But no matter what I do, I can not get this to work in my current script. What knowledge am I missing?
Thanks in advance for any help.
__ Solved __
All I had todo was add a link-tag inside the tab-set and have the tab-elements load it's containing style-class. I guess making the tab-elements have a css-class is somehow provoking Chrome to load their prototypes 'prematurely'.
I have my own custom non-jQuery ajax which I use for programming web applications. I recently ran into problems with IE9 using TinyMCE, so am trying to switch to CKeditor
The editable text is being wrapped in a div, like so:
<div id='content'>
<div id='editable' contenteditable='true'>
page of inline text filled with ajax when links throughout the site are clicked
</div>
</div>
When I try to getData on the editable content using the examples in the documentation, I get an error.
I do this:
CKEDITOR.instances.editable.getData();
And get this:
Uncaught TypeError: Cannot call method 'getData' of undefined
So I figure that it doesn't know where the editor is in the dom... I've tried working through all editors to get the editor name, but that doesn't work-- no name appears to be found.
I've tried this:
for(var i in CKEDITOR.instances) {
alert(CKEDITOR.instances[i].name);
}
The alert is just blank-- so there's no name associated with it apparently.
I should also mention, that despite my best efforts, I cannot seem to get the editable text to have a menu appear above it like it does in the Massive Inline Editing Example
Thanks for any assistance you can bring.
Jason Silver
UPDATE:
I'm showing off my lack of knowledge here, but I had never come across "contenteditable='true'" before, so thought that because I was able to type inline, therefore the editor was instantiated somehow... but now I'm wondering if the editor is even being applied to my div.
UPDATE 2:
When the page is loaded and the script is initially called, the div does not exist. The editable div is sent into the DOM using AJAX. #Zee left a comment below that made me wonder if there is some other command that should be called in order to apply the editor to that div, so I created a button in the page with the following onclick as a way to test this approach: (adapted from the ajax example)
var editor,html='';config = {};editor=CKEDITOR.appendTo('editable',config, html );
That gives the following error in Chrome:
> Uncaught TypeError: Cannot call method 'equals' of undefined
> + CKEDITOR.tools.extend.getEditor ckeditor.js:101
> b ckeditor.js:252
> CKEDITOR.appendTo ckeditor.js:257
> onclick www.pediatricjunction.com:410
Am I headed in the right direction? Is there another way to programmatically tell CKEditor to apply the editor to a div?
UPDATE 3:
Thanks to #Reinmar I had something new to try. The most obvious way for me to test to see if this was the solution was to put a button above the content editable div that called CKEDITOR.inlineAll() and inline('editable') respectively:
<input type='button' onclick=\"CKEDITOR.inlineAll();\" value='InlineAll'/>
<input type='button' onclick=\"CKEDITOR.inline('editable');\" value='Inline'/>
<input type='button' onclick=\"var editor = CKEDITOR.inline( document.getElementById( 'editable' ) );\" value='getElementById'/>
This returned the same type of error in Chrome for all three buttons, namely:
Uncaught TypeError: Cannot call method 'equals' of undefined ckeditor.js:101
+ CKEDITOR.tools.extend.getEditor ckeditor.js:101
CKEDITOR.inline ckeditor.js:249
CKEDITOR.inlineAll ckeditor.js:250
onclick
UPDATE 4:
Upon further fiddling, I've tracked down the problem being related to json2007.js, which is a script I use which works with Real Simple History (RSH.js). These scripts have the purpose of tracking ajax history, so as I move forward and back through the browser, the AJAX page views is not lost.
Here's the fiddle page: http://jsfiddle.net/jasonsilver/3CqPv/2/
When you want to initialize inline editor there are two ways:
If element which is editable (has contenteditable attribute) exists when page is loaded CKEditor will automatically initialize an instance for it. Its name will be taken from that element's id or it will be editor<number>. You can find editors initialized automatically on this sample.
If this element is created dynamically, then you need to initialize editor on your own.
E.g. after appending <div id="editor" contenteditable="true">X</div> to the document you should call:
CKEDITOR.inline( 'editor' )
or
CKEDITOR.inlineAll()
See docs and docs.
You can find editor initialized this way on this sample.
The appendTo method has different use. You can initialize themed (not inline) editor inside specified element. This method also accepts data of editor (as 3rd arg), when all other methods (CKEDITOR.inline, CKEDITOR.replace, CKEDITOR.inlineAll) take data from the element they are replacing/using.
Update
I checked that libraries you use together with CKEditor are poorly written and cause errors you mentioned. Remove json2007.js and rsh.js and CKEditor works fine.
OK, so I have tracked down the problem.
The library I was using for tracking Ajax history and remembering commands for the back button, called Real Simple History, was using a script called json2007 which was intrusive and extended native prototypes to the point where things broke.
RSH.js is kind of old, and I wasn't using it to it's full potential anyway, so my final solution was to rewrite the essential code I needed for that, namely, a listener that watched for anchor (hash) changes in the URL, then parsed those changes and resubmitted the ajax command.
var current_hash = window.location.hash;
function check_hash() {
if ( window.location.hash != current_hash ) {
current_hash = window.location.hash;
refreshAjax();
}
}
hashCheck = setInterval( "check_hash()", 50 );
'refreshAjax()' was an existing function anyway, so this is actually a more elegant solution than I was using with Real Simple History.
After stripping out the json2007.js script, everything else just worked, and CKEditor is beautiful.
Thanks so much for your help, #Reinmar... I appreciate your patience and effort.