3rd party extension forminput.js for HTML input elements

84 views
Skip to first unread message

wkoe...@googlemail.com

unread,
Apr 11, 2015, 10:17:04 AM4/11/15
to mathja...@googlegroups.com
Thanks to Davide for this helpful extension.
I have customized this extension to my needs by giving style properties to the input element after creation through HTML.Element("input"). That all works fine.

Now I need to attach event handlers to the input elements and that seems a big problem:

In line 65 of forminput.js:

var input = HTML.Element("input", ...);
input.onfocus = function() {alert("works");};

doesn't work. It looks like the HTML element is not itself integrated into the document, but a copy that contains all the properties without the event handlers.

Any solution would be greatly appreciated.

Wilhelm

Davide P. Cervone

unread,
Apr 11, 2015, 10:57:14 AM4/11/15
to mathja...@googlegroups.com
You are correct that the input element isn't itself inserted into the document.  What is inserted is a cloned copy created via cloneNode(), and apparently that doesn't copy attached event handlers.  It does seem to copy attributes, however, so perhaps

var input = HTML.Element("input", ...);
input.setAttribute("on focus",'alert("works")');

would work.  This does mean you have to give a string to be evaluated not a function, but perhaps you can arrange for that to call the function you really want to use.  Makes closures harder, but perhaps it is enough for your situation.

Alternatively, you can attach an ID to the input and after the math is typeset, get the element by ID and attach the listener afterward.  That is not ideal, but gives another possibility.  Another possibility would be to put a container around the math and add an event listener to that, and check if the event target is your input element.

A more aggressive approach would be to modify the HTML-CSS annotation-xml implementation to copy event handlers onto the cloned copy.   For your particular example, something like the following might do it:

<script type="text/x-mathjax-config">
MathJax.Hub.Register.StartupHook("HTML-CSS annotation-xml Ready",function () {
  MML.xml.Augment({
    toHTML: function (span,encoding) {
      for (var i = 0, m = this.data.length; i < m; i++) {
        var child = span.appendChild(this.data[i].cloneNode(true));
        child.onfocus = this.data[i].onfocus;
// ... add any other event handlers you want to copy
      }
      var bbox = span.bbox; span.bbox = null;
      bbox.rw = bbox.w = HTMLCSS.getW(span);
      var HD = HTMLCSS.getHD(span);
      bbox.h = HD.h; bbox.d = HD.d;
      span.bbox = bbox;
    }
  });
});
</script>

Hope that helps.

Davide


--
You received this message because you are subscribed to the Google Groups "MathJax Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mathjax-user...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Wilhelm Köpper

unread,
Apr 12, 2015, 11:00:08 AM4/12/15
to mathja...@googlegroups.com
Dear Davide,
thanks a lot for your great support. I have tried the last of your suggestions with annotation-xml but it did not work. The DOM object arriving in toHTML() is already a clone from what was created in the formInput extension.

What I need is in fact a pointer to the DOM object that is appended to the document tree and it seems that MathJax is cloning a lot. So I will resort to the method of getElementById and push the function on the MathJax.Hub.Queue.

Anyway I am very grateful for your explanations which let me get a little deeper glimpse into the mechanics of MathJax.

All the best to you!
Wilhelm  

--
You received this message because you are subscribed to a topic in the Google Groups "MathJax Users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/mathjax-users/c75Vg2J2ghM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to mathjax-user...@googlegroups.com.

Davide P. Cervone

unread,
Apr 12, 2015, 7:34:06 PM4/12/15
to mathja...@googlegroups.com
You're right, I hadn't looked far enough into the code.  The annotation-xml element usually comes from MathML, not TeX, and in that case, the original MathML has been parsed into a separate XML document, so it can't be put into the main document directly.  So when the internal annotation-xml object is created, MathJax used importNode() to make a copy that CAN be put into the current document.  I hadn't taken that copying into account.  (I only handled the later copy that toHTML() does to put a copy of the node into the document.)

If you are only using TeX input, then you can use

MathJax.Hub.Register.StartupHook("mml Jax Ready",function () {
  MathJax.ElementJax.mml.xml.Augment({
    Import: function (node) {return node}
  });
});

to avoid the importNode() call (since it is not needed for the input element that is created using HTML.Element).

