setting document position

  • 12 replies.
  • This is not yet resolved.
  • This question was started by Trevor.
  • Last post by Trevor.
Trevor
member
May 11th 2010
I have a Xopus Editor html page which contains an edit frame into which the xml document to edit is loaded. This is done by calling

editFrame.contentWindow.Editor.getActiveCanvas().loadDocument(myurl);

If myurl is something like "myfile.xml#Links" I would like to open myfile.xml in the edit frame, and scroll immediately to the anchor named "Links". The anchor does exist; the XSL transform from xml to html generates named anchors for all id'ed elements.

How can I implement this?

Thanks
Sjoerd Visscher
Xopus Team
May 12th 2010
Interesting idea!

The following code does this:

var originalLoadXMLFunction = IO.getLoadXMLFunction();
var hashPart;

function getHashPart(uri)
{
var hashPos = uri.indexOf("#");
if (hashPos > -1)
{
hashPart = uri.substr(hashPos);
uri = uri.substr(0, hashPos);
}
return originalLoadXMLFunction(uri);
}
IO.setLoadXMLFunction(getHashPart);

function scrollToId()
{
if (hashPart)
setTimeout(function () { document.location = hashPart; }, 100);
Editor.removeEventListener("afterRedraw", scrollToId);
}
Editor.addEventListener("afterRedraw", scrollToId);

However, when testing this I noticed that it would be easier if the cursor would jump to the node with the id. (And scrolling then happens automatically.)

Replace the last part of the above code with this:

function scrollToId(evt)
{
Editor.removeEventListener("xmlContextChange", scrollToId);
var node = Editor.getActiveCanvas().getDocument().selectSingleNode("//*[@id='" + hashPart.substr(1) + "']//text()");
if (node)
{
var rng = Editor.Selection.getRange();
rng.selectNode(node);
rng.collapse(true);
Editor.Selection.setRange(rng);
}
}
Editor.addEventListener("xmlContextChange", scrollToId);

Trevor
member
May 12th 2010
Cool, thanks!

I am having two problems with this, though.
Firstly, the code causes Xopus to think that the document has been modified, because the save button is already enabled when the document is displayed, and if the user navigates away the "unsaved edits" dialog appears.
Secondly, if the user navigates away and then requests to edit again - either the same document and location, or a different location, or even a different document entirely, the scroll/redraw doesn't happen.
I've checked the code very carefully and it matches yours precisely - and it is working the first time in, of course.

Is there something we have overlooked?
Trevor
member
May 12th 2010
One more thing; the display is scrolling so that the given element (an anchor placed immediately before a title, normally) is at the very bottom of the window. Is there a method that would position it at the top instead?

Sorry to be tiresome; this would be a neat feature if we could get it right.

Cheers
T
Trevor
member
May 14th 2010
OK, here's the relevant part of my config.js as of today:

var originalLoadXMLFunction = IO.getLoadXMLFunction();
var hashPart;

function getHashPart( uri ) {
var hashPos = uri.indexOf( "#" );
if ( hashPos > -1 ) {
hashPart = uri.substr( hashPos );
uri = uri.substr( 0, hashPos );
}
else {
// without next line get null object error on opening xopus.html
hashPart = "#";
}
return originalLoadXMLFunction( uri );
}
IO.setLoadXMLFunction( getHashPart );

function scrollToId( evt ) {
// only scroll once on load
Editor.removeEventListener( "xmlContextChange", scrollToId );
// this is the element AFTER the id
var node = Editor.getActiveCanvas().getDocument().selectSingleNode("//*[preceding-sibling::*[1]/@id='" + hashPart.substr(1) + "']//text()" );
if ( node ) {
var rng = Editor.Selection.getRange();
rng.selectNode( node );
rng.collapse( true );
Editor.Selection.setRange( rng );
}
}

Editor.addEventListener( "load", myLoadHandler );
function myLoadHandler( evt ) {
// re-establish scroll on each new load
Editor.addEventListener( "xmlContextChange", scrollToId );
}

This code has the following problems:

1. On the first document load the scroll happens, but the save button is enabled in the Xopus toolbar before the user types anything. A ctrl-Z clears the breadcrumb trail in the status line and disables the button, but I'm not sure why Xopus thinks the document has changed.

2. Load a different document (or reload the same document) and scrolling does not happen, instead the user sees a scripting error which says:
Line: 1
Char: 1
Error: 'null' is null or not an object
Code: 0
URL: ../xopus/xopus.html

3. Strangely, if the url passed to xopus's load includes a non-existent #anchor, this error doesn't happen!

4. Not an error, but a quibble; the method used here to show more than a few pixels of the given id is a bit unsatisfactory, but I can't see a better way to achieve what I would like (which is to position the fragment corresponding to the given id at the top of the window).

Thank you for your help so far!

Cheers
T
Sjoerd Visscher
Xopus Team
May 14th 2010
1. This is an Xopus bug. You can work around it by doing the setRange in a setTimeout.
2/3. Then the getRange probably returns null.
4. This can be fixed by getting the corresponding html node, and calling scrollIntoView on it.

