widgetParser.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. define("dojox/xml/widgetParser", [
  2. "dojo/_base/lang", // dojo.getObject
  3. "dojo/_base/window", // dojo.doc
  4. "dojo/_base/sniff", // dojo.isIE
  5. "dojo/query",
  6. "dojo/parser",
  7. "dojox/xml/parser"
  8. ], function(dojo, window, has, query, parser, dxparser){
  9. var dXml = lang.getObject("dojox.xml", true);
  10. /**
  11. Take some sort of xml block
  12. * like <dojo.button caption="blah"/> and turn
  13. * it into a widget..
  14. */
  15. /**
  16. * We want to support something like:
  17. * <body>
  18. * <script>
  19. * <dijit.layout.SplitContainer>
  20. * <dijit.button/>
  21. * <div>...</div>
  22. * </dijit.layout.SplitContainer>
  23. * </body>
  24. *
  25. * This is very tricky because if we parse this as XML then the <div> tag
  26. * is actually an XML tag, not an XML tag, which is problematic in at least
  27. * IE.
  28. *
  29. * So the strategy is this, silly as it may be: Convert EVERYTHING to HTML
  30. * nodes, including the dijit.layout.SplitContainer by converting it to a
  31. * div with the dojoType. Then run it through the standard parser.
  32. * The more HTML you have relative to XML the less extra overhead this is.
  33. *
  34. * For something that is all XML we could have a different approach,
  35. * perhaps signified by a different type of script tag. In that case we
  36. * could just instantiate all the elements without a sourceNodeRef and then
  37. * add the top level components to the app.
  38. *
  39. * That is very straightforward but I haven't done it.
  40. *
  41. * Right now there is no mechanism to have an intermediary bridge between
  42. * the XML and the widget, because we are relying on dojo.parser
  43. * to do the instantiation. It isn't clear to me why we would want
  44. * those bridges in this approach and not in that approach.
  45. *
  46. */
  47. xXml.widgetParser = new function(){
  48. var d = dojo;
  49. this.parseNode = function(node){
  50. var toBuild = [];
  51. //TODO figure out the proper type
  52. d.query("script[type='text/xml']", node).forEach(function(script){
  53. toBuild.push.apply(toBuild, this._processScript(script));
  54. }, this).orphan();
  55. //instantiate everything at the end, doing it piecewise can give ID conflicts
  56. return d.parser.instantiate(toBuild);
  57. };
  58. this._processScript = function(script){
  59. //the text is either loaded from a separate file by the src
  60. //attribute or underneath the src tag
  61. var text = script.src ? d._getText(script.src) : script.innerHTML || script.firstChild.nodeValue;
  62. var htmlNode = this.toHTML( dojox.xml.parser.parse(text).firstChild );
  63. //make the list BEFORE we copy things over to keep the query scope as
  64. //small as possible
  65. var ret = d.query('[dojoType]', htmlNode);
  66. //remove the script tag and replace with new HTML block
  67. query(">", htmlNode).place(script, "before")
  68. script.parentNode.removeChild(script);
  69. return ret;
  70. };
  71. /**
  72. * Given an XML node converts it to HTML where the existing HTML
  73. * is preserved and the dojo widget tags are converted to divs
  74. * with dojoType on them.
  75. */
  76. this.toHTML = function (/*XmlNode*/ node){
  77. var newNode;
  78. var nodeName = node.nodeName;
  79. var dd = window.doc;
  80. var type = node.nodeType;
  81. ///node type 3 and 4 are text and cdata
  82. if(type >= 3){
  83. return dd.createTextNode( (type == 3 || type == 4) ? node.nodeValue : "" );
  84. }
  85. var localName = node.localName||nodeName.split(":").pop();
  86. //TODO:
  87. // only check for namespace ONCE ever, instead of each time here,
  88. // by mixing in the right check for each browser?
  89. var namespace = node.namespaceURI || (node.getNamespaceUri ? node.getNamespaceUri() : "");
  90. //TODO check for some real namespace
  91. if(namespace == "html"){
  92. newNode = dd.createElement(localName);
  93. }else{
  94. var dojoType = namespace + "." + localName;
  95. /**
  96. * This is a horrible hack we need because creating a <div>
  97. * with <option> children doesn't work well. Specifically with
  98. * dojo.Declaration at some point the <option> tags get lost
  99. * entirely so we need the parent of <option> tags to be <select>
  100. * tags. (Not a problem outside of dojo.Delcaration)
  101. * There are a couple other ways we could do this:
  102. * 1. Look at the first element child to see if it is an option and
  103. * if so create a <select> here.
  104. * 2. When we add a child to parent fix up the parent then if the
  105. * child is an <option> and the parent isn't a <select>.
  106. * Both of those are a bit messy and slower than this.
  107. *
  108. * This is potentially a problem for other tag combinations as well,
  109. * such as <tr> under a <table> or <li> under a <ul>/<ol>.
  110. * (dojox.widget.SortList for example). Probably need a robust strategy for
  111. * dealing with this. Worst case scenario for now is that user has to use
  112. * html tag with dojoType for misbehaving widget.
  113. */
  114. newNode = newNode || dd.createElement((dojoType == "dijit.form.ComboBox") ? "select" : "div");
  115. newNode.setAttribute("dojoType", dojoType);
  116. }
  117. // TODO:
  118. // we should probably set this up different, mixin a function
  119. // depending on if it is IE rather than checking every time here
  120. // the xmlns problem and the style problem are both IE specific
  121. d.forEach(node.attributes, function(attr){
  122. // NOTE: IE always iterates *all* properties!!!
  123. var name = attr.name || attr.nodeName;
  124. var value = attr.value || attr.nodeValue;
  125. if(name.indexOf("xmlns") != 0){
  126. // style=blah blah blah is a problem, in IE if you use
  127. // setAttribute here you get all sorts of problems. Maybe it
  128. // would be better to just create a giant string of HTML
  129. // instead of an object graph, then set innerHTML on something
  130. // to get the object graph? That might be cleaner... that way
  131. // is uses the browser HTML parsing exactly at is and won't
  132. // cause any sort of issues. We could just special case style
  133. // as well?
  134. if(has("ie") && name == "style"){
  135. newNode.style.setAttribute("cssText", value);
  136. }else{
  137. newNode.setAttribute(name, value);
  138. }
  139. }
  140. });
  141. d.forEach(node.childNodes, function(cn){
  142. var childNode = this.toHTML(cn);
  143. // script tags in IE don't like appendChild, innerHTML or innerText
  144. // so if we are creating one programatically set text instead
  145. // could special case this for IE only
  146. if(localName == "script"){
  147. newNode.text += childNode.nodeValue;
  148. }else{
  149. newNode.appendChild(childNode);
  150. }
  151. }, this);
  152. return newNode;
  153. };
  154. }();
  155. return dXml.widgetParser;
  156. });