If you also use MathML input, then you can use

  MathJax.Hub.Register.StartupHook("mml Jax Ready",function () {
    var MML = MathJax.ElementJax.mml;
    var IMPORT = MML.xml.prototype.Import;
    MML.xml.Augment({
      Import: function (node) {
        var copy = IMPORT.call(this,node);
        copy.onfocus = node.onfocus;
//  ... copy other handlers here ...
        return copy;
      }
    });
  });

Here is code that does the complete job:

(function () {
  //
  //  The handlers to copy from the input node
  //
  var HANDLERS = ["onfocus"];
  //
  //  Copy the needed handlers
  //
  function CopyHandlers(from,to) {
    for (var i = 0, m = HANDLERS.length; i < m; i++) {
      var id = HANDLERS[i];
      if (from[id]) to[id] = from[id];
    }
  }
  //
  //  Hook into the xml Import() method to copy the needed
  //  handlers during the import.
  //
  MathJax.Hub.Register.StartupHook("mml Jax Ready",function () {
    var MML = MathJax.ElementJax.mml;
    var IMPORT = MML.xml.prototype.Import;
    MML.xml.Augment({
      Import: function (node) {
        var copy = IMPORT.call(this,node);
        CopyHandlers(node,copy);
        return copy;
      }
    });
  });
  //
  //  Hook into the xml toHTML() method to copy the needed
  //  handlers to the copied node.
  //

  MathJax.Hub.Register.StartupHook("HTML-CSS annotation-xml Ready",function () {
    var MML = MathJax.ElementJax.mml,
        HTMLCSS = MathJax.OutputJax["HTML-CSS"];

    MML.xml.Augment({
      toHTML: function (span,encoding) {
        for (var i = 0, m = this.data.length; i < m; i++) {
          var child = span.appendChild(this.data[i].cloneNode(true));
          CopyHandlers(this.data[i],child);

        }
        var bbox = span.bbox; span.bbox = null;
        bbox.rw = bbox.w = HTMLCSS.getW(span);
        var HD = HTMLCSS.getHD(span);
        bbox.h = HD.h; bbox.d = HD.d;
        span.bbox = bbox;
      }
    });
  });
})();

Hope that works for you, if you want to try it.

Davide

wkoe...@googlemail.com

unread,
Apr 16, 2015, 8:22:46 AM4/16/15
to mathja...@googlegroups.com, dp...@union.edu
thanks again, Davide. Now finally with your last patch everything works fine.

by the way: cloning elements is quite a time consuming operation and the only real complaint about MathJax that I have ever read was about the rendering speed. So I think it would be good to insert some if clauses for checking if the cloning is really necessary. I am well aware that there is generally a trade off between execution speed and readability/maintainability of code and one has to find a suitable compromise. From the for-loops where j=x.length is put into the first section I gather that there have already been efforts to improve speed.
Wilhelm

Davide P. Cervone

unread,
Apr 16, 2015, 9:29:45 AM4/16/15
to wkoe...@googlemail.com, mathja...@googlegroups.com
Thanks for the suggestion.  A couple of comments:  first, the cloning is only done for annotation-xml when used as the first child of a semantics node, which is a relatively rare situation.  Second, the initial importNode is nearly always required since the primary way of using annotation-xml is through MathML input, and that is processed in a separate document (where importing is required).  The only way this would not be the case is if the annotation-xml is created "by hand" as in the form input extension (this is the only case I know of, in fact).  Third, the cloneNode() from the internal representation to the page is also important, both for technical reasons involving potential memory leaks, and because the output can be needed more than once from the same internal form (e.g., during a zoom operation), and the original can't be inserted into the document in two different places.

Davide

Wilhelm Köpper

unread,
Apr 26, 2015, 12:26:06 PM4/26/15
to Davide P. Cervone, mathja...@googlegroups.com
ok, I see you have good reasons for cloning.

After everything worked just perfectly in version 2.4 I switched to version 2.5 today and now the output is broken (see the 2 attached pics). This output comes from html code:

--script in display mode--      (same problem without display mode)
\frac{\inputElement1}{\inputElement2}
--script end--

\inputElement is created with the formInput extension.

while mathjax 2.4 obviously took into account the width and height of the input elements created in the formInput extension and generated a fraction line long enough for numerator and denominator, mathjax 2.5 only creates an idea of a fraction line, probably calculating with an input element width of zero.

I have tried to get into the causes of the problem with the element inspector of firefox and found that the reason lies in the style clipping. Switching the clipping off immediately displays the full input box.

As always your expertise is much appreciated.

Wilhelm
broken_in_ver_25.png
style_clipping_problem.png
works_fine_in_ver_24.png