This code has these fixes applied:

var originalLoadXMLFunction = IO.getLoadXMLFunction();
var hashPart;

function getHashPart(uri)
{
var hashPos = uri.indexOf("#");
if (hashPos > -1)
{
hashPart = uri.substr(hashPos);
uri = uri.substr(0, hashPos);
Editor.addEventListener("xmlContextChange", scrollToId);
}
return originalLoadXMLFunction(uri);
}
IO.setLoadXMLFunction(getHashPart);

function scrollToId(evt)
{
Editor.removeEventListener("xmlContextChange", scrollToId);
var node = Editor.getActiveCanvas().getDocument().selectSingleNode("//*[@id='" + hashPart.substr(1) + "']");
if (node)
{
var rng = Editor.Selection.getRange();
if (!rng)
{
// try again
Editor.addEventListener("xmlContextChange", scrollToId);
return;
}
rng.selectNode(node);
rng.collapse(true);
setTimeout(function() {
Editor.Selection.setRange(rng);
var htmlNode = Editor.getHTMLElementsForXMLNode(node)[0];
if (htmlNode) htmlNode.scrollIntoView(true);
}, 0);
}
}
Trevor
member
May 14th 2010

Editor.getHTMLElementsForXMLNode

Aha! This method needs to be documented :-)

Thanks very much
T
Trevor
member
May 14th 2010

need to redraw or something

working brilliantly - with one caveat.

On the load of the initial url everything is perfect. The document opens and immediately scrolls to the correct location.

However when a second url is loaded the xml context change (the event we listen for to trigger the scroll-into-view) doesn't happen until the user clicks in this new document. At that point they suddenly find the document redrawn (and if they were trying to select a word they find they have selected a huge chunk of the document!)

Thus: on the initial load the xml context change is fired without user input, but on subsequent loads it is not. What do you suggest as a solution here?
Sjoerd Visscher
Xopus Team
May 15th 2010
Documented, you mean like this?
http://xopus.com/documentation/api/editor/editor.gethtmlelementsforxmlnode.html

The only reason I used xmlContextChange is that the API really is missing a createRange method, and the only way to get one is getRange, which in this case apparently often returns null.

It works the first time, so the script could be adapted to reuse that range. I'll give it a shot when I'm back at work on Tuesday, unless you get it working before then.
Trevor
member
May 17th 2010
> Documented, you mean like this?

Sorry, I must have missed it. Intuitively "context change" isn't the right event to use for this feature, but "load" (which makes more sense to me) didn't seem to work when I tried it. The redraw event has empty documentation so I've no idea whether it is a sensible option or not - but I'll try it next :-)
Sjoerd Visscher
Xopus Team
May 18th 2010

Another try

var originalLoadXMLFunction = IO.getLoadXMLFunction();
var hashPart;
var range;

function getHashPart(uri)
{
var hashPos = uri.indexOf("#");
if (hashPos > -1)
{
hashPart = uri.substr(hashPos);
uri = uri.substr(0, hashPos);
Editor.addEventListener("xmlContextChange", scrollToId);
}
return originalLoadXMLFunction(uri);
}
IO.setLoadXMLFunction(getHashPart);

function scrollToId(evt)
{
if (!range)
{
range = Editor.Selection.getRange();
if (!range) return;
}
Editor.removeEventListener("xmlContextChange", scrollToId);
var node = Editor.getActiveCanvas().getDocument().selectSingleNode("//*[@id='" + hashPart.substr(1) + "']");
if (node)
{
range.selectNode(node);
range.collapse(true);
setTimeout(function() {
Editor.Selection.setRange(range);
var htmlNode = Editor.getHTMLElementsForXMLNode(node)[0];
if (htmlNode) htmlNode.scrollIntoView(true);
}, 0);
}
}
Trevor
member
May 18th 2010

:-)

Brilliant!

Thank you so much
T

Trevor
member
May 26th 2010

minor issue with this

Xopus opens the document for editing and scrolls to the specified anchor. If the user clicks anywhere in the visible area of the document Xopus behaves correctly. However if the user drags the scrollbar up or down to display a different region and then clicks in the document, Xopus jumps and displays the cursor (or the highlight) somewhere else. For example, if I scroll up a page and click at the beginning of a line in the middle of the view, Xopus will redisplay the document so that the line I clicked in is the very first visible line - but the cursor will be near the end of the line. If instead of clicking in the line I double click a word in a line Xopus will suddenly scroll up and highlight a completely different word several paragraphs below where I was clicking.

It is not the scrollToId listener that is doing this, because an alert in the listener is displayed when the document opens, and not displayed when the user clicks. It appears to be one of the default listeners which is generating this wrong behaviour after we have scrolled our anchor into view.

Any ideas?

React

HTML will be shown as HTML code.
Linebreaks and Links starting with http:// are automatically resolved