Unpkg integration of mirador viewer not working within existDB app - exist-db

I have an app I'm translating from xslt-generated html to existDB, and I'd like to include a Mirador viewer.The example (modified for my purposes as shown below) works fine with a static html page, and if I take the html generated from my exist application and save it as a static html file then and put it in there it works fine as well. But if I attempt to use it within my exist app I get a very unstable version of the viewer, wherein any attempt to interact with it results in it eventually throwing an error (which I'll show below under the code). The minified code is not particularly helpful for troubleshooting purposes, so I was wondering if this is a situation others have run into and if so what they did to get around it. The germane bit of code is as follows, using the default Harvard manifest from the github example for testing:
<div id="viewer" allowfullscreen="allowfullscreen">
<script src="https://unpkg.com/mirador#latest/dist/mirador.min.js"></script>
<div id="mirador"></div>
<script type="text/javascript">
const mirador = Mirador.viewer({
"id": "mirador",
"manifests": {
"https://iiif.lib.harvard.edu/manifests/drs:48309543": {
"provider": "Harvard University"
}
},
"windows": [
{
"loadedManifest": "https://iiif.lib.harvard.edu/manifests/drs:48309543",
"canvasIndex": 2,
"thumbnailNavigationPosition": 'far-bottom'
}
]
});
</script>
</div>
The error I'm getting on load is as follows:
TypeError: this.gridRef.current is null
value ThumbnailNavigation.js:35
React 2
unstable_runWithPriority scheduler.production.min.js:19
React 4
unstable_runWithPriority scheduler.production.min.js:19
React 4
Redux 68
Ks createPluggableStore.js:22
e MiradorViewer.js:20
viewer init.js:15
<anonymous> index:15
react-dom.production.min.js:209:194
React 9
unstable_runWithPriority scheduler.production.min.js:19
React 4
Redux 68
Ks createPluggableStore.js:22
e MiradorViewer.js:20
viewer init.js:15
<anonymous> index:15
And any attempt to interact with the canvas results in a "too much recursion" error:
InternalError: too much recursion
c getScrollParent.js:27
G setupEventListeners.js:11
G mirador.min.js:2 -- THIS REPEATS 123 TIMES --
react-dom.production.min.js:209:194
React 9
os
payload
gi
Fa
Es
vc
gc
sc
Xo
unstable_runWithPriority scheduler.production.min.js:19
React 5
qo
Xo
Yo
nc
ya
o useControlled.js:38
we Tooltip.js:273
current Tooltip.js:306
(Async: setTimeout handler)
Oe Tooltip.js:305
React 12
s
p
v
v
st
it
ct
ht
L
F
Jt
Qt
unstable_runWithPriority scheduler.production.min.js:19
React 11
Xt
Zt
Kt
gt
un
es
bc
vc
gc
sc
Xo
unstable_runWithPriority scheduler.production.min.js:19
React 2
qo
Xo
W scheduler.production.min.js:17
onmessage scheduler.production.min.js:14
(Async: EventHandlerNonNull)
<anonymous> scheduler.production.min.js:13
Webpack 15
o
<anonymous>
o
<anonymous>
o
<anonymous>
o
<anonymous>
o
<anonymous>
o
<anonymous>
<anonymous>
<anonymous>
<anonymous>

Perhaps I have run into the same problem. IMO, the (my) problem is (was) a race condition and you have to make sure that you are addressing mirador only once it has finished initializing. The following works (for me) with mirador 2.7.0.
After some other approaches, I have now settled with loading mirador in an iframe and sending messages around:
my main reading view page, work.html, calls an exist-db template, template_work.html, that listens for a message about completion of mirador init and then calls a function miradorLoaded (line 564ff.):
<div id="parent">
<iframe title="Mirador" id="Mirador" src="viewer.html" allowfullscreen="" style="min-width: 99%;height:99%;"/>
</div>
<script type="text/javascript">
window.addEventListener('message', function(message){ if(message.data.type=="mirador.loaded"){ miradorLoaded(); } });
function miradorLoaded() {
/* here comes all the init stuff (see below) */
};
</script>
viewer.html analogously does nothing but call template_viewer.html where the mirador js files are imported and the actual initialization happens in a $(document).ready() function: The Mirador object is created and configured and a message is sent back to the parent (line 95ff.):
$(document).ready(function(){
MyObjects.myMirador = Mirador({
/* set mirador config according to docs */
});
// Binding to events happens in the parent html file (which includes the present file in an iframe).
MyObjects.myMirador.eventEmitter.subscribe('windowAdded', function(){
window.parent.postMessage({ type:'mirador.loaded' },"*");
});
});
That's the main approach. The miradorLoaded function in the main/parent template mentioned above then has a lot of things to do:
bind click event (on elements with pageNo class, that's my page numbers, and they include an attribute data-canvas holding the canvas for this page) for:
opening the popup holding the mirador iframe
pointing mirador to the correct image/canvas
update some elements of the viewer popup like title, buttons
bind click event on the popup's close icon to close the popup again
function miradorLoaded() {
// Bind click event for opening viewer popup
$(document).on('click', ".pageNo", function(event){
event.preventDefault();
$(this).blur();
var $myMirador = document.getElementById('Mirador').contentWindow.MyObjects.myMirador;
var $windowID = $myMirador.saveController.slots[0].window.id;
// Configure viewer to go to the correct canvas
var $targetCanvasID = $(this).attr('data-canvas');
$myMirador.eventEmitter.publish('SET_CURRENT_CANVAS_ID.'+ $windowID, $targetCanvasID);
// Update some values of the dialog popup window
$("#parent small").text($(this).attr('href')); // update Viewer Heading
$("#parent div").attr('title', $(this).attr('href')); // update Viewer Title
// Open the dialog window
$("#parent").dialog('open'); // show Viewer with jquery-ui Dialog method
});
// Bind click event for closing the popup
$('#Mirador').contents().find("#close").on('click', function(){
$("#parent").dialog('close');
});
});
In the case of our application, there is more going on, leading to much more code in the respective functions than what is listed above: We keep track of viewer state with a viewer query parameter (updated when viewer opens or goes to a different canvas, triggers opening the viewer when a URL with such a parameter is called), I have added a "Sync" button (that navigates the full text to the viewer's current canvas) and a "Download pdf" button. But I hope that what is listed is sufficient to get you going...
This approach can be seen in action at, e.g. https://id.salamanca.school/texts/W0034?format=html (but we may soon launch an updated version which probably no longer relies on mirador but the (more lightweight) tify viewer).

Related

How do you make multiple animations start from clicking one object?

I've started learning a-frame recently and I'm trying to create a domino effect type thing. I want all of my animations to start after I click on the first object. I've tried using delay but the delay starts immediately instead of after I start the animation. How do I make it so after someone clicks object 1, object 2's animation would start shortly after?
Let's try the delay approach - with a custom component for some managment :)
Lets say you have a setup like this (html pseudocode):
<a-box class='domino' foo animation='startEvents: go; ...
<a-box class='domino' animation='startEvents: go; delay: 200 ...
<a-box class='domino' animation='startEvents: go; delay: 400 ...
All items have some attributes / components:
The class attribute to make them easy to both grab and distinguish from any other entities.
The animation component which will make them animate - whats important: startEvents - here we'll throw the event which will be emitted simultaneously on all boxes, delay - so every next box will wait before moving.
This foo component - we'll make it by ourselves. It will detect a mouseclick, and emit the go event on all boxes.
So the concept is as follows:
We click one box with the foo component
the foo component detects the click and emits go on all .domino elements
all .domino elements should start their animations one after another - but each one 200ms later than the previous one.
Now lets make the custom component. Keep in mind it has to be defined before the <a-scene>:
<script src='component.js'></script>
<script>
// component
</script>
<a-scene>
</a-scene>
We will implement all logic within the init function - which is called upon initialisation.
// component definition
AFRAME.registerComponent('foo', {
// this is called upon initialisation
init: function() {
// grab all the domino pieces
var pieces = document.getElementsByClassName('domino')
// if this one gets pressed...
this.el.addEventListener('click', e => {
// ...emit the event on all of them
for (let piece of pieces) {
piece.emit('go')
}
})
}
})
and actually - thats it. To see it in action - here are two examples - in both clicking the blue box, should start the animation.
Simple delayed animation cascade
Domino-like animation

Javascript "Click" event on disabled input type:file in Firefox

I want to make input type="file" becomes available and clicked only when I click on some element, which is also activated it (for example, stylized span).
For this I have the Javascript parameters on span:
onclick="document.getElementById('upload_hidden').Disabled = false;
document.getElementById('upload_hidden').Click();"
But the trouble is that in Firefox only first click removes the input attribute disabled and second - opens the file selection window. In Chrome - all OK: input become enabled and clicked by first span click.
Why, first click in Firefox does not work ? :(
http://jsfiddle.net/ey47G/
P.S. In firefox v21 - all OK. Firefox v25 and v26 - have this trouble.
I could imagine that the script is already ahead when it tries to click the button - but the button is still disabled
var f = document.getElementById('f');
var s = document.getElementById('s');
s.onclick = function () {
f.removeAttribute('disabled');
setTimeout(function(){ f.click(); }, 100); // run the explorer after 100 ms
}
This does work.
http://plnkr.co/edit/9syOfSJHaJ4b3bhRufpv?p=preview

jquery load order affects list

I have the following jQuery to load status to a profile page using a ul with li status items.
The each() takes items from a JSON callback and the load() is supposed to ensure that the image is available before the li is created:
(showpic() gives me a well-formed url to use.)
function showStatus(data){
var jsondata = $.parseJSON(data);
var testText = "";
$('#doNews').empty();
$('#doNews').append($('<ul/>', {"class": "newsList", id: "theNews"}));
$.each(jsondata, function(i, item){
$('<img src="' + showpic(item[3]) + '" class="newsImage">')
.load(function(){
$(this)
.appendTo($('#theNews'))
.wrap($('<li>', {"class": "newsItem"}))
.closest('li')
.append(item[5])
});
});
$("#statustext").val('');
}
the problem is that the status feed now seems to be written to the page in the order the images load. i.e., instead of being written according to the JSON item order, the li s are written in the order of loaded images (this has the effect of grouping status by user, not writing it out by date, as in the JSON).
So...
how would I both write items in the JSON order and still wait for the img to load?
By the way, I looked at this qn:
jQuery each() and load() ordering
and it seems to be on the right track, but when I tried using hide() and then show()inside the load() function, it never seemed to be called, and the img remained hidden. Please give me a simple example if this is the solution you suggest.
Thanks!
Either:
1) Maintain a counter & timeout, count the # of images to load at start, create the items as "hidden", countdown the number of unloaded as they load, then (when counter hits 0 or timer times out) display everything;
or:
2) Create the items as hidden, and show them in the 'load' event. Items will appear in arbitrary order, but will end up being correctly ordered.
Here's a possible example: your code isn't very clear as to what structure is being built & where appended, so this is just a rough (but clearly coded) outline.
Try using intermediate variables more, rather than vast fabulous jQuery constructions, in your own. It will help you debug it.
console.log('loading news items');
$.each( jsondata, function(i, item){
console.log(' item', showpic(item[3]), item[5]);
var img = $('<img src="' + showpic(item[3]) + '" class="newsImage" >');
var element = img.wrap($('<li>', {"class": "newsItem", "style": "display:none;"}))
.closest('li')
.append(item[5]);
element.appendTo( $('#theNews'));
// when the IMG loads, find it's surrounding LI.. and show it.
img.load( function(){
console.log(' loaded', $(this).attr('src'));
$(this).closest('li').show();
});
// put a timer & show it anyway after 8s, if img still hasn't loaded.
element.delay( 8000).show(0);
});
You will also notice logging in the code. Logging is good software practice. Console.log is not available on IE, so you should eventually shim it or use a small library function of your own.
Fundamentally, you have to get away from adding to the DOM inside the 'load' event. That's what's causing the mis-ordering. Add to the DOM in the jsondata each() function, or in a separate well-structured bit of code that guarantees correct ordering.

Extension: How to intercept AJAX responses?

Since extensions can not access unsafeWindow, like Firefox can, to hook into DOM scripts am I looking for other ideas so I come to SO for help!
How about using some code to inject into DOM and sending the intercepted response to a background page, which then does some initial processing before calling a content script for final processing. When done, it answers to the background with a modified response, or the original (it depends), and the background page sends the response back to DOM which handles it to the DOM script response function.
There is just one problem with this, a background page cant communicate with the DOM.
I did a small test with injecting some code, where I output something to the console and an alert. The result wasnt good, as the alert fired but the console was empty - not even an error, which makes me wonder - what console received the output ?
function injectCode(fn){ // Executing an anonymous script
var script = document.createElement('script');
script.type = 'application/javascript';
script.textContent = '(' + fn + ')();';
document.documentElement.appendChild(script); // run the script
document.documentElement.removeChild(script); // clean up
}
var code = function(){
console.log('dom',window);
alert('code injected');
}
injectCode(code);
I also tried addEventListener, with DOMAttrModified DOMSubtreeModified DOMNodeInserted, on DOM elements that change when the DOM ajax response is fully parsed but all failed to fire.
Am I trying to do the impossible, by any means ?
Before continuing, make sure that you know the differences between the script contexts in an extension.
To inject a script from the background page, you have to execute a Content script, which on his turn injects the script as mentioned in your question / here.
Examples (using chrome.tabs.executeScript):
// null = current active tab
// Simple code, background:
chrome.tabs.executeScript(null, {
code: [
'var s = document.createElement("script");',
's.textContent = "console.log(window);";',
'(document.head||document.documentElement).appendChild(s);',
's.parentNode.removeChild(s);'
].join('\n')
});
I can imagine that this method is not doable for a big chuck of code. For a set of pre-defined scripts, you can then use two scripts: the code itself, and a helper script:
// config.js
var fn_code = function() {
console.log(window); ....
};
// helper.js
var s = document.createElement('script');
s.textContent = '(' + fn_code + ')();';
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
// Background:
chrome.tabs.executeScript(null, {file: 'config.js'}, function() {
chrome.tabs.executeScript(null, {file: 'helper.js'});
});
Note: I did not directly link to "config.js", because that complicates the use when using manifest version 2, see "web_accessible_resources".
The previous method only shows how to execute code in one direction (background -> page). If there's a need to activate a background's function from the injected script, you have to define and listen to a custom event handler. See this answer + demo.
Because the code is injected, thus runs in the scope of the page, you have to check the console at the page.
When chrome.tabs.executeScript fails to execute the Content script (eg. because the extension does not have the permission to access a certain page), an error is logged at the console in the background page. This console can be accessed by following these steps.

Unable to remove element loaded by AJAX

I am trying to remove an element on AJAX success which was loaded and attached to the document during a previous AJAX call.
My code looks something like this:
$("#jobs-table-body").on("click", ".one-rc-button", function() {
var ctx = $.parseJSON($(this).siblings(".context").html());
$("#one-rc-candidate-id").val(ctx.candidateId);
$("#one-rc-job-id").val(ctx.jobId);
var loader = $("#wrapper").loader();
$.post($("#one-rc-form").attr("action"), $("#one-rc-form").serialize(), function(result) {
loader.remove();
if(result.success) {
// This works and returns 1
alert($("#candidate-row-" + result.rejectedCandidateId).length);
// This doesn't seem to be doing anything
$("#candidate-row-" + result.rejectedCandidateId).remove();
} else {
//$("#one-jc-messages").html(result.error);
}
});
});
The elements .one-rc-button and #candidate-row-<candidateId> were loaded by a previous AJAX call and they are attached to the document as I can very well see them on my page.
Now, on click of the previously generated .one-rc-button, I trigger a second AJAX call (which works fine) and on result.success, I want to delete the #candidate-row-<candidateId> (which is within the previously generated parent element).
The alert works and returns 1. So I know for sure that the selector is fine and it is matching one unique element.
What I don't understand is why it is unable to remove the element from the page.
Observations
I use Firefox 10.0.2 where this problem is reproducible.
On IE 8, it works (element gets removed)
On debugging the script on Firebug, I can verify that I have got a handle to the right eleemnt.
Try using FireBug to set a breakpoint on that line so you can see exactly what it's getting from that selector. Ideally break up the statement first, like this:
var unwantedDiv = $("#candidate-row-" + result.rejectedCandidateId);
unwantedDiv.remove(); // <-- Set a breakpoint on this line
You can then look at the unwantedDiv variable in the watch pane on the right of the firebug debugger and see what it is, what methods it has/has not got etc. I would assume that you are not getting back exactly what you think you are, possibly because of how you attached the div after the previous AJAX call. More information about JavaScript debugging with FireBug here.
Another option is to turn on strict warnings in the firebug console and see if you get any 'undefined method' errors, which don't stop the show on FireFox, but just bounce you out of that function. Do you get an error in IE?
Solved it by a really ugly workaround. I am still not sure what causes this behaviour.
if(result.success) {
var removeThis = $("#candidate-row-" + result.rejectedCandidateId);
removeThis.remove();
removeThis = $("#candidate-row-" + result.rejectedCandidateId);
if(removeThis.length != 0) {
removeThis.remove();
}
}
Now it works on both Firefox and IE.

Resources