wkoe...@googlemail.com

unread,
Apr 27, 2015, 6:29:12 AM4/27/15
to mathja...@googlegroups.com
Thanks Davide, I didn't use the new size computations you gave me because I thought this was the standard code for a later version. Now I included them in my patch (which I included in my config.js) and everything works perfectly  :-)

I am really grateful for your support and so I would like to contribute my enhanced version of formInput (see below). It takes two arguments only: 1. Any attributes for the input/select element in the form attribute: value with comma as a separator, 2. any style attributes. In addition type: select is permitted. In that case options must be given with options:|first option|second option|third .... in the first argument. The | or any other first character is used as separator between options.

All the best to you
Wilhelm


MathJax.Callback.Queue(
MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
  var VERSION = "1.0";
  var TEX = MathJax.InputJax.TeX,
      TEXDEF = TEX.Definitions,
      MML = MathJax.ElementJax.mml,
      HTML = MathJax.HTML;
  TEXDEF.macros.inp = "inp";
  TEX.Parse.Augment({
    //  Implements \inp{property: value, ...}{style}
    inp: function (name) {
      var props = this.GetArgument(name);
      var style = this.GetArgument(name);
      var inptype = "input";
      props = props.split(","); style = style.trim();
      for (var i=props.length-1; i>=0; --i) {
          props[i] = props[i].split(":");
          if (props[i].length !== 2) {TEX.Error("Wrong number of arguments in "+name+" for attribute "+props[i][0]); return;}
          props[i][0] = props[i][0].trim(); props[i][1] = props[i][1].trim();
          if (props[i][0] === "type" && props[i][1] === "select") {
             inptype = "select";
             props.splice(i, 1);
          }
          if (props[i][0] === "options") var options = props.splice(i, 1);
      }
      if (inptype === "select" && typeof options === "undefined") {TEX.Error(name+" type=select needs options: |o1|o2|..."); return;}
      if (style.indexOf("position") === -1) {  // if no position is given in style it is now added in order to activate the z-Index
  if (style.charAt(style.length - 1) !== ";") style += ";";
         style += " position:relative;";
      }
      if (style.indexOf("z-index") === -1) {  // if no z-Index is given in style it is now added in order to prevent a covering of input boxes by nearby math
  if (style.charAt(style.length - 1) !== ";") style += ";";
         style += " z-index:5;";
      }
      var input = HTML.Element(inptype);
      for (var i=0, j=props.length; i < j; ++i) input.setAttribute(props[i][0], props[i][1]);
      input.setAttribute("style", style);
      input.setAttribute("xmlns","http://www.w3.org/1999/xhtml");
      if (inptype === "select") {
         options = options[0][1].split(options[0][1].substr(0, 1));
         options[0] = 'Please select';
         for (i=0; i<options.length; ++i) {
             var opt = document.createElement("option");
             if (i>0) opt.value = i; else opt.value = "";
             var opttext = document.createTextNode(options[i].trim());
             opt.appendChild(opttext);
             input.appendChild(opt);
         }
      }
      var mml = MML["annotation-xml"](MML.xml(input)).With({encoding:"application/xhtml+xml",isToken:true});
      this.Push(MML.semantics(mml));
    }
  });
 
}));
MathJax.Ajax.loadComplete("[MathJax]/extensions/inp.js");


This is issue #1139 at


and is fixed in version 2.5.2 (due out next week).  

On the other hand, the change to the MML.xml object that I gave you earlier included the fix for the size computations.  Are you using that?

Davide

Peter Krautzberger

unread,
Apr 29, 2015, 2:27:07 PM4/29/15
to mathja...@googlegroups.com
I didn't follow the discussion in detail so if someone could make a pull request to https://github.com/mathjax/MathJax-third-party-extensions/tree/master/forminput I'd appreciate it.

Peter.

Wilhelm Köpper

unread,
May 1, 2015, 3:26:59 AM5/1/15
to mathja...@googlegroups.com
Dear Peter, I don't have a git hub account. Further my version is not compatible to Davide's because the arguments are different. So it is not just a patch but a completely new version. Also the code is not very long and everyone who is familiar with the basics of Javascript can easily write his own version of this simple and useful extension. I think its ok to leave it here in the news group for anyone who is interested in this extension as a demonstration how to customize it to individual needs.

Wilhelm

--
You received this message because you are subscribed to a topic in the Google Groups "MathJax Users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/mathjax-users/c75Vg2J2ghM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to mathjax-user...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages