Forum How do I...?

JQuery $( this ): error: TypeError: value is not an object

chops
Hello,

I'd like to use JQuery for calculating the padding-right for some td. It works in a browser, but not in Princexml. After some research, I discovered, that the problem can be reduced to this code:
$('td.centeredright16').each(function(){
$(this).width();
});


The problem is this line $(this).width();. The error message is
prince: http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js:2: error: TypeError: value is not an object


I changed the code to see what's going on
$('td.centeredright16').each(function(){
  console.log(typeof($(this)));
  console.log($(this).width());
})

and the error message is a little bit confusing:
object
prince: http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js:2: error: TypeError: value is not an object
object
prince: http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js:2: error: TypeError: value is not an object

$(this) is an object, but I get a TypeError, because it's not an object...?!?

Soo.. What's the problem?

Kind regards,
chops
mikeday
Prince does not support the jQuery width() and height() methods. More generally, JavaScript is run before typesetting, so you cannot interrogate element positions and dimensions and then make changes. There is no layout reflow like in a browser.
itsazzad
Then what is the usefulness of JavaScript in PrinceXML?
mikeday
It can be used to do structural transformations of the input document, eg. generating a table of contents or index, defining new counter styles, generating charts or graphs using SVG or the <canvas> element. It just can't do anything that depends on the result of typesetting the document. Not yet, anyway. :)
itsazzad
Thanks mikeday.
Do you have any example usage of JavaScript?

I have tried to change innerHTML using jQuery and its not working. Seems to be that PrinceXML doesn't work with document.ready
mikeday
You can use onload or DOMContentLoaded events. Not sure why $(document).ready doesn't work though, presumably it uses a similar event mechanism.
dsmith
I'd like to move certain elements to the end of the document but am getting the TypeError: undefined value is not an object. From your comments above, I'm wondering if this kind of structural change is not supported?

Specifically, something like this:
<script type="text/javascript">
  function moveGraphics() {
    var pageNode = document.getElementById('page');
    var graphicsList = document.getElementsByTagName("div");
    for (i = 0; i < graphicsList.length; i++) {
      if (graphicsList[i].classList.contains('graphicWrapper')) {
        pageNode.appendChild(graphicsList[i]);
      }
    }
  }
</script>
textninja
I'm spotting a couple of issues here.

1. Prince does not support classList

Prince does not support classList, probably because it's not part of a W3C web standard, inclusion in the WhatWG standard notwithstanding. With the code you've provided, I recommend using a regular expression matched against an element's className property instead, e.g.
element.className.match(/(^|\s)graphicWrapper($|\s)/)


If the question of whether a graphicWrapper is a div or a span is not important, then an alternate approach is to use document.getElementsByClassName("graphicWrapper") to get the list of elements to shunt to the end. If on the other hand it is important to consider the element type, then you can (and probably should) use document.querySelectorAll("div.graphicWrapper"). This approach has the additional benefit (in the context of your code) of NOT updating its NodeList live, as described in my next point.

2. The NodeList returned by getElementsByTagName updates itself dynamically

document.getElementsByTagName returns a NodeList which is updated "live", which is to say it changes as the DOM does. Because you're looping through a list from the beginning while simultaneously mutating it at the end, that will produce unexpected results. In your specific case I would recommend iterating backwards, e.g.

for (i = graphicsList.length - 1; i >= 0; i--) { /* ... */ }


Depending on the structure of your document, your code might also work if instead of making the change above you decremented i every time you removed an element from the NodeList or pushed it to the end, e.g.

pageNode.appendChild(graphicsList[i--]);


That may prove to be brittle however. One final option is to convert the NodeList to an array before iterating through it, e.g.:

var graphicsList = [].slice.call(document.getElementsByTagName("div"));


Here's a simple demonstration of the live updating behaviour of getElementsByTagName:

<article>
  <div></div>
  <div></div>
  <div></div>
</article>

<script>

  // 1. select divs in article
  var divs = document.querySelector("article").getElementsByTagName("div");
  // 2. log how many (result = 3)
  Log.data("number of divs selected", divs.length);
  // 3. remove the first div
  divs[0].parentNode.removeChild(divs[0]);
  // 4. log how many (result = 2)
  Log.data("number of divs selected", divs.length)

</script>
</body>


That little example should give you a pretty good idea of why iterating through NodeLists from beginning to end rather than the inverse is so fraught with errors.
dsmith
Hello textninja,

Thanks for the reply, that was very helpful. My only issue is that iterating in reverse order ends up putting my images into the document in reverse order since appendChild always appends to the end. I tried iterating in order though and I see why you said not to do that as it did indeed create quite the mess. I'm not that familiar with javascript but I'll look for a different way to do the appends in reverse that works. Thanks again!
textninja
No problem, and insertBefore is probably the function you're looking for. For example, given a new element and a container, to insert the new element to the start of the container you can use code like the following:

container.insertBefore(newElement, container.firstChild);


It's referencing the container's first child element, which may not exist, but the code will work regardless as it falls back to append in such instances.